20130427
[gdash.git] / src / fileops / bdcffsave.cpp
blobbf46a4eb7ef2336bf5a92aee0d74cf06ae1523e7
1 /*
2 * Copyright (c) 2007-2013, Czirkos Zoltan http://code.google.com/p/gdash/
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
19 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
20 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 #include "config.h"
26 #include <cstring>
28 #include "fileops/bdcffsave.hpp"
29 #include "cave/cavestored.hpp"
30 #include "cave/caveset.hpp"
31 #include "fileops/bdcffhelper.hpp"
32 #include "cave/elementproperties.hpp"
33 #include "misc/autogfreeptr.hpp"
36 /// @file fileops/bdcffsave.cpp
37 /// BDCFF save functions
40 /// write highscore to a bdcff file
41 static void write_highscore_func(std::list<std::string> &out, HighScoreTable const &scores) {
42 for (unsigned int i = 0; i < scores.size(); i++)
43 out.push_back(BdcffFormat() << scores[i].score << scores[i].name);
47 /// Save properties of a reflective object in bdcff format.
48 /// Used to save caves, cavesets, replays.
49 /// @param out The list of output strings to append to.
50 /// @param str The reflective object.
51 /// @param str_def Another reflective object, which is of the same type. Default values are taken from that,
52 /// i.e. if a property in str has the same value as in str_def, it is not saved.
53 /// @param ratio The cave size, for ratio types. Set to cave->w*cave->h when calling.
54 /// @todo rename
55 void save_properties(std::list<std::string> &out, Reflective &str, Reflective &str_def, int ratio, PropertyDescription const *prop_desc) {
56 bool should_write = false;
57 const char *identifier = NULL;
58 BdcffFormat line;
60 /* for all properties */
61 for (unsigned i = 0; prop_desc[i].identifier != NULL; i++) {
62 std::auto_ptr<GetterBase> const &prop = prop_desc[i].prop;
64 // used only by the gui, nothing to do
65 if (prop_desc[i].type == GD_TAB || prop_desc[i].type == GD_LABEL)
66 continue;
67 // skip these
68 if (prop_desc[i].flags & GD_DONT_SAVE)
69 continue;
70 // if it is a string, write as one line. do not even write identifier if no string, as default is empty.
71 if (prop_desc[i].type == GD_TYPE_STRING) {
72 if (str.get<GdString>(prop) != "")
73 out.push_back(BdcffFormat(prop_desc[i].identifier) << str.get<GdString>(prop));
74 continue;
76 // long string - also as one line. escape newlines.
77 if (prop_desc[i].type == GD_TYPE_LONGSTRING) {
78 if (str.get<GdString>(prop) != "") {
79 AutoGFreePtr<char> escaped(g_strescape(str.get<GdString>(prop).c_str(), NULL));
80 out.push_back(BdcffFormat(prop_desc[i].identifier) << escaped);
82 continue;
84 // effects are also stored in a different fashion.
85 if (prop_desc[i].type == GD_TYPE_EFFECT) {
86 if (str.get<GdElement>(prop) != str_def.get<GdElement>(prop))
87 out.push_back(BdcffFormat("Effect") << prop_desc[i].identifier << str.get<GdElement>(prop));
88 continue;
91 // and now process tags which have to be treated normally.
93 // if identifier differs from the previous, write out the line collected, and start a new one
94 if (!identifier || strcmp(prop_desc[i].identifier, identifier) != 0) {
95 // write lines only which carry information other than the default settings
96 if (should_write)
97 out.push_back(line);
99 line.start_new(prop_desc[i].identifier);
100 should_write = false;
102 // remember identifier
103 identifier = prop_desc[i].identifier;
106 // if we always save this identifier, remember now
107 if (prop_desc[i].flags & GD_ALWAYS_SAVE)
108 should_write = true;
110 switch (prop_desc[i].type) {
111 case GD_TYPE_BOOLEAN:
112 line << str.get<GdBool>(prop);
113 if (str.get<GdBool>(prop) != str_def.get<GdBool>(prop))
114 should_write = true;
115 break;
116 case GD_TYPE_INT:
117 if (prop_desc[i].flags & GD_BDCFF_RATIO_TO_CAVE_SIZE)
118 line << (str.get<GdInt>(prop) / (double)ratio); /* save as ratio! */
119 else
120 line << str.get<GdInt>(prop); /* save as normal int */
121 if (str.get<GdInt>(prop) != str_def.get<GdInt>(prop))
122 should_write = true;
123 break;
124 case GD_TYPE_INT_LEVELS:
125 for (unsigned j = 0; j < prop->count; j++) {
126 if (prop_desc[i].flags & GD_BDCFF_RATIO_TO_CAVE_SIZE)
127 line << (str.get<GdIntLevels>(prop)[j] / (double)ratio); /* save as ratio! */
128 else
129 line << str.get<GdIntLevels>(prop)[j]; /* save as normal int */
130 if (str.get<GdIntLevels>(prop)[j] != str_def.get<GdIntLevels>(prop)[j])
131 should_write = true;
133 break;
134 case GD_TYPE_PROBABILITY:
135 line << str.get<GdProbability>(prop);
136 if (str.get<GdProbability>(prop) != str_def.get<GdProbability>(prop))
137 should_write = true;
138 break;
139 case GD_TYPE_PROBABILITY_LEVELS:
140 for (unsigned j = 0; j < prop->count; j++) {
141 line << str.get<GdProbabilityLevels>(prop)[j];
142 if (str.get<GdProbabilityLevels>(prop)[j] != str_def.get<GdProbabilityLevels>(prop)[j])
143 should_write = true;
145 break;
146 case GD_TYPE_ELEMENT:
147 line << str.get<GdElement>(prop);
148 if (str.get<GdElement>(prop) != str_def.get<GdElement>(prop))
149 should_write = true;
150 break;
151 case GD_TYPE_COLOR:
152 line << str.get<GdColor>(prop);
153 should_write = true;
154 break;
155 case GD_TYPE_DIRECTION:
156 line << str.get<GdDirection>(prop);
157 if (str.get<GdDirection>(prop) != str_def.get<GdDirection>(prop))
158 should_write = true;
159 break;
160 case GD_TYPE_SCHEDULING:
161 line << str.get<GdScheduling>(prop);
162 if (str.get<GdScheduling>(prop) != str_def.get<GdScheduling>(prop))
163 should_write = true;
164 break;
165 case GD_TAB:
166 case GD_LABEL:
167 case GD_TYPE_EFFECT: /* handled above */
168 case GD_TYPE_STRING: /* handled above */
169 case GD_TYPE_LONGSTRING: /* handled above */
170 case GD_TYPE_COORDINATE: /* currently not needed */
171 case GD_TYPE_BOOLEAN_LEVELS: /* currently not needed */
172 g_assert_not_reached();
173 break;
176 /* write remaining data */
177 if (should_write)
178 out.push_back(line);
182 static void save_own_properties(std::list<std::string> &out, Reflective &str, Reflective &str_def, int ratio) {
183 save_properties(out, str, str_def, ratio, str.get_description_array());
187 /* remove a line from the list of strings. */
188 /* the prefix should be a property; add an equal sign! so properties which have names like
189 "slime" and "slimeproperties" won't match each other. */
190 static void cave_properties_remove(std::list<std::string> &out, const char *attrib) {
191 out.remove_if(HasAttrib(attrib));
195 static BdcffSection save_replay_func(CaveReplay &replay) {
196 BdcffSection out;
197 CaveReplay default_values; // an empty replay to store default values
198 save_own_properties(out, replay, default_values, 0); // 0 is for ratio, here it is not used
199 out.push_back(BdcffFormat("Movements") << replay.movements_to_bdcff());
201 return out;
205 /// Output properties of a CaveStored to a BdcffFile::CaveInfo structure.
206 /// Saves everything; properties, map, objects.
207 static BdcffFile::CaveInfo caveset_save_cave_func(CaveStored &cave) {
208 BdcffFile::CaveInfo out;
210 write_highscore_func(out.highscore, cave.highscore);
212 // first add the properties to the list.
213 // later, some are deleted (slime permeability, for example) - this is needed because of the inconsistencies of the bdcff.
214 CaveStored default_values;
215 save_own_properties(out.properties, cave, default_values, cave.w * cave.h);
217 // here come properties which are handled explicitly. these cannot be handled easily above,
218 // as they have some special meaning. for example, slime_permeability=x sets permeability to
219 // x, and sets predictable to false. bdcff format is simply inconsistent in these aspects.
221 // slime permeability is always set explicitly, as it also sets predictability.
222 // both have the ALWAYS_SAVE flags, so now they are in the array regardless of their values.
223 if (cave.slime_predictable)
224 // if slime is predictable, remove permeab. flag, as that would imply unpredictable slime.
225 cave_properties_remove(out.properties, "SlimePermeability");
226 else
227 // if slime is UNpredictable, remove permeabc64 flag, as that would imply predictable slime.
228 cave_properties_remove(out.properties, "SlimePermeabilityC64");
230 // save unknown tags as they are. somewhat hackish - writes a string with multi-lines.
231 if (cave.unknown_tags != "")
232 out.properties.push_back(cave.unknown_tags);
234 // is cave has a map
235 if (!cave.map.empty()) {
236 std::string line(cave.w, ' '); // creates a string of length w filled with ' '
237 // save map
238 for (int y = 0; y < cave.h; ++y) {
239 for (int x = 0; x < cave.w; ++x) {
240 // check if character is non-zero; the ...save() should have assigned a character to every element
241 // the gd_element_properties[...].character_new is created by the caller.
242 g_assert(gd_element_properties[cave.map(x, y)].character_new != 0);
243 line[x] = gd_element_properties[cave.map(x, y)].character_new;
245 out.map.push_back(line);
249 // save drawing objects
250 for (CaveObjectStore::const_iterator it = cave.objects.begin(); it != cave.objects.end(); ++it) {
251 CaveObject const *object = *it; /* eh */
253 // not for all levels?
254 if (!object->is_seen_on_all()) {
255 std::string line = "[Level=";
256 bool once = false; // will be true if already written one number
257 for (int i = 0; i < 5; i++) {
258 if (object->seen_on[i]) {
259 if (once) // if written at least one number so far, we need a comma
260 line += ',';
261 line += char('1' + i); // level number, ascii character 1, 2, 3, 4 or 5
262 once = true;
265 line += ']';
266 out.objects.push_back(line);
268 out.objects.push_back(object->get_bdcff());
269 // again, not for all? then save closing tag, too
270 if (!object->is_seen_on_all())
271 out.objects.push_back("[/Level]");
275 // save replays
276 for (std::list<CaveReplay>::iterator r_it = cave.replays.begin(); r_it != cave.replays.end(); ++r_it)
277 if (r_it->saved)
278 out.replays.push_back(save_replay_func(*r_it));
280 return out;
284 /// Add a group of properties to a bdcff file.
285 /// A new line is started, then the group name is written like [group].
286 /// Then the properties, and the closing [/group].
287 /// Only creates group, if it won't be empty.
288 /// @param name The name of the group in te bdcff file.
289 /// @param in The list of properties to save to the file. Will be cleared after this.
290 /// @param out The list of strings, which will be the bdcff file. Writes to this list.
291 static void add_group(std::string name, std::list<std::string> &in, std::list<std::string> &out) {
292 if (!in.empty()) {
293 out.push_back("");
294 out.push_back("[" + name + "]");
295 // Move all lines from the list 'in' to the end of list 'out'
296 out.splice(out.end(), in);
297 out.push_back("[/" + name + "]");
301 /// Save caveset in BDCFF format to a list of strings.
302 void save_to_bdcff(CaveSet &caveset, std::list<std::string> &out) {
304 BdcffFile outfile;
306 outfile.bdcff.push_back(BdcffFormat("Version") << BDCFF_VERSION);
308 /* check if we need an own mapcode table ------ */
309 /* copy original characters to character_new fields; new elements will be added to that one */
310 /* check all caves */
311 bool write_mapcodes = false;
312 CharToElementTable ctet; // create a new table
313 for (unsigned int i = 0; i < O_MAX; i++)
314 gd_element_properties[i].character_new = gd_element_properties[i].character;
315 for (unsigned int i = 0; i < caveset.caves.size(); i++) {
316 CaveStored &cave = caveset.cave(i);
318 // if they have a map (random elements+object based maps do not need characters)
319 if (!cave.map.empty()) {
320 // check every element of map
321 for (int y = 0; y < cave.h; ++y)
322 for (int x = 0; x < cave.w; ++x) {
323 GdElementEnum e = cave.map(x, y);
324 if (gd_element_properties[e].character_new == 0) {
325 write_mapcodes = true;
326 gd_element_properties[e].character_new = ctet.find_place_for(e);
331 // this flag was set above if we need to write mapcodes
332 if (write_mapcodes) {
333 outfile.mapcodes.push_back(BdcffFormat("Length") << 1);
334 for (unsigned int i = 0; i < O_MAX; i++) {
335 // if no character assigned by specification BUT (AND) we assigned one
336 if (gd_element_properties[i].character == 0 && gd_element_properties[i].character_new != 0)
337 // write something like ".=DIRT".
338 outfile.mapcodes.push_back(BdcffFormat(std::string(1, gd_element_properties[i].character_new)) << gd_element_properties[i].filename);
342 // caveset data
343 write_highscore_func(outfile.highscore, caveset.highscore);
344 CaveSet default_caveset; // temporary object holds default values
345 save_own_properties(outfile.caveset_properties, caveset, default_caveset, 0);
346 outfile.caveset_properties.push_back(BdcffFormat("Levels") << 5);
348 // caves data
349 for (unsigned int i = 0; i < caveset.caves.size(); ++i)
350 outfile.caves.push_back(caveset_save_cave_func(caveset.cave(i)));
352 // now convert it to an output file.
353 // move all strings created to the output list of strings in sections.
354 out.push_back("[BDCFF]");
355 out.splice(out.end(), outfile.bdcff); // bdcff version string
356 add_group("mapcodes", outfile.mapcodes, out);
358 out.push_back("");
359 out.push_back("[game]");
360 out.splice(out.end(), outfile.caveset_properties); // game (caveset) properties)
361 add_group("highscore", outfile.highscore, out); // game highscores
363 // data of caves
364 for (std::list<BdcffFile::CaveInfo>::iterator it = outfile.caves.begin(); it != outfile.caves.end(); ++it) {
365 out.push_back("");
366 out.push_back("[cave]");
367 out.splice(out.end(), it->properties); // cave properties
368 add_group("map", it->map, out);
369 add_group("objects", it->objects, out);
370 add_group("highscore", it->highscore, out);
371 // each replay has its own group
372 for (std::list<BdcffSection>::iterator rit = it->replays.begin(); rit != it->replays.end(); ++rit)
373 add_group("replay", *rit, out);
374 out.push_back("[/cave]");
377 out.push_back("[/game]");
378 out.push_back("[/BDCFF]");