X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;ds=sidebyside;f=src%2Fgame_bd%2Fbd_bdcff.c;h=31fab6e814144d2b5a88cf19e200dbcaea7fd50a;hb=10547cdd1dfb8b60766e04f26adc58fb3a31197b;hp=9972b7f2b9a4e0309002690d4ede7cccc2aa9da7;hpb=ec529b5df69b376a1d55e38862975b7d9a26ea97;p=rocksndiamonds.git diff --git a/src/game_bd/bd_bdcff.c b/src/game_bd/bd_bdcff.c index 9972b7f2..31fab6e8 100644 --- a/src/game_bd/bd_bdcff.c +++ b/src/game_bd/bd_bdcff.c @@ -14,9 +14,6 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include -#include - #include #include "main_bd.h" @@ -107,17 +104,17 @@ static boolean attrib_is_valid_for_cave(const char *attrib) int i; /* bdcff engine flag............ */ - if (strcasecmp(attrib, "Engine")==0) + if (strcasecmp(attrib, "Engine") == 0) return TRUE; /* old flags - for compatibility */ - if (strcasecmp(attrib, "BD1Scheduling")==0) + if (strcasecmp(attrib, "BD1Scheduling") == 0) return TRUE; - if (strcasecmp(attrib, "SnapExplosions")==0) + if (strcasecmp(attrib, "SnapExplosions") == 0) return TRUE; - if (strcasecmp(attrib, "AmoebaProperties")==0) + if (strcasecmp(attrib, "AmoebaProperties") == 0) return TRUE; /* search in property database */ @@ -140,7 +137,7 @@ static boolean attrib_is_valid_for_caveset(const char *attrib) return FALSE; } -static boolean struct_set_property(gpointer str, const GdStructDescriptor *prop_desc, +static boolean struct_set_property(void *str, const GdStructDescriptor *prop_desc, const char *attrib, const char *param, int ratio) { char **params; @@ -151,7 +148,7 @@ static boolean struct_set_property(gpointer str, const GdStructDescriptor *prop_ boolean was_string; params = getSplitStringArray(param, " ", -1); - paramcount = g_strv_length(params); + paramcount = getStringArrayLength(params); identifier_found = FALSE; /* check all known tags. do not exit this loop if identifier_found == true... @@ -163,7 +160,7 @@ static boolean struct_set_property(gpointer str, const GdStructDescriptor *prop_ if (strcasecmp(prop_desc[i].identifier, attrib) == 0) { /* found the identifier */ - gpointer value = G_STRUCT_MEMBER_P(str, prop_desc[i].offset); + void *value = STRUCT_MEMBER_P(str, prop_desc[i].offset); /* these point to the same, but to avoid the awkward cast syntax */ int *ivalue = value; @@ -188,12 +185,10 @@ static boolean struct_set_property(gpointer str, const GdStructDescriptor *prop_ if (prop_desc[i].type == GD_TYPE_LONGSTRING) { - GString *str = *(GString **)value; - char *compressed; + char **str = (char **)value; - compressed = g_strcompress(param); - g_string_assign(str, compressed); - free(compressed); + checked_free(*str); + *str = getUnescapedString(param); /* remember this to skip checking the number of parameters at the end of the function */ was_string = TRUE; @@ -206,7 +201,7 @@ static boolean struct_set_property(gpointer str, const GdStructDescriptor *prop_ for (j = 0; j < prop_desc[i].count && params[paramindex] != NULL; j++) { boolean success = FALSE; - gdouble res; + double res; switch (prop_desc[i].type) { @@ -256,7 +251,8 @@ static boolean struct_set_property(gpointer str, const GdStructDescriptor *prop_ break; case GD_TYPE_PROBABILITY: - res = g_ascii_strtod(params[paramindex], NULL); + errno = 0; /* must be reset before calling strtod() to detect overflow/underflow */ + res = strtod(params[paramindex], NULL); if (errno == 0 && res >= 0 && res <= 1) { /* fill all remaining items in array - may be only one */ @@ -270,7 +266,8 @@ static boolean struct_set_property(gpointer str, const GdStructDescriptor *prop_ break; case GD_TYPE_RATIO: - res = g_ascii_strtod (params[paramindex], NULL); + errno = 0; /* must be reset before calling strtod() to detect overflow/underflow */ + res = strtod (params[paramindex], NULL); if (errno == 0 && res >= 0 && res <= 1) { for (k = j; k < prop_desc[i].count; k++) @@ -358,7 +355,7 @@ static boolean replay_store_more_from_bdcff(GdReplay *replay, const char *param) } /* report all remaining tags; called after the above function. */ -static void replay_report_unknown_tags_func(const char *attrib, const char *param, gpointer data) +static void replay_report_unknown_tags_func(const char *attrib, const char *param, void *data) { Warn("unknown replay tag '%s'", attrib); } @@ -389,10 +386,10 @@ static boolean replay_process_tags_func(const char *attrib, const char *param, G } /* ... */ -static void replay_process_tags(GdReplay *replay, GHashTable *tags) +static void replay_process_tags(GdReplay *replay, HashTable *tags) { /* process all tags */ - g_hash_table_foreach_remove(tags, (GHRFunc) replay_process_tags_func, replay); + hashtable_foreach_remove(tags, (hashtable_remove_fn)replay_process_tags_func, replay); } /* a GHashTable foreach func. @@ -401,9 +398,11 @@ static void replay_process_tags(GdReplay *replay, GHashTable *tags) static boolean cave_process_tags_func(const char *attrib, const char *param, GdCave *cave) { char **params; + int paramcount; boolean identifier_found; params = getSplitStringArray(param, " ", -1); + paramcount = getStringArrayLength(params); identifier_found = FALSE; if (strcasecmp(attrib, "SnapExplosions") == 0) @@ -465,17 +464,65 @@ static boolean cave_process_tags_func(const char *attrib, const char *param, GdC else if (strcasecmp(attrib, "Colors") == 0) { /* colors attribute is a mess, have to process explicitly */ + boolean ok = TRUE; /* Colors = [border background] foreground1 foreground2 foreground3 [amoeba slime] */ identifier_found = TRUE; - cave->colorb = GD_GDASH_BLACK; /* border - black */ - cave->color0 = GD_GDASH_BLACK; /* background - black */ - cave->color1 = GD_GDASH_RED; - cave->color2 = GD_GDASH_PURPLE; - cave->color3 = GD_GDASH_YELLOW; - cave->color4 = cave->color3; /* amoeba */ - cave->color5 = cave->color1; /* slime */ + if (paramcount == 3) + { + /* only color1,2,3 */ + cave->colorb = gd_c64_color(0); /* border - black */ + cave->color0 = gd_c64_color(0); /* background - black */ + cave->color1 = gd_color_get_from_string(params[0]); + cave->color2 = gd_color_get_from_string(params[1]); + cave->color3 = gd_color_get_from_string(params[2]); + cave->color4 = cave->color3; /* amoeba */ + cave->color5 = cave->color1; /* slime */ + } + else if (paramcount == 5) + { + /* bg,color0,1,2,3 */ + cave->colorb = gd_color_get_from_string(params[0]); + cave->color0 = gd_color_get_from_string(params[1]); + cave->color1 = gd_color_get_from_string(params[2]); + cave->color2 = gd_color_get_from_string(params[3]); + cave->color3 = gd_color_get_from_string(params[4]); + cave->color4 = cave->color3; /* amoeba */ + cave->color5 = cave->color1; /* slime */ + } + else if (paramcount == 7) + { + /* bg,color0,1,2,3,amoeba,slime */ + cave->colorb = gd_color_get_from_string(params[0]); + cave->color0 = gd_color_get_from_string(params[1]); + cave->color1 = gd_color_get_from_string(params[2]); + cave->color2 = gd_color_get_from_string(params[3]); + cave->color3 = gd_color_get_from_string(params[4]); + cave->color4 = gd_color_get_from_string(params[5]); /* amoeba */ + cave->color5 = gd_color_get_from_string(params[6]); /* slime */ + } + else + { + Warn("invalid number of color strings: %s", param); + + ok = FALSE; + } + + /* now check and maybe make up some new. */ + if (!ok || + gd_color_is_unknown(cave->colorb) || + gd_color_is_unknown(cave->color0) || + gd_color_is_unknown(cave->color1) || + gd_color_is_unknown(cave->color2) || + gd_color_is_unknown(cave->color3) || + gd_color_is_unknown(cave->color4) || + gd_color_is_unknown(cave->color5)) + { + Warn("created a new C64 color scheme."); + + gd_cave_set_random_c64_colors(cave); /* just create some random */ + } } else { @@ -489,39 +536,39 @@ static boolean cave_process_tags_func(const char *attrib, const char *param, GdC } /* report all remaining tags; called after the above function. */ -static void cave_report_and_copy_unknown_tags_func(char *attrib, char *param, gpointer data) +static void cave_report_and_copy_unknown_tags_func(char *attrib, char *param, void *data) { GdCave *cave = (GdCave *)data; Warn("unknown tag '%s'", attrib); - g_hash_table_insert(cave->tags, g_strdup(attrib), g_strdup(param)); + hashtable_insert(cave->tags, getStringCopy(attrib), getStringCopy(param)); } /* having read all strings belonging to the cave, process it. */ -static void cave_process_tags(GdCave *cave, GHashTable *tags, GList *maplines) +static void cave_process_tags(GdCave *cave, HashTable *tags, List *maplines) { char *value; /* first check cave name, so we can report errors correctly (saying that GdCave xy: error foobar) */ - value = g_hash_table_lookup(tags, "Name"); + value = hashtable_search(tags, "Name"); if (value) cave_process_tags_func("Name", value, cave); /* process lame engine tag first so its settings may be overwritten later */ - value = g_hash_table_lookup(tags, "Engine"); + value = hashtable_search(tags, "Engine"); if (value) { cave_process_tags_func("Engine", value, cave); - g_hash_table_remove(tags, "Engine"); + hashtable_remove(tags, "Engine"); } /* check if this is an intermission, so we can set to cavesize or intermissionsize */ - value = g_hash_table_lookup(tags, "Intermission"); + value = hashtable_search(tags, "Intermission"); if (value) { cave_process_tags_func("Intermission", value, cave); - g_hash_table_remove(tags, "Intermission"); + hashtable_remove(tags, "Intermission"); } if (cave->intermission) @@ -546,39 +593,39 @@ static void cave_process_tags(GdCave *cave, GHashTable *tags, GList *maplines) } /* process size at the beginning... as ratio types depend on this. */ - value = g_hash_table_lookup(tags, "Size"); + value = hashtable_search(tags, "Size"); if (value) { cave_process_tags_func("Size", value, cave); - g_hash_table_remove(tags, "Size"); + hashtable_remove(tags, "Size"); } /* these are read from the hash table, but also have some implications */ /* we do not delete them from the hash table here; as _their values will be processed later_. */ /* here we only set their implicite meanings. */ /* these also set predictability */ - if (g_hash_table_lookup(tags, "SlimePermeability")) + if (hashtable_search(tags, "SlimePermeability")) cave->slime_predictable = FALSE; - if (g_hash_table_lookup(tags, "SlimePermeabilityC64")) + if (hashtable_search(tags, "SlimePermeabilityC64")) cave->slime_predictable = TRUE; /* these set scheduling type. framedelay takes precedence, if there are both; so we check it later. */ - if (g_hash_table_lookup(tags, "CaveDelay")) + if (hashtable_search(tags, "CaveDelay")) { /* only set scheduling type, when it is not the gdash-default. */ /* this allows settings cavescheduling = bd1 in the [game] section, for example. */ /* in that case, this one will not overwrite it. */ if (cave->scheduling == GD_SCHEDULING_MILLISECONDS) cave->scheduling = GD_SCHEDULING_PLCK; - } + } - if (g_hash_table_lookup(tags, "FrameTime")) + if (hashtable_search(tags, "FrameTime")) /* but if the cave has a frametime setting, always switch to milliseconds. */ cave->scheduling = GD_SCHEDULING_MILLISECONDS; /* process all tags */ - g_hash_table_foreach_remove(tags, (GHRFunc) cave_process_tags_func, cave); + hashtable_foreach_remove(tags, (hashtable_remove_fn)cave_process_tags_func, cave); /* and at the end, when read all tags (especially the size= tag) */ /* process map, if any. */ @@ -586,8 +633,8 @@ static void cave_process_tags(GdCave *cave, GHashTable *tags, GList *maplines) /* some old bdcff files use smaller intermissions than the one specified. */ if (maplines) { - int x, y, length = g_list_length(maplines); - GList *iter; + int x, y, length = list_length(maplines); + List *iter; /* create map and fill with initial border, in case that map strings are shorter or somewhat */ cave->map = gd_cave_map_new(cave, GdElement); @@ -644,7 +691,7 @@ boolean gd_caveset_load_from_bdcff(const char *contents) char **lines; int lineno; GdCave *cave; - GList *iter; + List *iter; boolean reading_replay = FALSE; boolean reading_map = FALSE; boolean reading_mapcodes = FALSE; @@ -653,9 +700,9 @@ boolean gd_caveset_load_from_bdcff(const char *contents) boolean reading_bdcff_demo = FALSE; /* assume version to be 0.32, also when the file does not specify it explicitly */ GdString version_read = "0.32"; - GList *mapstrings = NULL; + List *mapstrings = NULL; int linenum; - GHashTable *tags, *replay_tags; + HashTable *tags, *replay_tags; GdObjectLevels levels = GD_OBJECT_LEVEL_ALL; GdCave *default_cave; @@ -665,8 +712,8 @@ boolean gd_caveset_load_from_bdcff(const char *contents) set_intermissionsize_defaults(); gd_create_char_to_element_table(); - tags = g_hash_table_new_full(gd_str_case_hash, gd_str_case_equal, free, free); - replay_tags = g_hash_table_new_full(gd_str_case_hash, gd_str_case_equal, free, free); + tags = create_hashtable(gd_str_case_hash, gd_str_case_equal, free, free); + replay_tags = create_hashtable(gd_str_case_hash, gd_str_case_equal, free, free); /* split into lines */ lines = getSplitStringArray (contents, "\n", 0); @@ -676,7 +723,7 @@ boolean gd_caveset_load_from_bdcff(const char *contents) default_cave = gd_cave_new(); cave = default_cave; - linenum = g_strv_length(lines); + linenum = getStringArrayLength(lines); for (lineno = 0; lineno < linenum; lineno++) { @@ -703,7 +750,7 @@ boolean gd_caveset_load_from_bdcff(const char *contents) if (mapstrings) { Warn("incorrect file format: new [cave] section, but already read some map lines"); - g_list_free(mapstrings); + list_free(mapstrings); mapstrings = NULL; } @@ -712,17 +759,17 @@ boolean gd_caveset_load_from_bdcff(const char *contents) /* ... to be able to create a copy for a new cave. */ cave = gd_cave_new_from_cave(default_cave); - gd_caveset = g_list_append (gd_caveset, cave); + gd_caveset = list_append (gd_caveset, cave); } else if (strcasecmp(line, "[/cave]") == 0) { cave_process_tags(cave, tags, mapstrings); - g_list_free(mapstrings); + list_free(mapstrings); mapstrings = NULL; - if (g_hash_table_size(tags) != 0) - g_hash_table_foreach(tags, (GHFunc) cave_report_and_copy_unknown_tags_func, cave); - g_hash_table_remove_all(tags); + hashtable_foreach(tags, (hashtable_fn)cave_report_and_copy_unknown_tags_func, cave); + hashtable_remove_all(tags); + /* set this to point the pseudo-cave which holds default values */ cave = default_cave; } @@ -732,7 +779,7 @@ boolean gd_caveset_load_from_bdcff(const char *contents) if (mapstrings != NULL) { Warn("incorrect file format: new [map] section, but already read some map lines"); - g_list_free(mapstrings); + list_free(mapstrings); mapstrings = NULL; } } @@ -775,7 +822,7 @@ boolean gd_caveset_load_from_bdcff(const char *contents) replay = gd_replay_new(); replay->saved = TRUE; replay->success = TRUE; /* we think that it is a successful demo */ - cave->replays = g_list_append(cave->replays, replay); + cave->replays = list_append(cave->replays, replay); gd_strcpy(replay->player_name, "???"); /* name not saved */ } else @@ -809,12 +856,12 @@ boolean gd_caveset_load_from_bdcff(const char *contents) #endif /* report any remaining unknown tags */ - g_hash_table_foreach(replay_tags, (GHFunc) replay_report_unknown_tags_func, NULL); - g_hash_table_remove_all(replay_tags); + hashtable_foreach(replay_tags, (hashtable_fn)replay_report_unknown_tags_func, NULL); + hashtable_remove_all(replay_tags); if (replay->movements->len != 0) { - cave->replays = g_list_append(cave->replays, replay); + cave->replays = list_append(cave->replays, replay); } else { @@ -879,14 +926,14 @@ boolean gd_caveset_load_from_bdcff(const char *contents) if (reading_map) { /* just append to the mapstrings list. we will process it later */ - mapstrings = g_list_append(mapstrings, line); + mapstrings = list_append(mapstrings, line); continue; } /* strip leading and trailing spaces AFTER checking if we are reading a map. map lines might begin or end with spaces */ - g_strstrip(line); + stripString(line); if (reading_highscore) { @@ -914,13 +961,13 @@ boolean gd_caveset_load_from_bdcff(const char *contents) if (reading_bdcff_demo) { GdReplay *replay; - GList *iter; + List *iter; /* demo must be in [cave] section. we already showed an error message for this. */ if (cave == default_cave) continue; - iter = g_list_last(cave->replays); + iter = list_last(cave->replays); replay = (GdReplay *)iter->data; replay_store_more_from_bdcff(replay, line); @@ -936,7 +983,7 @@ boolean gd_caveset_load_from_bdcff(const char *contents) if (new_object) { new_object->levels = levels; /* apply levels to new object */ - cave->objects = g_list_append(cave->objects, new_object); + cave->objects = list_append(cave->objects, new_object); } else { @@ -958,7 +1005,7 @@ boolean gd_caveset_load_from_bdcff(const char *contents) /* own tag: not too much thinking :P */ if (reading_replay) { - g_hash_table_insert(replay_tags, g_strdup(attrib), g_strdup(param)); + hashtable_insert(replay_tags, getStringCopy(attrib), getStringCopy(param)); } else if (reading_mapcodes) { @@ -1051,7 +1098,7 @@ boolean gd_caveset_load_from_bdcff(const char *contents) params = getSplitStringArray(param, " ", -1); /* an effect command has two parameters */ - if (g_strv_length(params) == 2) + if (getStringArrayLength(params) == 2) { int i; @@ -1062,7 +1109,7 @@ boolean gd_caveset_load_from_bdcff(const char *contents) strcasecmp(params[0], gd_cave_properties[i].identifier) == 0) { /* found identifier */ - gpointer value = G_STRUCT_MEMBER_P (cave, gd_cave_properties[i].offset); + void *value = STRUCT_MEMBER_P (cave, gd_cave_properties[i].offset); *((GdElement *) value) = gd_get_element_from_string (params[1]); break; @@ -1113,18 +1160,20 @@ boolean gd_caveset_load_from_bdcff(const char *contents) { /* it must be a default setting for all caves. is it a valid identifier? */ /* yes, it is. add to the hash table, which will be copied for all caves. */ - g_hash_table_insert(tags, g_strdup(attrib), g_strdup(param)); + hashtable_insert(tags, getStringCopy(attrib), getStringCopy(param)); } else + { /* unknown setting - report. */ Warn("invalid attribute for [game] '%s'", attrib); + } } else { /* we are reading a [cave] */ /* cave settings are immediately added to cave hash table. */ /* if it is unknown, we have to remember it, and save it again. */ - g_hash_table_insert(tags, g_strdup(attrib), g_strdup(param)); + hashtable_insert(tags, getStringCopy(attrib), getStringCopy(param)); } } @@ -1137,7 +1186,7 @@ boolean gd_caveset_load_from_bdcff(const char *contents) if (mapstrings) { Warn("incorrect file format: end of file, but still have some map lines read"); - g_list_free(mapstrings); + list_free(mapstrings); mapstrings = NULL; } @@ -1150,8 +1199,8 @@ boolean gd_caveset_load_from_bdcff(const char *contents) /* cleanup */ freeStringArray(lines); - g_hash_table_destroy(tags); - g_hash_table_destroy(replay_tags); + hashtable_destroy(tags); + hashtable_destroy(replay_tags); gd_cave_free(default_cave); /* old bdcff files hack. explanation follows. */ @@ -1165,7 +1214,7 @@ boolean gd_caveset_load_from_bdcff(const char *contents) if (strEqual(version_read, "0.32")) { - GList *iter; + List *iter; Warn("No BDCFF version, or 0.32. Using unspecified-intermission-size hack."); @@ -1196,12 +1245,12 @@ boolean gd_caveset_load_from_bdcff(const char *contents) object.element = cave->initial_border; object.fill_element = cave->initial_border; - cave->objects = g_list_prepend(cave->objects, g_memdup(&object, sizeof(object))); + cave->objects = list_prepend(cave->objects, get_memcpy(&object, sizeof(object))); object.x1 = 19; object.y1 = 0; /* 19, as it is also the border */ - cave->objects = g_list_prepend(cave->objects, g_memdup(&object, sizeof(object))); /* another */ + cave->objects = list_prepend(cave->objects, get_memcpy(&object, sizeof(object))); /* another */ } } } @@ -1216,3 +1265,505 @@ boolean gd_caveset_load_from_bdcff(const char *contents) /* if there was some error message - return fail XXX */ return TRUE; } + +/******************************************************************************** + * + * BDCFF saving + * + */ + +#define GD_PTR_ARRAY_MINIMUM_INITIAL_SIZE 64 + +GdPtrArray *gd_ptr_array_sized_new(unsigned int size) +{ + GdPtrArray *array = checked_calloc(sizeof(GdPtrArray)); + + array->data = checked_calloc(size * sizeof(void *)); + array->size_allocated = size; + array->size_initial = size; + array->size = 0; + + return array; +} + +GdPtrArray *gd_ptr_array_new(void) +{ + return gd_ptr_array_sized_new(GD_PTR_ARRAY_MINIMUM_INITIAL_SIZE); +} + +void gd_ptr_array_add(GdPtrArray *array, void *data) +{ + if (array->size == array->size_allocated) + { + array->size_allocated += array->size_initial; + array->data = checked_realloc(array->data, array->size_allocated * sizeof(void *)); + } + + array->data[array->size++] = data; +} + +boolean gd_ptr_array_remove(GdPtrArray *array, void *data) +{ + int i, j; + + for (i = 0; i < array->size; i++) + { + if (array->data[i] == data) + { + checked_free(array->data[i]); + + for (j = i; j < array->size - 1; j++) + array->data[j] = array->data[j + 1]; + + array->size--; + + return TRUE; + } + } + + return FALSE; +} + +void gd_ptr_array_free(GdPtrArray *array, boolean free_data) +{ + int i; + + if (free_data) + { + for (i = 0; i < array->size; i++) + checked_free(array->data[i]); + } + + checked_free(array); +} + +/* ratio: max cave size for GD_TYPE_RATIO. should be set to cave->w*cave->h when calling */ +static void save_properties(GdPtrArray *out, void *str, void *str_def, + const GdStructDescriptor *prop_desc, int ratio) +{ + int i, j; + boolean parameter_written = FALSE, should_write = FALSE; + char *line = NULL; + const char *identifier = NULL; + + for (i = 0; prop_desc[i].identifier != NULL; i++) + { + void *value, *default_value; + + if (prop_desc[i].type == GD_TAB || prop_desc[i].type == GD_LABEL) + /* used only by the gui */ + continue; + + /* these are handled explicitly */ + if (prop_desc[i].flags & GD_DONT_SAVE) + continue; + + /* string data */ + /* write together with identifier, as one string per line. */ + if (prop_desc[i].type == GD_TYPE_STRING) + { + /* treat strings as special - do not even write identifier if no string. */ + char *text = STRUCT_MEMBER_P(str, prop_desc[i].offset); + + if (strlen(text) > 0) + gd_ptr_array_add(out, getStringPrint("%s=%s", prop_desc[i].identifier, text)); + + continue; + } + + /* dynamic string: need to escape newlines */ + if (prop_desc[i].type == GD_TYPE_LONGSTRING) + { + char *string = STRUCT_MEMBER(char *, str, prop_desc[i].offset); + + if (string != NULL && strlen(string) > 0) + { + char *escaped = getEscapedString(string); + + gd_ptr_array_add(out, getStringPrint("%s=%s", prop_desc[i].identifier, escaped)); + + checked_free(escaped); + } + + continue; + } + + /* if identifier differs from the previous, write out the line collected, and start a new one */ + if (identifier == NULL || !strEqual(prop_desc[i].identifier, identifier)) + { + if (should_write) + { + /* write lines only which carry information other than the default settings */ + gd_ptr_array_add(out, getStringCopy(line)); + } + + if (prop_desc[i].type == GD_TYPE_EFFECT) + setStringPrint(&line, "Effect="); + else + setStringPrint(&line, "%s=", prop_desc[i].identifier); + + parameter_written = FALSE; /* no value written yet */ + should_write = FALSE; + + /* remember identifier */ + identifier = prop_desc[i].identifier; + } + + /* if we always save this identifier, remember now */ + if (prop_desc[i].flags & GD_ALWAYS_SAVE) + should_write = TRUE; + + value = STRUCT_MEMBER_P(str, prop_desc[i].offset); + default_value = STRUCT_MEMBER_P(str_def, prop_desc[i].offset); + + for (j = 0; j < prop_desc[i].count; j++) + { + /* separate values by spaces. of course no space required for the first one */ + if (parameter_written) + appendStringPrint(&line, " "); + + parameter_written = TRUE; /* at least one value written, so write space the next time */ + + switch (prop_desc[i].type) + { + case GD_TYPE_BOOLEAN: + appendStringPrint(&line, "%s", ((boolean *) value)[j] ? "true" : "false"); + if (((boolean *) value)[j] != ((boolean *) default_value)[j]) + should_write = TRUE; + break; + + case GD_TYPE_INT: + appendStringPrint(&line, "%d", ((int *) value)[j]); + if (((int *) value)[j] != ((int *) default_value)[j]) + should_write = TRUE; + break; + + case GD_TYPE_RATIO: + appendStringPrint(&line, "%6.5f", ((int *) value)[j] / (double)ratio); + if (((int *) value)[j] != ((int *) default_value)[j]) + should_write = TRUE; + break; + + case GD_TYPE_PROBABILITY: + appendStringPrint(&line, "%6.5f", ((int *) value)[j] / 1E6); /* probabilities are stored as *1E6 */ + + if (((double *) value)[j] != ((double *) default_value)[j]) + should_write = TRUE; + break; + + case GD_TYPE_ELEMENT: + appendStringPrint(&line, "%s", gd_elements[((GdElement *) value)[j]].filename); + if (((GdElement *) value)[j] != ((GdElement *) default_value)[j]) + should_write = TRUE; + break; + + case GD_TYPE_EFFECT: + /* for effects, the property identifier is the effect name. "Effect=" is hardcoded; see above. */ + appendStringPrint(&line, "%s %s", prop_desc[i].identifier, gd_elements[((GdElement *) value)[j]].filename); + if (((GdElement *) value)[j] != ((GdElement *) default_value)[j]) + should_write = TRUE; + break; + + case GD_TYPE_COLOR: + appendStringPrint(&line, "%s", gd_color_get_string(((GdColor *) value)[j])); + should_write = TRUE; + break; + + case GD_TYPE_DIRECTION: + appendStringPrint(&line, "%s", gd_direction_get_filename(((GdDirection *) value)[j])); + if (((GdDirection *) value)[j] != ((GdDirection *) default_value)[j]) + should_write = TRUE; + break; + + case GD_TYPE_SCHEDULING: + appendStringPrint(&line, "%s", gd_scheduling_get_filename(((GdScheduling *) value)[j])); + if (((GdScheduling *) value)[j] != ((GdScheduling *) default_value)[j]) + should_write = TRUE; + break; + + case GD_TAB: + case GD_LABEL: + /* used by the editor ui */ + break; + + case GD_TYPE_STRING: + case GD_TYPE_LONGSTRING: + /* never reached */ + break; + } + } + } + + /* write remaining data */ + if (should_write) + gd_ptr_array_add(out, getStringCopy(line)); + + checked_free(line); +} + +/* remove a line from the list of strings. */ +/* the prefix should be a property; add an equal sign! so properties which have names like + "slime" and "slimeproperties" won't match each other. */ +static void cave_properties_remove(GdPtrArray *out, const char *prefix) +{ + int i; + + if (!strSuffix(prefix, "=")) + Warn("string '%s' should have suffix '='", prefix); + + /* search for strings which match, and set them to NULL. */ + /* also free them. */ + for (i = 0; i < out->size; i++) + { + if (strPrefix(gd_ptr_array_index(out, i), prefix)) + { + checked_free(gd_ptr_array_index(out, i)); + gd_ptr_array_index(out, i) = NULL; + } + } + + /* remove all "null" occurrences */ + while (gd_ptr_array_remove(out, NULL)) + ; +} + +/* output properties of a structure to a file. */ +/* list_foreach func, so "out" is the last parameter! */ +static void caveset_save_cave_func(GdCave *cave, GdPtrArray *out) +{ + GdCave *default_cave; + GdPtrArray *this_out; + char *line = NULL; /* used for various purposes */ + int i; + + gd_ptr_array_add(out, getStringCopy("")); + gd_ptr_array_add(out, getStringCopy("[cave]")); + + /* first add the properties to a local ptr array. */ + /* later, some are deleted (slime permeability, for example) - this is needed because of the inconsistencies of the bdcff. */ + /* finally, remaining will be added to the normal "out" array. */ + this_out = gd_ptr_array_new(); + + default_cave = gd_cave_new(); + save_properties(this_out, cave, default_cave, gd_cave_properties, cave->w * cave->h); + gd_cave_free(default_cave); + + /* properties which are handled explicitly. these cannot be handled easily above, + as they have some special meaning. for example, slime_permeability=x sets permeability to + x, and sets predictable to false. bdcff format is simply inconsistent in these aspects. */ + + /* slime permeability is always set explicitly, as it also sets predictability. */ + if (cave->slime_predictable) + /* if slime is predictable, remove permeab. flag, as that would imply unpredictable slime. */ + cave_properties_remove(this_out, "SlimePermeability="); + else + /* if slime is UNpredictable, remove permeabc64 flag, as that would imply predictable slime. */ + cave_properties_remove(this_out, "SlimePermeabilityC64="); + + /* add tags to output, and free local array */ + for (i = 0; i < this_out->size; i++) + gd_ptr_array_add(out, gd_ptr_array_index(this_out, i)); + + /* do not free data pointers, which were just added to array "out" */ + gd_ptr_array_free(this_out, FALSE); + +#if 0 + /* save unknown tags as they are */ + if (cave->tags) + { + List *hashkeys; + List *iter; + + hashkeys = g_hash_table_get_keys(cave->tags); + for (iter = hashkeys; iter != NULL; iter = iter->next) + { + gchar *key = (gchar *)iter->data; + + gd_ptr_array_add(out, getStringPrint("%s=%s", key, (const char *) g_hash_table_lookup(cave->tags, key))); + } + + list_free(hashkeys); + } +#endif + + /* map */ + if (cave->map) + { + int x, y; + + gd_ptr_array_add(out, getStringCopy("")); + gd_ptr_array_add(out, getStringCopy("[map]")); + + line = checked_calloc(cave->w + 1); + + /* save map */ + for (y = 0; y < cave->h; y++) + { + for (x = 0; x < cave->w; x++) + { + /* check if character is non-zero; the ...save() should have assigned a character to every element */ + if (gd_elements[cave->map[y][x]].character_new == 0) + Warn("gd_elements[cave->map[y][x]].character_new should be non-zero"); + + line[x] = gd_elements[cave->map[y][x]].character_new; + } + + gd_ptr_array_add(out, getStringCopy(line)); + } + + gd_ptr_array_add(out, getStringCopy("[/map]")); + } + + /* save drawing objects */ + if (cave->objects) + { + List *listiter; + + gd_ptr_array_add(out, getStringCopy("")); + gd_ptr_array_add(out, getStringCopy("[objects]")); + + for (listiter = cave->objects; listiter; listiter = list_next(listiter)) + { + GdObject *object = listiter->data; + char *text; + + /* not for all levels? */ + if (object->levels != GD_OBJECT_LEVEL_ALL) + { + int i; + boolean once; /* true if already written one number */ + + setStringPrint(&line, "[Level="); + once = FALSE; + + for (i = 0; i < 5; i++) + { + if (object->levels & gd_levels_mask[i]) + { + if (once) /* if written at least one number so far, we need a comma */ + appendStringPrint(&line, ","); + + appendStringPrint(&line, "%d", i+1); + once = TRUE; + } + } + + appendStringPrint(&line, "]"); + gd_ptr_array_add(out, getStringCopy(line)); + } + + text = gd_object_get_bdcff(object); + gd_ptr_array_add(out, getStringCopy(text)); + checked_free(text); + + if (object->levels != GD_OBJECT_LEVEL_ALL) + gd_ptr_array_add(out, getStringCopy("[/Level]")); + } + + gd_ptr_array_add(out, getStringCopy("[/objects]")); + } + + gd_ptr_array_add(out, getStringCopy("[/cave]")); + + checked_free(line); +} + +/* save cave in bdcff format. */ +/* "out" will be added lines of bdcff description. */ +GdPtrArray *gd_caveset_save_to_bdcff(void) +{ + GdPtrArray *out = gd_ptr_array_sized_new(512); + GdCavesetData *default_caveset; + boolean write_mapcodes = FALSE; + List *iter; + int i; + + /* check if we need an own mapcode table ------ */ + /* copy original characters to character_new fields; new elements will be added to that one */ + for (i = 0; i < O_MAX; i++) + gd_elements[i].character_new = gd_elements[i].character; + + /* also regenerate this table as we use it */ + gd_create_char_to_element_table(); + + /* check all caves */ + for (iter = gd_caveset; iter != NULL; iter = iter->next) + { + GdCave *cave = (GdCave *)iter->data; + + /* if they have a map (random elements+object based maps do not need characters) */ + if (cave->map) + { + int x, y; + + /* check every element of map */ + for(y = 0; y < cave->h; y++) + for (x = 0; x < cave->w; x++) + { + GdElement e = cave->map[y][x]; + + /* if no character assigned */ + if (gd_elements[e].character_new == 0) + { + int j; + + /* we found at least one, so later we have to write the mapcodes */ + write_mapcodes = TRUE; + + /* find a character which is not yet used for any element */ + for (j = 32; j < 128; j++) + { + /* the string contains the characters which should not be used. */ + if (strchr("<>&[]/=\\", j) == NULL && gd_char_to_element[j] == O_UNKNOWN) + break; + } + + /* if no more space... XXX we should rather report to the user */ + if (j == 128) + Warn("variable j should be != 128"); + + gd_elements[e].character_new = j; + + /* we also record to this table, as we use it ^^ a few lines above */ + gd_char_to_element[j] = e; + } + } + } + } + + gd_ptr_array_add(out, getStringCopy("[BDCFF]")); + gd_ptr_array_add(out, getStringPrint("Version=%s", BDCFF_VERSION)); + + /* this flag was set above if we need to write mapcodes */ + if (write_mapcodes) + { + int i; + + gd_ptr_array_add(out, getStringCopy("[mapcodes]")); + gd_ptr_array_add(out, getStringCopy("Length=1")); + + for (i = 0; i < O_MAX; i++) + { + /* if no character assigned by specification BUT (AND) we assigned one */ + if (gd_elements[i].character == 0 && gd_elements[i].character_new != 0) + gd_ptr_array_add(out, getStringPrint("%c=%s", gd_elements[i].character_new, gd_elements[i].filename)); + } + + gd_ptr_array_add(out, getStringCopy("[/mapcodes]")); + } + + gd_ptr_array_add(out, getStringCopy("[game]")); + + default_caveset = gd_caveset_data_new(); + save_properties(out, gd_caveset_data, default_caveset, gd_caveset_properties, 0); + gd_caveset_data_free(default_caveset); + gd_ptr_array_add(out, getStringCopy("Levels=5")); + + list_foreach(gd_caveset, (list_fn)caveset_save_cave_func, out); + + gd_ptr_array_add(out, getStringCopy("[/game]")); + gd_ptr_array_add(out, getStringCopy("[/BDCFF]")); + + /* saved to ptrarray */ + return out; +}