* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#include <glib.h>
-#include <glib/gi18n.h>
-
#include <errno.h>
#include "main_bd.h"
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 */
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;
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...
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;
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;
for (j = 0; j < prop_desc[i].count && params[paramindex] != NULL; j++)
{
boolean success = FALSE;
- gdouble res;
+ double res;
switch (prop_desc[i].type)
{
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 */
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++)
}
/* 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);
}
}
/* ... */
-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.
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)
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
{
}
/* 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)
}
/* 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. */
/* 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);
char **lines;
int lineno;
GdCave *cave;
- GList *iter;
+ List *iter;
boolean reading_replay = FALSE;
boolean reading_map = FALSE;
boolean reading_mapcodes = FALSE;
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;
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);
default_cave = gd_cave_new();
cave = default_cave;
- linenum = g_strv_length(lines);
+ linenum = getStringArrayLength(lines);
for (lineno = 0; lineno < linenum; lineno++)
{
if (mapstrings)
{
Warn("incorrect file format: new [cave] section, but already read some map lines");
- g_list_free(mapstrings);
+ list_free(mapstrings);
mapstrings = NULL;
}
/* ... 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;
}
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;
}
}
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
#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
{
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)
{
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);
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
{
/* 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)
{
params = getSplitStringArray(param, " ", -1);
/* an effect command has two parameters */
- if (g_strv_length(params) == 2)
+ if (getStringArrayLength(params) == 2)
{
int i;
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;
{
/* 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));
}
}
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;
}
/* 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. */
if (strEqual(version_read, "0.32"))
{
- GList *iter;
+ List *iter;
Warn("No BDCFF version, or 0.32. Using unspecified-intermission-size hack.");
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 */
}
}
}
/* 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;
+}