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.
20 #include <glib/gi18n.h>
27 #include "cave/cavetypes.hpp"
28 #include "cave/elementproperties.hpp"
29 #include "misc/printf.hpp"
30 #include "misc/logger.hpp"
31 #include "cave/helper/namevaluepair.hpp"
34 /* TRANSLATORS: None here means "no direction to move"; when there is no gravity while stirring the pot. */
35 static const char* direction_name
[]={ N_("None"), N_("Up"), N_("Up+right"), N_("Right"), N_("Down+right"), N_("Down"), N_("Down+left"), N_("Left"), N_("Up+left") };
36 static const char* direction_filename
[]={ "none", "up", "upright", "right", "downright", "down", "downleft", "left", "upleft" };
38 static const char* scheduling_name
[]={ N_("Milliseconds"), "BD1", "BD2", "Construction Kit", "Crazy Dream 7", "Atari BD1", "Atari BD2/Construction Kit" };
39 static const char* scheduling_filename
[]={ "ms", "bd1", "bd2", "plck", "crdr7", "bd1atari", "bd2ckatari" };
41 /* used for bdcff engine flag. */
42 static const char *engines_name
[]={"BD1", "BD2", "PLCK", "1stB", "Crazy Dream", "Crazy Light"};
43 static const char *engines_filename
[]={"BD1", "BD2", "PLCK", "1stB", "CrDr", "CrLi"};
45 /// Write a coordinate to an output stream.
46 /// Delimits the x and y components with space.
47 std::ostream
& operator<<(std::ostream
&os
, Coordinate
const &p
) {
48 return (os
<< p
.x
<< ' ' << p
.y
);
51 /// Read a coordinate from an input stream.
52 /// Reads x and y coordinates; if both could be read, set p.
53 std::istream
& operator>>(std::istream
&is
, Coordinate
&p
) {
56 /* only modify p if read both parameters correctly */
64 /// Add a vector to a coordinate.
65 /// @param p The vector to add.
66 Coordinate
& Coordinate::operator+=(Coordinate
const &p
) {
72 /// Add two coordinates (vectors).
73 Coordinate
Coordinate::operator+(Coordinate
const& rhs
) const {
74 return Coordinate(x
+rhs
.x
, y
+rhs
.y
);
77 /// Compare two coordinates for equality.
78 /// @return True, if they are the same.
79 bool Coordinate::operator==(Coordinate
const& rhs
) const {
80 return x
==rhs
.x
&& y
==rhs
.y
;
83 /// Get on-screen description of a coordinate.
84 std::string
visible_name(Coordinate
const &p
) {
85 std::ostringstream os
;
86 os
<<'('<<p
.x
<<','<<p
.y
<<')';
90 /// Drag the corners of the rectangle set by p1 and p2.
91 /// This is used for many objects in the editor. When clicking and dragging
92 /// one of the corners of a square, its size can be changed.
93 /// Whereas by clicking and dragging the edges, the whole square is moved.
94 /// We can detect clicking on the edges by comparing the coordinate clicked
95 /// with p1 and p2; all four possibilities have to be taken into account.
96 /// @param p1 One corner of the rectangle.
97 /// @param p2 The other corner of the rectangle.
98 /// @param current The coordinate clicked.
99 /// @param displacement The movement vector.
100 void Coordinate::drag_rectangle(Coordinate
&p1
, Coordinate
&p2
, Coordinate current
, Coordinate displacement
) {
101 /* dragging objects which are box-shaped */
102 if (current
.x
==p1
.x
&& current
.y
==p1
.y
) { /* try to drag (x1;y1) corner. */
103 p1
.x
+=displacement
.x
;
104 p1
.y
+=displacement
.y
;
106 else if (current
.x
==p2
.x
&& current
.y
==p1
.y
) { /* try to drag (x2;y1) corner. */
107 p2
.x
+=displacement
.x
;
108 p1
.y
+=displacement
.y
;
110 else if (current
.x
==p1
.x
&& current
.y
==p2
.y
) { /* try to drag (x1;y2) corner. */
111 p1
.x
+=displacement
.x
;
112 p2
.y
+=displacement
.y
;
114 else if (current
.x
==p2
.x
&& current
.y
==p2
.y
) { /* try to drag (x2;y2) corner. */
115 p2
.x
+=displacement
.x
;
116 p2
.y
+=displacement
.y
;
119 /* drag the whole thing */
120 p1
.x
+=displacement
.x
;
121 p1
.y
+=displacement
.y
;
122 p2
.x
+=displacement
.x
;
123 p2
.y
+=displacement
.y
;
128 /// get on-screen visible "name" of an int
129 std::string
visible_name(GdInt
const &i
) {
130 std::ostringstream os
;
135 /// get on-screen visible "name" of a probability
136 /// @todo change 1000000.0 to a constans EVERYWHERE in the code
137 /// @todo check everywhere when reading and writing if a +0.5 is needed, and explain why
138 std::string
visible_name(GdProbability
const &p
) {
139 std::ostringstream os
;
140 os
<<std::fixed
<<std::setprecision(2)<<p
*100.0/1000000.0<<'%';
144 /// get on-screen visible "name" of an int
145 const char *visible_name(GdBool
const &b
) {
146 return b
?N_("Yes"):N_("No");
149 /// get on-screen visible name of a direction
150 const char *visible_name(GdDirectionEnum dir
) {
151 g_assert(dir
>=0 && unsigned(dir
)<G_N_ELEMENTS(direction_name
));
152 return direction_name
[dir
];
155 /// get on-screen visible name of a scheduling
156 const char *visible_name(GdSchedulingEnum sched
) {
157 g_assert(sched
>=0 && unsigned(sched
)<G_N_ELEMENTS(scheduling_name
));
158 return scheduling_name
[sched
];
161 /// get on-screen visible name of a scheduling
162 const char *visible_name(GdEngineEnum eng
) {
163 g_assert(eng
>=0 && unsigned(eng
)<G_N_ELEMENTS(engines_name
));
164 return engines_name
[eng
];
167 /// get on-screen visible name of a scheduling
168 const char *visible_name(GdElementEnum elem
) {
169 return gd_element_properties
[elem
].visiblename
;
173 /// Creates a CharToElementTable for conversion.
174 /// Adds all fixed elements, read from the gd_element_properties array.
175 CharToElementTable::CharToElementTable() {
176 for (unsigned i
=0; i
<ArraySize
; i
++)
179 /* then set fixed characters */
180 for (unsigned i
=0; i
<O_MAX
; i
++) {
181 int c
=gd_element_properties
[i
].character
;
184 /* check if already used for element */
185 g_assert(table
[c
]==O_UNKNOWN
);
186 table
[c
]=GdElementEnum(i
);
192 * @brief Return the GdElementEnum assigned to the character.
194 * @param i The character.
195 * @return The element, or O_UNKNOWN if character is invalid.
197 GdElementEnum
CharToElementTable::get(unsigned i
) const {
198 if (i
>=ArraySize
|| table
[i
]==O_UNKNOWN
) {
199 gd_warning(CPrintf("Invalid character representing element: %c") % char(i
));
206 * @brief Find an empty character to store the element in a map.
207 * If finds a suitable character, also remembers.
209 * @param e The element to find place for.
210 * @return The (new) character for the element.
212 unsigned CharToElementTable::find_place_for(GdElementEnum e
) {
213 const char *not_allowed
="<>&[]/=\\";
215 // first check if it is already in the array.
216 for (unsigned i
=32; i
<ArraySize
; ++i
)
221 for (i
=32; i
<ArraySize
; ++i
)
222 // if found a good empty char, break
223 if (table
[i
]==O_UNKNOWN
&& strchr(not_allowed
, i
)==NULL
)
226 throw std::runtime_error("no more characters");
232 * @brief Set an element assigned to a character.
234 * @param i The character.
235 * @param e The element assigned.
237 void CharToElementTable::set(unsigned i
, GdElementEnum e
) {
239 gd_warning(CPrintf("Invalid character representing element: %c") % char(i
));
243 if (table
[i
]!=O_UNKNOWN
)
244 gd_warning(CPrintf("Character %c already used by elements %s") % char(i
) % visible_name(table
[i
]));
250 static NameValuePair
<GdElementEnum
> name_to_element
;
252 void gd_cave_types_init() {
253 /* put names to a hash table */
254 /* this is a helper for file read operations */
256 for (int i
=0; i
<O_MAX
; i
++) {
257 const char *key
=gd_element_properties
[i
].filename
;
259 g_assert(key
!=NULL
&& !gd_str_equal(key
, ""));
260 /* check if every name is used once */
261 g_assert(!name_to_element
.has_name(key
));
262 name_to_element
.add(key
, GdElementEnum(i
));
264 /* for compatibility with tim stridmann's memorydump->bdcff converter... .... ... */
265 name_to_element
.add("HEXPANDING_WALL", O_H_EXPANDING_WALL
);
266 name_to_element
.add("FALLING_DIAMOND", O_DIAMOND_F
);
267 name_to_element
.add("FALLING_BOULDER", O_STONE_F
);
268 name_to_element
.add("EXPLOSION1S", O_EXPLODE_1
);
269 name_to_element
.add("EXPLOSION2S", O_EXPLODE_2
);
270 name_to_element
.add("EXPLOSION3S", O_EXPLODE_3
);
271 name_to_element
.add("EXPLOSION4S", O_EXPLODE_4
);
272 name_to_element
.add("EXPLOSION5S", O_EXPLODE_5
);
273 name_to_element
.add("EXPLOSION1D", O_PRE_DIA_1
);
274 name_to_element
.add("EXPLOSION2D", O_PRE_DIA_2
);
275 name_to_element
.add("EXPLOSION3D", O_PRE_DIA_3
);
276 name_to_element
.add("EXPLOSION4D", O_PRE_DIA_4
);
277 name_to_element
.add("EXPLOSION5D", O_PRE_DIA_5
);
278 name_to_element
.add("WALL2", O_STEEL_EXPLODABLE
);
279 /* compatibility with old bd-faq (pre disassembly of bladder) */
280 name_to_element
.add("BLADDERd9", O_BLADDER_8
);
282 /* create table to show errors at the start of the application */
283 CharToElementTable _ctet
;
285 /* check element database for faults. */
286 for (int i
=0; gd_element_properties
[i
].element
!=-1; i
++) {
287 g_assert(gd_element_properties
[i
].element
==i
);
288 /* game pixbuf should not use (generated) editor pixbuf */
289 g_assert(abs(gd_element_properties
[i
].image_game
)<NUM_OF_CELLS_X
*NUM_OF_CELLS_Y
);
290 /* editor pixbuf should not be animated */
291 g_assert(gd_element_properties
[i
].image
>=0);
292 if (gd_element_properties
[i
].flags
&P_CAN_BE_HAMMERED
)
293 g_assert(gd_element_get_hammered(GdElementEnum(i
))!=O_NONE
);
295 /* if its pair is not the same as itself, it is a scanned pair. */
296 if (gd_element_properties
[i
].pair
!=i
) {
297 /* check if it has correct scanned pair, a->b, b->a */
298 g_assert(gd_element_properties
[gd_element_properties
[i
].pair
].pair
==i
);
299 if (gd_element_properties
[i
].flags
& P_SCANNED
) {
300 /* if this one is the scanned */
301 /* check if non-scanned pair is not tagged as scanned */
302 g_assert((gd_element_properties
[gd_element_properties
[i
].pair
].flags
&P_SCANNED
)==0);
303 /* check if no ckdelay */
304 g_assert(gd_element_properties
[i
].ckdelay
==0);
305 } else if (gd_element_properties
[gd_element_properties
[i
].pair
].flags
& P_SCANNED
) {
306 /* if this one is the non-scanned */
307 g_assert((gd_element_properties
[gd_element_properties
[i
].pair
].flags
& P_SCANNED
)!=0);
309 /* scan pair - one of them should be scanned */
310 g_assert_not_reached();
315 g_assert(GD_SCHEDULING_MAX
==G_N_ELEMENTS(scheduling_filename
));
316 g_assert(GD_SCHEDULING_MAX
==G_N_ELEMENTS(scheduling_name
));
317 g_assert(MV_MAX
==G_N_ELEMENTS(direction_filename
));
318 g_assert(MV_MAX
==G_N_ELEMENTS(direction_name
));
319 g_assert(GD_ENGINE_MAX
==G_N_ELEMENTS(engines_filename
));
320 g_assert(GD_ENGINE_MAX
==G_N_ELEMENTS(engines_name
));
323 /// Load an element from a stream, where it is stored in its name.
324 /// If loading fails, the stream is set to an error state.
325 /// If the element name is not found, an error state is also set.
326 /// @param is The istream to load from.
327 /// @param e The element to store to.
328 std::istream
& operator>>(std::istream
&is
, GdElementEnum
&e
) {
331 if (!name_to_element
.has_name(s
))
332 is
.setstate(std::ios::failbit
);
334 e
=name_to_element
.lookup_name(s
);
339 /// Save a GdBool to a stream, by writing either "false" or "true".
340 std::ostream
& operator<<(std::ostream
& os
, GdBool
const& b
) {
341 os
<< (b
?"true":"false");
345 /// Convert a string to a GdBool.
346 /// If conversion succeeds, sets b; otherwise b is left untouched.
347 /// @param s The string to convert. Can contain 0, 1, true, false, on, off, yes, no.
348 /// @param b The GdBool to write to.
349 /// @return true, if the conversion succeeded.
350 bool read_from_string(const std::string
& s
, GdBool
& b
) {
352 || gd_str_ascii_caseequal(s
, "true")
353 || gd_str_ascii_caseequal(s
, "on")
354 || gd_str_ascii_caseequal(s
, "yes")) {
359 || gd_str_ascii_caseequal(s
, "false")
360 || gd_str_ascii_caseequal(s
, "off")
361 || gd_str_ascii_caseequal(s
, "no")) {
368 /// Save a GdInt to an ostream.
369 std::ostream
& operator<<(std::ostream
& os
, GdInt
const& i
) {
370 /* have to convert, or else it would be infinite recursion? */
376 /// Load a GdInt from a string.
377 /// If conversion succeeds, sets i; otherwise it is left untouched.
378 /// @param s The string to convert.
379 /// @param i The GdInt to write to.
380 /// @return true, if the conversion succeeded.
381 bool read_from_string(const std::string
& s
, GdInt
& i
) {
382 std::istringstream
is(s
);
383 /* was saved as a normal int */
385 bool success
=(is
>>read
);
392 /// Save a GdProbability to an ostream.
393 std::ostream
& operator<<(std::ostream
& os
, GdProbability
const& i
) {
394 double conv
=i
/1000000.0;
399 /// Load a GdProbability (stored as a floating point number) from a string.
400 /// If conversion succeeds, sets i; otherwise it is left untouched.
401 /// @param s The string to convert.
402 /// @param p The GdProbability to write to.
403 /// @return true, if the conversion succeeded.
404 bool read_from_string(const std::string
& s
, GdProbability
& p
) {
405 std::istringstream
is(s
);
407 bool success
=(is
>>read
) && (read
>=0 && read
<=1);
413 /// Load a GdInt (stored as a floating point number) from a string.
414 /// If conversion succeeds, sets i; otherwise it is left untouched.
415 /// @param s The string to convert.
416 /// @param i The GdInt to write to.
417 /// @param conversion_ratio The number to multiply the converted value with. (1million for probabilities, cave width*height for ratios)
418 /// @return true, if the conversion succeeded.
419 bool read_from_string(const std::string
& s
, GdInt
& i
, double conversion_ratio
) {
420 std::istringstream
is(s
);
422 bool success
=(is
>>read
) && (read
>=0 && read
<=1);
424 i
=read
*conversion_ratio
+0.5;
428 /// Save a GdScheduling to an ostream with its name.
429 std::ostream
& operator<<(std::ostream
& os
, GdScheduling
const& s
) {
430 os
<< scheduling_filename
[s
];
434 /// Read a GdScheduling from a string.
435 /// If conversion succeeds, sets sch; otherwise it is left untouched.
436 /// @param s The string to convert.
437 /// @param sch The GdScheduling to write to.
438 /// @return true, if the conversion succeeded.
439 bool read_from_string(const std::string
& s
, GdScheduling
& sch
) {
440 for (unsigned i
=0; i
<G_N_ELEMENTS(scheduling_filename
); ++i
)
441 if (gd_str_ascii_caseequal(s
, scheduling_filename
[i
])) {
442 sch
=GdSchedulingEnum(i
);
448 /// Save a GdDirection to an ostream with its name.
449 std::ostream
& operator<<(std::ostream
& os
, GdDirection
const& d
) {
450 os
<< direction_name
[d
];
454 /// Read a GdDirection from a string.
455 /// If conversion succeeds, sets d; otherwise it is left untouched.
456 /// @param s The string to convert.
457 /// @param d The GdDirection to write to.
458 /// @return true, if the conversion succeeded.
459 bool read_from_string(const std::string
& s
, GdDirection
& d
) {
460 for (unsigned i
=0; i
<G_N_ELEMENTS(direction_filename
); ++i
)
461 if (gd_str_ascii_caseequal(s
, direction_filename
[i
])) {
462 d
=GdDirectionEnum(i
);
468 /// Save a GdElement to an ostream with its name.
469 std::ostream
& operator<<(std::ostream
& os
, GdElement
const& e
) {
470 os
<< gd_element_properties
[e
].filename
;
474 /// Read a GdElement from a string.
475 /// If conversion succeeds, sets e; otherwise it is left untouched.
476 /// @param s The string to convert.
477 /// @param e The GdElement to write to.
478 /// @return true, if the conversion succeeded.
479 bool read_from_string(const std::string
& s
, GdElement
& e
) {
480 if (!name_to_element
.has_name(s
))
482 e
=name_to_element
.lookup_name(s
);
486 /// Read a GdEngine from a string.
487 /// If conversion succeeds, sets e; otherwise it is left untouched.
488 /// @param s The string to convert.
489 /// @param e The GdEngine to write to.
490 /// @return true, if the conversion succeeded.
491 bool read_from_string(const std::string
& s
, GdEngine
& e
) {
492 for (unsigned i
=0; i
<G_N_ELEMENTS(engines_filename
); ++i
)
493 if (gd_str_ascii_caseequal(s
, engines_filename
[i
])) {