2 * Copyright (c) 2007-2013, Czirkos Zoltan http://code.google.com/p/gdash/
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 #include "misc/logger.hpp"
22 #include "fileops/bdcffload.hpp"
23 #include "fileops/bdcffhelper.hpp"
24 #include "cave/caveset.hpp"
25 #include "cave/helper/cavereplay.hpp"
26 #include "cave/cavestored.hpp"
27 #include "misc/printf.hpp"
28 #include "misc/util.hpp"
29 #include "cave/elementproperties.hpp"
31 /// @todo engine types should be moved somewhere else?
32 #include "fileops/c64import.hpp" /* c64import defines the engine types */
33 #include "cave/object/caveobjectfillrect.hpp" /* bdcff intermission hack - adding a cavefillrect */
35 static bool struct_set_property(Reflective
&str
, const std::string
&attrib
, const std::string
¶m
, int ratio
)
37 PropertyDescription
const *prop_desc
=str
.get_description_array();
40 char **params
=g_strsplit_set(param
.c_str(), " ", -1);
41 int paramcount
=g_strv_length(params
);
42 bool identifier_found
=false;
44 /* check all known tags. do not exit this loop if identifier_found==true...
45 as there are more lines in the array which have the same identifier. */
46 bool was_string
=false;
47 for (unsigned i
=0; prop_desc
[i
].identifier
!=NULL
; i
++)
48 if (gd_str_ascii_caseequal(prop_desc
[i
].identifier
, attrib
)) {
49 /* found the identifier */
50 identifier_found
=true;
51 std::auto_ptr
<GetterBase
> const &prop
= prop_desc
[i
].prop
;
52 if (prop_desc
[i
].type
==GD_TYPE_STRING
) {
53 /* strings are treated different, as occupy the whole length of the line */
54 str
.get
<GdString
>(prop
)=param
;
55 was_string
=true; /* remember this to skip checking the number of parameters at the end of the function */
59 if (prop_desc
[i
].type
==GD_TYPE_LONGSTRING
) {
60 char *compressed
=g_strcompress(param
.c_str());
61 str
.get
<GdString
>(prop
)=compressed
;
63 was_string
=true; /* remember this to skip checking the number of parameters at the end of the function */
67 /* not a string, so use scanf calls */
68 /* try to read as many words, as there are elements in this property (array) */
69 /* ALSO, if no more parameters to process, exit loop */
70 for (unsigned j
=0; j
<prop
->count
&& params
[paramindex
]!=NULL
; j
++) {
73 switch (prop_desc
[i
].type
) {
75 success
=read_from_string(params
[paramindex
], str
.get
<GdBool
>(prop
));
76 /* if we are processing an array, fill other values with these. if there are other values specified, those will be overwritten. */
79 if (prop_desc
[i
].flags
&GD_BDCFF_RATIO_TO_CAVE_SIZE
)
80 success
=read_from_string(params
[paramindex
], str
.get
<GdInt
>(prop
), ratio
); /* saved as double, ratio to cave size */
82 success
=read_from_string(params
[paramindex
], str
.get
<GdInt
>(prop
));
84 case GD_TYPE_INT_LEVELS
:
85 if (prop_desc
[i
].flags
&GD_BDCFF_RATIO_TO_CAVE_SIZE
)
86 success
=read_from_string(params
[paramindex
], str
.get
<GdIntLevels
>(prop
)[j
], ratio
); /* saved as double, ratio to cave size */
88 success
=read_from_string(params
[paramindex
], str
.get
<GdIntLevels
>(prop
)[j
]);
89 if (success
) /* copy to other if array */
90 for (unsigned k
=j
+1; k
<prop
->count
; k
++)
91 str
.get
<GdIntLevels
>(prop
)[k
]=str
.get
<GdIntLevels
>(prop
)[j
];
93 case GD_TYPE_PROBABILITY
:
94 success
=read_from_string(params
[paramindex
], str
.get
<GdProbability
>(prop
));
96 case GD_TYPE_PROBABILITY_LEVELS
:
97 success
=read_from_string(params
[paramindex
], str
.get
<GdProbabilityLevels
>(prop
)[j
]);
98 if (success
) /* copy to other if array */
99 for (unsigned k
=j
+1; k
<prop
->count
; k
++)
100 str
.get
<GdProbabilityLevels
>(prop
)[k
]=str
.get
<GdProbabilityLevels
>(prop
)[j
];
102 case GD_TYPE_ELEMENT
:
103 success
=read_from_string(params
[paramindex
], str
.get
<GdElement
>(prop
));
105 case GD_TYPE_DIRECTION
:
106 success
=read_from_string(params
[paramindex
], str
.get
<GdDirection
>(prop
));
108 case GD_TYPE_SCHEDULING
:
109 success
=read_from_string(params
[paramindex
], str
.get
<GdScheduling
>(prop
));
112 case GD_TYPE_LONGSTRING
:
116 case GD_TYPE_COORDINATE
: /* caves do not have */
117 case GD_TYPE_BOOLEAN_LEVELS
: /* caves do not have */
118 case GD_TAB
: /* ui */
119 case GD_LABEL
: /* ui */
120 g_assert_not_reached();
125 paramindex
++; /* go to next parameter to process */
127 gd_warning(CPrintf("invalid parameter '%s' for attribute %s") % params
[paramindex
] % attrib
);
130 /* if we found the identifier, but still could not process all parameters... */
131 /* of course, not for strings, as the whole line is the string */
132 if (identifier_found
&& !was_string
&& paramindex
<paramcount
)
133 gd_message(CPrintf("excess parameters for attribute '%s': '%s'") % attrib
% params
[paramindex
]);
136 return identifier_found
;
139 static bool replay_process_tag(CaveReplay
&replay
, const std::string
&attrib
, const std::string
¶m
)
141 bool identifier_found
=false;
144 if (gd_str_ascii_caseequal(attrib
, "Movements")) {
145 identifier_found
=true;
146 bool correct
=replay
.load_from_bdcff(param
);
148 gd_warning("Error in replay data");
151 identifier_found
=struct_set_property(replay
, attrib
, param
, 0); /* 0: for ratio types; not used */
153 /* the func returns true if the identifier is to be removed */
154 return identifier_found
;
158 static bool cave_process_tags_func(CaveStored
&cave
, const std::string
&attrib
, const std::string
¶m
)
160 char **params
=g_strsplit_set(param
.c_str(), " ", -1);
161 int paramcount
=g_strv_length(params
);
162 bool identifier_found
=false;
164 /* compatibility with old snapexplosions flag */
165 if (gd_str_ascii_caseequal(attrib
, "SnapExplosions")) {
166 identifier_found
=true;
168 if (read_from_string(param
, b
)) {
169 if (b
) // was "true" -> snapping explosions
170 cave
.snap_element
=O_EXPLODE_1
;
171 else // was "false" -> normal space snapping
172 cave
.snap_element
=O_SPACE
;
174 gd_warning(CPrintf("invalid param for '%s': '%s'") % attrib
% param
);
177 /* compatibility with old bd1scheduling flag */
178 if (gd_str_ascii_caseequal(attrib
, "BD1Scheduling")) {
179 identifier_found
=true;
181 if (read_from_string(param
, b
)) {
183 if (cave
.scheduling
==GD_SCHEDULING_PLCK
)
184 cave
.scheduling
=GD_SCHEDULING_BD1
;
186 gd_warning(CPrintf("invalid param for '%s': '%s'") % attrib
% param
);
189 /* bdcff engine flag */
190 if (gd_str_ascii_caseequal(attrib
, "Engine")) {
191 identifier_found
=true;
193 if (read_from_string(param
, e
))
194 C64Import::cave_set_engine_defaults(cave
, e
);
196 gd_warning(CPrintf("invalid param for '%s': '%s'") % attrib
% param
);
199 /* compatibility with old AmoebaProperties flag */
200 if (gd_str_ascii_caseequal(attrib
, "AmoebaProperties")) {
201 identifier_found
=true;
202 GdElement elem1
=O_STONE
, elem2
=O_DIAMOND
;
203 bool success
=read_from_string(params
[0], elem1
) && read_from_string(params
[1], elem2
);
205 cave
.amoeba_too_big_effect
=elem1
;
206 cave
.amoeba_enclosed_effect
=elem2
;
208 gd_warning(CPrintf("invalid param for '%s': '%s'") % attrib
% param
);
211 /* colors attribute is a mess, have to process explicitly */
212 if (gd_str_ascii_caseequal(attrib
, "Colors")) {
213 /* Colors=[border background] foreground1 foreground2 foreground3 [amoeba slime] */
214 identifier_found
=true;
216 GdColor cb
, c0
, c1
, c2
, c3
, c4
, c5
;
220 cb
=GdColor::from_c64(0); // border - black
221 c0
=GdColor::from_c64(0); // background - black
222 ok
=ok
&&read_from_string(params
[0], c1
);
223 ok
=ok
&&read_from_string(params
[1], c2
);
224 ok
=ok
&&read_from_string(params
[2], c3
);
229 /* bg,color0,1,2,3 */
230 ok
=ok
&&read_from_string(params
[0], cb
);
231 ok
=ok
&&read_from_string(params
[1], c0
);
232 ok
=ok
&&read_from_string(params
[2], c1
);
233 ok
=ok
&&read_from_string(params
[3], c2
);
234 ok
=ok
&&read_from_string(params
[4], c3
);
239 // bg,color0,1,2,3,amoeba,slime
240 ok
=ok
&&read_from_string(params
[0], cb
);
241 ok
=ok
&&read_from_string(params
[1], c0
);
242 ok
=ok
&&read_from_string(params
[2], c1
);
243 ok
=ok
&&read_from_string(params
[3], c2
);
244 ok
=ok
&&read_from_string(params
[4], c3
);
245 ok
=ok
&&read_from_string(params
[5], c4
); // amoeba
246 ok
=ok
&&read_from_string(params
[6], c5
); // slime
260 gd_message(CPrintf("invalid param for '%s': '%s'") % attrib
% param
);
264 /* effects are also handled in an ugly way in bdcff */
265 if (gd_str_ascii_caseequal(attrib
, "Effect")) {
266 identifier_found
=true;
267 /* an effect command has two parameters */
270 PropertyDescription
const *descriptor
=cave
.get_description_array();
273 for (i
=0; descriptor
[i
].identifier
!=NULL
; i
++) {
274 /* we have to search for this effect */
275 if (descriptor
[i
].type
==GD_TYPE_EFFECT
&& gd_str_ascii_caseequal(params
[0], descriptor
[i
].identifier
)) {
276 /* found identifier */
277 success
=read_from_string(params
[1], cave
.get
<GdElement
>(descriptor
[i
].prop
));
279 cave
.get
<GdElement
>(descriptor
[i
].prop
)=nonscanned_pair(cave
.get
<GdElement
>(descriptor
[i
].prop
));
283 /* if we didn't find first element name */
284 if (descriptor
[i
].identifier
==NULL
) {
285 /* for compatibility with tim stridmann's memorydump->bdcff converter... .... ... */
286 if (gd_str_ascii_caseequal(params
[0], "BOUNCING_BOULDER")) {
287 success
=read_from_string(params
[1], cave
.stone_bouncing_effect
);
289 cave
.stone_bouncing_effect
=nonscanned_pair(cave
.stone_bouncing_effect
);
291 else if (gd_str_ascii_caseequal(params
[0], "EXPLOSION3S")) {
292 success
=read_from_string(params
[1], cave
.explosion_3_effect
);
294 cave
.explosion_3_effect
=nonscanned_pair(cave
.explosion_3_effect
);
296 /* falling with one l... */
297 else if (gd_str_ascii_caseequal(params
[0], "STARTING_FALING_DIAMOND")) {
298 success
=read_from_string(params
[1], cave
.diamond_falling_effect
);
300 cave
.diamond_falling_effect
=nonscanned_pair(cave
.diamond_falling_effect
);
303 else if (gd_str_ascii_caseequal(params
[0], "DIRT"))
304 success
=read_from_string(params
[1], cave
.dirt_looks_like
);
305 else if (gd_str_ascii_caseequal(params
[0], "HEXPANDING_WALL") && gd_str_ascii_caseequal(params
[1], "STEEL_HEXPANDING_WALL")) {
306 success
=read_from_string(params
[1], cave
.expanding_wall_looks_like
);
309 /* didn't find at all */
310 gd_warning(CPrintf("invalid effect name '%s'") % params
[0]);
311 success
=true; // to ignore
315 gd_warning(CPrintf("cannot read element name '%s'") % params
[1]);
318 gd_warning(CPrintf("invalid effect specification '%s'") % param
);
321 identifier_found
=struct_set_property(cave
, attrib
, param
, cave
.w
*cave
.h
);
325 /* a ghrfunc should return true if the identifier is to be removed */
326 return identifier_found
;
329 /// process a given cave property (by its name) - and do nothing, if no such property exists.
330 /// this function helps processing some cave tags in advance.
331 /// @param cave The cave to process the tag for.
332 /// @param lines The list of lines to find the attrib in.
333 /// @param name The name of the attribute to find.
334 /// @return true, if the property is found. If found, it is also processed and removed.
335 static bool cave_process_specific_tag(CaveStored
&cave
, BdcffSection
&lines
, const std::string
& name
)
337 BdcffSectionIterator it
=find_if(lines
.begin(), lines
.end(), HasAttrib(name
));
338 bool found
=it
!=lines
.end();
341 AttribParam
ap(*it
); // split into attrib and param
342 cave_process_tags_func(cave
, ap
.attrib
, ap
.param
);
343 } catch (std::exception
&e
) {
344 gd_warning(CPrintf("Cannot parse: %s") % *it
);
346 lines
.erase(it
); // erase after processing
351 /// Process properties for the cave, and set cave parameters according to it.
352 /// Some cave properties must be read in correct order, because bdcff is not a well designed format.
353 /// For example, the name is processed first, to be able to show all error messages with the cave name context.
354 /// Then the engine tag is processed - well, because bdcff sucks.
355 /// Then the size - to make sure ratios are read correctly - bdcff sucks.
356 static void cave_process_all_tags(CaveStored
&cave
, BdcffSection
&lines
)
358 BdcffSectionIterator it
;
360 // first check cave name, so we can report errors correctly (saying that CaveStored xy: error foobar)
361 cave_process_specific_tag(cave
, lines
, "Name");
362 SetLoggerContextForFunction
scf((cave
.name
=="") ? SPrintf("<unnamed cave>") : (SPrintf("Cave '%s'") % cave
.name
));
364 // process lame engine tag first so its settings may be overwritten later. fail.
365 cave_process_specific_tag(cave
, lines
, "Engine");
366 // check if this is an intermission, so we can set to cavesize or intermissionsize - another epic fail
367 cave_process_specific_tag(cave
, lines
, "Intermission");
368 // process size at the beginning... as ratio types depend on this. bdcff design fail.
369 cave_process_specific_tag(cave
, lines
, "Size");
371 // these properties have values, but also make some implications.
372 if (cave_process_specific_tag(cave
, lines
, "SlimePermeability"))
373 cave
.slime_predictable
=false;
374 if (cave_process_specific_tag(cave
, lines
, "SlimePermeabilityC64"))
375 cave
.slime_predictable
=true;
377 // these set scheduling type. framedelay takes precedence, if both exist. so we check it AFTER checking CaveDelay..
378 if (cave_process_specific_tag(cave
, lines
, "CaveDelay")) {
379 // only set scheduling type, when it is not the gdash-default.
380 // this allows setting cavescheduling=bd1 in the [game] section, for example.
381 // in that case, this one will not overwrite it.
382 // bdcff fail, fail, fail.
383 if (cave
.scheduling
==GD_SCHEDULING_MILLISECONDS
)
384 cave
.scheduling
=GD_SCHEDULING_PLCK
;
386 if (cave_process_specific_tag(cave
, lines
, "FrameTime")) {
387 // but if the cave has a frametime setting, always switch to milliseconds.
388 // bdcff says that we should select the better scheduling if we support both.
389 cave
.scheduling
=GD_SCHEDULING_MILLISECONDS
;
392 // process remaining tags - most of them do not require special care.
393 for (BdcffSectionConstIterator it
=lines
.begin(); it
!=lines
.end(); ++it
) {
396 if (!cave_process_tags_func(cave
, ap
.attrib
, ap
.param
)) {
397 gd_message(CPrintf("unknown tag '%s'") % ap
.attrib
);
398 cave
.unknown_tags
+=*it
;
399 cave
.unknown_tags
+='\n';
401 } catch (std::exception
&e
) {
402 gd_warning(CPrintf("Cannot parse line: %s") % *it
);
407 static bool add_highscore(HighScoreTable
&hs
, const std::string
&name
, const std::string
&score
)
409 std::istringstream
is(score
);
422 static BdcffFile
parse_bdcff_sections(const char *file_contents
) throw (std::runtime_error
)
425 std::istringstream
is(file_contents
);
427 Start
, ///< should be nothing here.
428 Bdcff
, ///< inside [bdcff], eg. version=0.5
429 BdcffMapCodes
, ///< inside [bdcff] [mapcodes] - not "standard", but gdash made files like this
430 Game
, ///< inside [game], eg. author=foo
431 GameHighScore
, ///< trivial
432 GameMapCodes
, ///< eg. x=STEELWALL
433 Cave
, ///< a cave, eg. name=Cave A
434 CaveSReplay
, ///< replay section for a cave
435 CaveDemo
, ///< old styled demo (replay), just movements, no random data & the like
436 CaveHighScore
, ///< highscores for a cave
437 CaveObjects
, ///< objects for a cave
438 CaveMap
///< map-encoded cave
444 for (int lineno
=1; !bailout
&& getline(is
, line
); lineno
++) {
445 SetLoggerContextForFunction
scf(SPrintf("Line %d") % lineno
);
448 while ((found_r
=line
.find('\r'))!=std::string::npos
)
449 line
.erase(found_r
, 1); /* remove windows-nightmare \r-s */
451 continue; /* skip empty lines */
453 if (state
!=CaveMap
&& line
[0]==';')
454 continue; /* just skip comments. be aware that map lines may start with a semicolon... */
456 /* STARTING WITH A BRACKET [ IS A SECTION */
458 if (gd_str_ascii_caseequal(line
, "[BDCFF]")) {
460 gd_critical("first section should be [BDCFF]. Bailing out!");
465 else if (gd_str_ascii_caseequal(line
, "[/BDCFF]")) {
468 else if (gd_str_ascii_caseequal(line
, "[game]")) {
470 gd_warning("[game] should be inside [BDCFF]");
473 else if (gd_str_ascii_caseequal(line
, "[/game]")) {
475 gd_warning("[/game] not in [game] section");
477 else if (gd_str_ascii_caseequal(line
, "[mapcodes]")) {
480 state
=GameMapCodes
; break;
482 state
=BdcffMapCodes
; break;
484 gd_warning("[mapcodes] allowed only in [game] section");
489 else if (gd_str_ascii_caseequal(line
, "[/mapcodes]")) {
496 gd_warning("[/mapcodes] not after [mapcodes]");
500 else if (gd_str_ascii_caseequal(line
, "[cave]")) {
502 gd_warning("[cave] allowed only in [game] section");
504 file
.caves
.push_back(BdcffFile::CaveInfo()); /* new empty space for a cave */
506 else if (gd_str_ascii_caseequal(line
, "[/cave]")) {
508 gd_warning("[/cave] tag without starting [cave]");
511 else if (gd_str_ascii_caseequal(line
, "[map]")) {
513 gd_warning("[map] section only allowed inside [cave]");
514 else /* else: do not enter map reading when not in a cave! */
517 else if (gd_str_ascii_caseequal(line
, "[/map]")) {
519 gd_warning("[/map] tag without starting [map]");
522 else if (gd_str_ascii_caseequal(line
, "[highscore]")) {
523 /* can be inside game or cave */
530 gd_critical("[highscore] section only allowed inside [game] and [cave]. This confuses the parser, bailing out!");
534 else if (gd_str_ascii_caseequal(line
, "[/highscore]")) {
535 if (state
==GameHighScore
)
538 if (state
==CaveHighScore
)
541 gd_critical("[/highscore] only allowed after starting [highscore]. This confuses the parser, bailing out!");
545 else if (gd_str_ascii_caseequal(line
, "[objects]")) {
547 gd_warning("[objects] tag only allowed in [cave]");
548 if (file
.caves
.empty()) {
549 gd_warning("[replay] tag does not belong to any cave!");
550 file
.caves
.push_back(BdcffFile::CaveInfo());
554 else if (gd_str_ascii_caseequal(line
, "[/objects]")) {
555 if (state
!=CaveObjects
)
556 gd_warning("[/objects] tag without starting [objects] tag");
559 else if (gd_str_ascii_caseequal(line
, "[demo]")) {
561 gd_warning("[demo] tag only allowed in [cave]");
562 if (file
.caves
.empty()) {
563 gd_warning("[demo] tag does not belong to any cave!");
564 file
.caves
.push_back(BdcffFile::CaveInfo());
567 file
.caves
.back().demo
.push_back(""); /* push an empty string, lines will be added */
569 else if (gd_str_ascii_caseequal(line
, "[/demo]")) {
571 gd_warning("[/demo] tag without starting [demo] tag");
574 else if (gd_str_ascii_caseequal(line
, "[replay]")) {
576 gd_warning("[replay] tag only allowed in [cave]");
577 if (file
.caves
.empty()) {
578 gd_warning("[replay] tag does not belong to any cave!");
579 file
.caves
.push_back(BdcffFile::CaveInfo());
582 file
.caves
.back().replays
.push_back(BdcffSection());
584 else if (gd_str_ascii_caseequal(line
, "[/replay]")) {
585 if (state
!=CaveSReplay
)
586 gd_warning("[/replay] tag without starting [replay] tag");
589 /* GOSH i hate bdcff */
590 else if (gd_str_ascii_prefix(line
, "[level=")) {
591 /* dump this thing in the object list. */
592 if (state
!=CaveObjects
)
593 gd_message("[level] tag only allowed inside [objects] section. Ignored.");
595 file
.caves
.back().objects
.push_back(line
);
597 else if (gd_str_ascii_caseequal(line
, "[/level]")) {
598 /* dump this thing in the object list. */
599 if (state
!=CaveObjects
)
600 gd_message("[/level] tag only allowed inside [objects] section. Ignored.");
602 file
.caves
.back().objects
.push_back(line
);
605 gd_warning(CPrintf("unknown section: \"%s\"") % line
);
610 /* OK, processed the section tags. */
611 /* now copy the line to the correct part of the BdcffFile object. */
613 /* first, check if we are at a map line. no stripping of spaces then! */
614 if (state
==CaveMap
) {
615 file
.caves
.back().map
.push_back(line
);
619 /* if not a map, we may strip spaces. do it here. */
623 case Start
: /* should be nothing here. */
624 gd_critical(CPrintf("nothing allowed outside [BDCFF]: %s") % line
);
628 case Bdcff
: /* inside [bdcff], eg. version=0.5 */
629 file
.bdcff
.push_back(line
);
632 case Game
: /* inside [game], eg. author=foo */
633 file
.caveset_properties
.push_back(line
);
636 case GameHighScore
: /* trivial */
637 file
.highscore
.push_back(line
);
641 case GameMapCodes
: /* eg. x=STEELWALL */
642 file
.mapcodes
.push_back(line
);
645 case Cave
: /* a cave, eg. name=Cave A */
646 file
.caves
.back().properties
.push_back(line
);
649 case CaveSReplay
: /* replay for a cave */
650 file
.caves
.back().replays
.back().push_back(line
);
653 case CaveDemo
: /* old styled demo (replay), just movements, no random data & the like */
654 /* does not contain anything to check for! */
655 file
.caves
.back().demo
.back()+=line
+' ';
658 case CaveHighScore
: /* highscores for a cave */
659 file
.caves
.back().highscore
.push_back(line
);
662 case CaveObjects
: /* objects for a cave */
663 file
.caves
.back().objects
.push_back(line
);
667 /* should already have handled it above */
668 g_assert_not_reached();
674 throw std::runtime_error("Error parsing BDCFF input");
679 CaveSet
load_from_bdcff(const char *contents
) throw(std::runtime_error
)
681 // this may throw, but we do not catch
682 BdcffFile file
= parse_bdcff_sections(contents
);
684 /* this cave will store the default properties, specified in the [game] section for caves. */
685 /* especially the pain-in-the-ass engine tag. */
686 CaveStored default_cave
;
688 CharToElementTable ctet
;
689 std::string version_read
="0.32"; /* assume version to be 0.32, also when the file does not specify it explicitly */
691 /* PROCESS BDCFF PROPERTIES */
692 for (BdcffSectionConstIterator it
=file
.bdcff
.begin(); it
!=file
.bdcff
.end(); ++it
) {
695 if (gd_str_ascii_caseequal(ap
.attrib
, "Version"))
696 version_read
=ap
.param
;
698 if (gd_str_ascii_caseequal(ap
.attrib
, "Engine")) {
699 // invalid but we accept
700 cave_process_tags_func(default_cave
, ap
.attrib
, ap
.param
);
701 gd_message("Invalid BDCFF: Engine= belongs in the [game] section!");
704 gd_warning(CPrintf("Invalid attribute: %s") % ap
.attrib
);
709 /* PROCESS CAVESET PROPERTIES */
710 for (BdcffSectionConstIterator it
=file
.caveset_properties
.begin(); it
!=file
.caveset_properties
.end(); ++it
) {
713 if (gd_str_ascii_caseequal(ap
.attrib
, "Caves"))
714 continue; /* BDCFF files sometimes state how many caves they have; we ignore this field. */
715 if (gd_str_ascii_caseequal(ap
.attrib
, "Levels"))
716 continue; /* BDCFF files sometimes state how many caves they have; we ignore this field. */
718 /* try to interpret property for caveset */
719 if (!struct_set_property(cs
, ap
.attrib
, ap
.param
, 0))
720 /* if not applicable, use it for the default cave */
721 if (!cave_process_tags_func(default_cave
, ap
.attrib
, ap
.param
))
722 /* if not applicable for that, it is invalid. */
723 gd_warning(CPrintf("Invalid attribute: %s") % ap
.attrib
);
727 /* PROCESS CAVESET PROPERTIES */
728 for (BdcffSectionConstIterator it
=file
.caveset_properties
.begin(); it
!=file
.caveset_properties
.end(); ++it
) {
731 if (gd_str_ascii_caseequal(ap
.attrib
, "Caves"))
732 continue; /* BDCFF files sometimes state how many caves they have; we ignore this field. */
733 if (gd_str_ascii_caseequal(ap
.attrib
, "Levels"))
734 continue; /* BDCFF files sometimes state how many caves they have; we ignore this field. */
736 /* try to interpret property for caveset */
737 if (!struct_set_property(cs
, ap
.attrib
, ap
.param
, 0))
738 /* if not applicable, use it for the default cave */
739 if (!struct_set_property(default_cave
, ap
.attrib
, ap
.param
, default_cave
.w
*default_cave
.h
))
740 /* if not applicable for that, it is invalid. */
741 gd_warning(CPrintf("Invalid attribute: %s") % ap
.attrib
);
744 /* PROCESS CAVESET HIGHSCORE */
745 for (BdcffSectionConstIterator it
=file
.highscore
.begin(); it
!=file
.highscore
.end(); ++it
) {
746 /* stored as <score> <space> <name> */
748 AttribParam
ap(*it
, ' ');
749 if (!add_highscore(cs
.highscore
, ap
.param
, ap
.attrib
))
750 gd_message(CPrintf("Invalid highscore: '%s'") % *it
);
751 } catch (std::exception
&e
) {
752 gd_message(CPrintf("Invalid highscore line: '%s'") % *it
);
756 /* PROCESS CAVESET MAPCODES */
757 for (BdcffSectionConstIterator it
=file
.mapcodes
.begin(); it
!=file
.mapcodes
.end(); ++it
) {
760 if (gd_str_ascii_caseequal(ap
.attrib
, "Length")) {
762 gd_critical("Only one-character map codes are currently supported!");
765 if (read_from_string(ap
.param
, elem
))
766 ctet
.set(ap
.attrib
[0], elem
);
768 gd_warning(CPrintf("Unknown element name for map char: '%s'") % ap
.attrib
[0]);
773 /* xxx const iterator cannot be used */
774 for (std::list
<BdcffFile::CaveInfo
>::iterator it
=file
.caves
.begin(); it
!=file
.caves
.end(); ++it
) {
775 CaveStored
*pcave
=new CaveStored(default_cave
);
776 CaveStored
&cave
=*pcave
; /* use it as a reference, too */
778 cs
.caves
.push_back_adopt(pcave
); /* add new cave */
780 cave_process_all_tags(cave
, it
->properties
);
782 /* process cave highscore */
783 for (BdcffSectionConstIterator hit
=it
->highscore
.begin(); hit
!=it
->highscore
.end(); ++hit
) {
784 /* stored as <score> <space> <name> */
786 AttribParam
ap(*hit
, ' ');
787 if (!add_highscore(cave
.highscore
, ap
.param
, ap
.attrib
))
788 gd_message(CPrintf("Invalid highscore: '%s'") % *hit
);
789 } catch (std::exception
&e
) {
790 gd_message(CPrintf("Invalid highscore line: '%s'") % *hit
);
794 /* at the end, when read all tags (especially the size= tag) */
795 /* process map, if any. */
796 /* only report if map read is bigger than size= specified. */
797 /* some old bdcff files use smaller intermissions than the one specified. */
798 if (!it
->map
.empty()) {
799 /* yes, we have a map. */
800 /* create map and fill with initial border, in case that map strings are shorter or somewhat */
801 cave
.map
.set_size(cave
.w
, cave
.h
, cave
.initial_border
);
803 if (int(it
->map
.size())!=cave
.height())
804 gd_warning(CPrintf("map error: cave height=%d (%d visible), map height=%u") % cave
.height() % (cave
.y2
-cave
.y1
+1) % it
->map
.size());
806 BdcffSectionConstIterator mit
; /* to iterate through map lines */
808 for (y
=0, mit
=it
->map
.begin(); y
<cave
.h
&& mit
!=it
->map
.end(); ++mit
, ++y
) {
809 int linelen
=mit
->size();
811 for (int x
=0; x
<std::min(linelen
, signed(cave
.w
)); x
++)
812 cave
.map(x
, y
)=ctet
.get((*mit
)[x
]);
816 /* process cave objects */
818 for (unsigned n
=0; n
<5; ++n
)
820 for (BdcffSectionConstIterator oit
=it
->objects
.begin(); oit
!=it
->objects
.end(); ++oit
) {
821 // process [levels] tags for objects, or process objects.
822 // [level] tags are badly designed in bdcff, as they are
823 // not really "sections", but properties of objects.
824 // yet, they are stored in sections. huge fail.
825 if (*oit
=="[/Level]") {
826 for (unsigned n
=0; n
<5; ++n
)
829 else if (gd_str_ascii_prefix(*oit
, "[Level=")) {
830 std::istringstream
is(oit
->substr(oit
->find('=')+1));
831 for (unsigned n
=0; n
<5; ++n
)
838 gd_warning(CPrintf("Invalid [Levels=xxx] specification"));
839 for (unsigned n
=0; n
<5; ++n
)
847 CaveObject
*newobj
=CaveObject::create_from_bdcff(*oit
);
849 for (unsigned n
=0; n
<5; ++n
)
850 newobj
->seen_on
[n
]=levels
[n
];
851 cave
.objects
.push_back_adopt(newobj
);
854 gd_warning(CPrintf("invalid object specification: %s") % *oit
);
858 /* process replays */
859 for (std::list
<BdcffSection
>::const_iterator rit
=it
->replays
.begin(); rit
!=it
->replays
.end(); ++rit
) {
860 cave
.replays
.push_back(CaveReplay()); /* push an empty replay */
861 CaveReplay
&replay
=cave
.replays
.back(); /* and work on that object */
863 replay
.saved
=true; /* set "saved" flag, so this replay will be written when the caveset is saved again */
864 /* and process its contents */
865 for (BdcffSectionConstIterator lines_it
=rit
->begin(); lines_it
!=rit
->end(); ++lines_it
) {
866 if (lines_it
->find('=')!=std::string::npos
) {
867 AttribParam
ap(*lines_it
);
868 replay_process_tag(replay
, ap
.attrib
, ap
.param
);
870 replay_process_tag(replay
, "Movements", *lines_it
); /* try to interpret it as a bdcff replay */
875 for (BdcffSectionConstIterator dit
=it
->demo
.begin(); dit
!=it
->demo
.end(); ++dit
) {
876 cave
.replays
.push_back(CaveReplay()); /* push an empty replay */
877 CaveReplay
&replay
=cave
.replays
.back(); /* and work on that object */
879 replay
.saved
=true; /* set "saved" flag, so this replay will be written when the caveset is saved again */
880 replay
.player_name
="???";
881 replay_process_tag(replay
, "Movements", *dit
); /* try to interpret it as a bdcff replay */
885 /* old bdcff files hack. explanation follows. */
886 /* there were 40x22 caves in c64 bd, intermissions were also 40x22, but the visible */
887 /* part was the upper left corner, 20x12. 40x22 caves are needed, as 20x12 caves would */
888 /* look different (random cave elements needs the correct size.) */
889 /* also, in older bdcff files, there is no size= tag. caves default to 40x22 and 20x12. */
890 /* even the explicit drawrect and other drawing instructions, which did set up intermissions */
891 /* to be 20x12, are deleted. very very bad decision. */
892 /* here we try to detect and correct this. */
893 if (version_read
=="0.32") {
894 gd_message("No BDCFF version, or 0.32. Using unspecified-intermission-size hack.");
896 for (unsigned int i
=0; i
<cs
.caves
.size(); ++i
) {
897 CaveStored
&cav
=cs
.cave(i
);
899 /* only applies to intermissions */
900 /* not applied to mapped caves, as maps are filled with initial border, if the map read is smaller */
901 if (cav
.intermission
&& cav
.map
.empty()) {
902 /* we do not set the cave to 20x12, rather to 40x22 with 20x12 visible. */
906 cav
.x2
=19; cav
.y2
=11;
908 /* and cover the invisible area */
909 cav
.objects
.push_back_adopt(new CaveFillRect(Coordinate(0, 11), Coordinate(39, 21), cav
.initial_border
, cav
.initial_border
));
910 cav
.objects
.push_back_adopt(new CaveFillRect(Coordinate(19, 0), Coordinate(39, 21), cav
.initial_border
, cav
.initial_border
));
915 if (version_read
!=BDCFF_VERSION
)
916 gd_warning(CPrintf("BDCFF version %s, loaded caveset may have errors.") % version_read
);
918 // check for replays which are problematic
919 for (unsigned int i
=0; i
<cs
.caves
.size(); ++i
)
920 gd_cave_check_replays(cs
.cave(i
), true, false, false);
922 // return the created caveset.