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.
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.
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
;
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
)
68 if (prop_desc
[i
].flags
& GD_DONT_SAVE
)
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
));
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
);
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
));
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
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
)
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
))
117 if (prop_desc
[i
].flags
& GD_BDCFF_RATIO_TO_CAVE_SIZE
)
118 line
<< (str
.get
<GdInt
>(prop
) / (double)ratio
); /* save as ratio! */
120 line
<< str
.get
<GdInt
>(prop
); /* save as normal int */
121 if (str
.get
<GdInt
>(prop
) != str_def
.get
<GdInt
>(prop
))
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! */
129 line
<< str
.get
<GdIntLevels
>(prop
)[j
]; /* save as normal int */
130 if (str
.get
<GdIntLevels
>(prop
)[j
] != str_def
.get
<GdIntLevels
>(prop
)[j
])
134 case GD_TYPE_PROBABILITY
:
135 line
<< str
.get
<GdProbability
>(prop
);
136 if (str
.get
<GdProbability
>(prop
) != str_def
.get
<GdProbability
>(prop
))
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
])
146 case GD_TYPE_ELEMENT
:
147 line
<< str
.get
<GdElement
>(prop
);
148 if (str
.get
<GdElement
>(prop
) != str_def
.get
<GdElement
>(prop
))
152 line
<< str
.get
<GdColor
>(prop
);
155 case GD_TYPE_DIRECTION
:
156 line
<< str
.get
<GdDirection
>(prop
);
157 if (str
.get
<GdDirection
>(prop
) != str_def
.get
<GdDirection
>(prop
))
160 case GD_TYPE_SCHEDULING
:
161 line
<< str
.get
<GdScheduling
>(prop
);
162 if (str
.get
<GdScheduling
>(prop
) != str_def
.get
<GdScheduling
>(prop
))
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();
176 /* write remaining data */
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
) {
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());
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");
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
);
235 if (!cave
.map
.empty()) {
236 std::string
line(cave
.w
, ' '); // creates a string of length w filled with ' '
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
261 line
+= char('1' + i
); // level number, ascii character 1, 2, 3, 4 or 5
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]");
276 for (std::list
<CaveReplay
>::iterator r_it
= cave
.replays
.begin(); r_it
!= cave
.replays
.end(); ++r_it
)
278 out
.replays
.push_back(save_replay_func(*r_it
));
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
) {
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
) {
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
);
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);
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
);
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
364 for (std::list
<BdcffFile::CaveInfo
>::iterator it
= outfile
.caves
.begin(); it
!= outfile
.caves
.end(); ++it
) {
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]");