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 "cave/elementproperties.hpp"
21 #include "cave/caverendered.hpp"
22 #include "cave/cavestored.hpp"
23 #include "misc/printf.hpp"
24 #include "misc/logger.hpp"
26 /// Add extra ckdelay to cave by checking the existence some animated elements.
27 /// bd1 and similar engines had animation bits in cave data, to set which elements to animate (firefly, butterfly, amoeba).
28 /// animating an element also caused some delay each frame; according to my measurements, around 2.6 ms/element.
29 void CaveRendered::set_ckdelay_extra_for_animation() {
30 g_assert(!map
.empty());
32 bool has_amoeba
=false, has_firefly
=false, has_butterfly
=false, has_slime
=false;
34 for (int y
=0; y
<height(); y
++)
35 for (int x
=0; x
<width(); x
++) {
56 /* other animated elements are not important,
57 * because they were not present in bd2. */
61 ckdelay_extra_for_animation
=0;
63 ckdelay_extra_for_animation
+=2600;
65 ckdelay_extra_for_animation
+=2600;
67 ckdelay_extra_for_animation
+=2600;
69 ckdelay_extra_for_animation
+=2600;
71 ; /* slime was always animated, no extra cost */
74 /// Do some init - setup some cave variables before the game.
75 /// Put in a different function, so things which are not
76 /// important for the editor are not done when constructing the cave.
77 void CaveRendered::setup_for_game() {
78 set_ckdelay_extra_for_animation();
80 /* find the player which will be the one to scroll to at the beginning of the game (before the player's birth) */
81 if (active_is_first_found
) {
82 /* uppermost player is active */
83 for (int y
=height()-1; y
>=0; y
--)
84 for (int x
=width()-1; x
>=0; x
--)
85 if (map(x
,y
)==O_INBOX
) {
90 /* lowermost player is active */
91 for (int y
=0; y
<height(); y
++)
92 for (int x
=0; x
<width(); x
++)
93 if (map(x
,y
)==O_INBOX
) {
98 for (unsigned i
=0; i
<PlayerMemSize
; ++i
) {
99 player_x_mem
[i
]=player_x
;
100 player_y_mem
[i
]=player_y
;
103 /* select number of milliseconds (for pal and ntsc) */
104 timing_factor
=pal_timing
?1200:1000;
106 magic_wall_time
*=timing_factor
;
107 amoeba_time
*=timing_factor
;
108 amoeba_2_time
*=timing_factor
;
109 hatching_delay_time
*=timing_factor
;
112 objects_order
.remove(); /* only needed by the editor */
113 if (hammered_walls_reappear
)
114 hammered_reappear
.set_size(w
, h
, 0);
115 /* set cave get function; to implement perfect or lineshifting borders */
117 map
.set_wrap_type(CaveMapBase::LineShift
);
119 map
.set_wrap_type(CaveMapBase::Perfect
);
122 /// Count diamonds in a cave, and set diamonds_needed accordingly.
123 /// Cave diamonds needed can be set to n<=0. If so, count the diamonds at the time of the hatching, and decrement that value from
124 /// the number of diamonds found. Of course, this function is to be called from the cave engine, at the exact time of hatching.
125 void CaveRendered::count_diamonds() {
126 /* if automatically counting diamonds. if this was negative,
127 * the sum will be this less than the number of all the diamonds in the cave */
128 if (diamonds_needed
<=0) {
129 for (int y
=0; y
<height(); y
++)
130 for (int x
=0; x
<width(); x
++)
134 case O_FLYING_DIAMOND
:
135 case O_FLYING_DIAMOND_F
:
139 diamonds_needed
+=skeletons_worth_diamonds
;
144 if (diamonds_needed
<0)
145 /* if still below zero, let this be 0, so gate will be open immediately */
151 /// Draw a cave into a gfx buffer (integer map) - set the cave cell index from the png.
152 /// Takes a cave and a gfx buffer, and fills the buffer with cell indexes.
153 /// The indexes might change if bonus life flash is active (small lines in "SPACE" cells),
154 /// for the paused state (which is used in gdash but not in sdash) - yellowish color.
155 /// Also one can select the animation frame (0..7) to draw the cave on. So the caller manages
157 /// If a cell is changed, it is flagged with GD_REDRAW; the flag can be cleared by the caller.
158 /// @param gfx_buffer A map, which must be the same size as the map of the cave.
159 /// @param bonus_life_flash Set to true, if the player got a bonus life. The space element will change accordingly.
160 /// @param animcycle Animation cycle - an integer between 0 and 7 to select animated frames.
161 /// @param hate_invisible_outbox Show invisible outboxes as visible (blinking) ones.
162 void CaveRendered::draw_indexes(CaveMap
<int> &gfx_buffer
, CaveMap
<bool> const &covered
, bool bonus_life_flash
, int animcycle
, bool hate_invisible_outbox
) {
163 int elemdrawing
[O_MAX
];
165 g_assert(!map
.empty());
166 g_assert(animcycle
>=0);
167 g_assert(animcycle
<=7);
169 if (last_direction
) { /* he is moving, so stop blinking and tapping. */
170 player_blinking
=false;
171 player_tapping
=false;
173 else { /* he is idle, so animations can be done. */
174 if (animcycle
==0) { /* blinking and tapping is started at the beginning of animation sequences. */
175 player_blinking
=g_random_int_range(0, 4)==0; /* 1/4 chance of blinking, every sequence. */
176 if (g_random_int_range(0, 16)==0) /* 1/16 chance of starting or stopping tapping. */
177 player_tapping
=!player_tapping
;
181 for (int x
=0; x
<O_MAX
; x
++)
182 elemdrawing
[x
]=gd_element_properties
[x
].image_game
;
183 if (bonus_life_flash
)
184 elemdrawing
[O_SPACE
]=gd_element_properties
[O_FAKE_BONUS
].image_game
;
185 elemdrawing
[O_MAGIC_WALL
]=gd_element_properties
[magic_wall_state
==GD_MW_ACTIVE
? O_MAGIC_WALL
: O_BRICK
].image_game
;
186 elemdrawing
[O_CREATURE_SWITCH
]=gd_element_properties
[creatures_backwards
? O_CREATURE_SWITCH_ON
: O_CREATURE_SWITCH
].image_game
;
187 elemdrawing
[O_EXPANDING_WALL_SWITCH
]=gd_element_properties
[expanding_wall_changed
? O_EXPANDING_WALL_SWITCH_VERT
: O_EXPANDING_WALL_SWITCH_HORIZ
].image_game
;
188 elemdrawing
[O_GRAVITY_SWITCH
]=gd_element_properties
[gravity_switch_active
?O_GRAVITY_SWITCH_ACTIVE
:O_GRAVITY_SWITCH
].image_game
;
189 elemdrawing
[O_REPLICATOR_SWITCH
]=gd_element_properties
[replicators_active
?O_REPLICATOR_SWITCH_ON
:O_REPLICATOR_SWITCH_OFF
].image_game
;
190 if (!replicators_active
)
191 /* if the replicators are inactive, do not animate them. */
192 elemdrawing
[O_REPLICATOR
]=abs(elemdrawing
[O_REPLICATOR
]);
193 elemdrawing
[O_CONVEYOR_SWITCH
]=gd_element_properties
[conveyor_belts_active
?O_CONVEYOR_SWITCH_ON
:O_CONVEYOR_SWITCH_OFF
].image_game
;
194 if (conveyor_belts_direction_changed
) {
195 /* if direction is changed, animation is changed. */
198 temp
=elemdrawing
[O_CONVEYOR_LEFT
];
199 elemdrawing
[O_CONVEYOR_LEFT
]=elemdrawing
[O_CONVEYOR_RIGHT
];
200 elemdrawing
[O_CONVEYOR_RIGHT
]=temp
;
202 elemdrawing
[O_CONVEYOR_DIR_SWITCH
]=gd_element_properties
[O_CONVEYOR_DIR_CHANGED
].image_game
;
205 elemdrawing
[O_CONVEYOR_DIR_SWITCH
]=gd_element_properties
[O_CONVEYOR_DIR_NORMAL
].image_game
;
206 if (!conveyor_belts_active
) {
207 /* if they are not running, do not animate them. */
208 elemdrawing
[O_CONVEYOR_LEFT
]=abs(elemdrawing
[O_CONVEYOR_LEFT
]);
209 elemdrawing
[O_CONVEYOR_RIGHT
]=abs(elemdrawing
[O_CONVEYOR_RIGHT
]);
212 elemdrawing
[O_PNEUMATIC_ACTIVE_LEFT
]+=2; /* also a hack, like biter_switch */
213 elemdrawing
[O_PNEUMATIC_ACTIVE_RIGHT
]+=2;
214 elemdrawing
[O_PLAYER_PNEUMATIC_LEFT
]+=2;
215 elemdrawing
[O_PLAYER_PNEUMATIC_RIGHT
]+=2;
219 if ((last_direction
) == MV_STILL
) { /* player is idle. */
220 if (player_blinking
&& player_tapping
)
221 draw
=gd_element_properties
[O_PLAYER_TAP_BLINK
].image_game
;
222 else if (player_blinking
)
223 draw
=gd_element_properties
[O_PLAYER_BLINK
].image_game
;
224 else if (player_tapping
)
225 draw
=gd_element_properties
[O_PLAYER_TAP
].image_game
;
227 draw
=gd_element_properties
[O_PLAYER
].image_game
;
229 else if (last_horizontal_direction
== MV_LEFT
)
230 draw
=gd_element_properties
[O_PLAYER_LEFT
].image_game
;
232 /* of course this is MV_RIGHT. */
233 draw
=gd_element_properties
[O_PLAYER_RIGHT
].image_game
;
234 elemdrawing
[O_PLAYER
]=draw
;
235 elemdrawing
[O_PLAYER_GLUED
]=draw
;
236 /* player with bomb does not blink or tap - no graphics drawn for that. running is drawn using w/o bomb cells */
237 if (last_direction
!=MV_STILL
)
238 elemdrawing
[O_PLAYER_BOMB
]=draw
;
239 elemdrawing
[O_INBOX
]=gd_element_properties
[inbox_flash_toggle
? O_OUTBOX_OPEN
: O_OUTBOX_CLOSED
].image_game
;
240 elemdrawing
[O_OUTBOX
]=gd_element_properties
[inbox_flash_toggle
? O_OUTBOX_OPEN
: O_OUTBOX_CLOSED
].image_game
;
241 elemdrawing
[O_BITER_SWITCH
]=gd_element_properties
[O_BITER_SWITCH
].image_game
+biter_delay_frame
; /* hack, cannot do this with gd_element_properties */
243 elemdrawing
[O_DIRT
]=elemdrawing
[dirt_looks_like
];
244 elemdrawing
[O_EXPANDING_WALL
]=elemdrawing
[expanding_wall_looks_like
];
245 elemdrawing
[O_V_EXPANDING_WALL
]=elemdrawing
[expanding_wall_looks_like
];
246 elemdrawing
[O_H_EXPANDING_WALL
]=elemdrawing
[expanding_wall_looks_like
];
247 elemdrawing
[O_AMOEBA_2
]=elemdrawing
[amoeba_2_looks_like
];
249 /* change only graphically */
250 if (hate_invisible_outbox
) {
251 elemdrawing
[O_PRE_INVIS_OUTBOX
]=elemdrawing
[O_PRE_OUTBOX
];
252 elemdrawing
[O_INVIS_OUTBOX
]=elemdrawing
[O_OUTBOX
];
255 for (int y
=y1
; y
<=y2
; y
++) {
256 for (int x
=x1
; x
<=x2
; x
++) {
259 if (covered(x
, y
)) /* if covered, real element is not important */
260 draw
=gd_element_properties
[O_COVERED
].image_game
;
262 draw
=elemdrawing
[map(x
,y
)];
264 /* if negative, animated. */
266 draw
=-draw
+animcycle
;
271 /* set to buffer, with caching */
272 if (gfx_buffer(x
,y
)!=draw
)
273 gfx_buffer(x
,y
)=draw
| GD_REDRAW
;
278 /// Convert cave time stored in milliseconds to a visible time in seconds.
279 /// Internal time may be in real milliseconds or "1200 milliseconds/second"
280 /// for pal timing. This is taken into account by this function.
281 /// Cave time returned is rounded _UP_ to seconds. So at the exact moment when it changes from
282 /// 2sec remaining to 1sec remaining, the player has exactly one second. When it changes
283 /// to zero, it is the exact moment of timeout.
284 /// @param internal_time The internal time variable of the cave.
285 /// @return The time value in seconds, which can be shown to the user.
286 int CaveRendered::time_visible(int internal_time
) const {
287 return (internal_time
+timing_factor
-1)/timing_factor
;
290 /// Calculate adler checksum for a rendered cave; this can be used for more caves.
291 void gd_cave_adler_checksum_more(const CaveRendered
&cave
, unsigned &a
, unsigned &b
) {
292 for (int y
=0; y
<cave
.h
; y
++) {
293 for (int x
=0; x
<cave
.w
; x
++) {
294 a
+=gd_element_properties
[cave
.map(x
,y
)].character
;
303 /// Calculate adler checksum for a single rendered cave
304 unsigned gd_cave_adler_checksum(const CaveRendered
&cave
) {
308 gd_cave_adler_checksum_more(cave
, a
, b
);
312 int gd_cave_check_replays(CaveStored
&cave
, bool report
, bool remove
, bool repair
) {
314 for (std::list
<CaveReplay
>::iterator it
=cave
.replays
.begin(); it
!=cave
.replays
.end(); ++it
) {
315 CaveReplay
&replay
=*it
;
318 CaveRendered
rendered(cave
, replay
.level
-1, replay
.seed
);
319 checksum
=gd_cave_adler_checksum(rendered
);
321 replay
.wrong_checksum
=false;
322 /* count wrong ones... the checksum might be changed later to "repair" */
323 if (replay
.checksum
!=0 && checksum
!=replay
.checksum
)
326 if (replay
.checksum
==0 || repair
) {
327 /* if no checksum found, add one. or if repair requested, overwrite old one. */
328 replay
.checksum
=checksum
;
330 /* if has a checksum, compare with this one. */
331 if (replay
.checksum
!=checksum
) {
332 replay
.wrong_checksum
=true;
335 gd_warning(CPrintf("%s: replay played by %s at %s is invalid") % cave
.name
% replay
.player_name
% replay
.date
);
338 cave
.replays
.erase(it
);
347 /// Put an element to the specified position.
348 /// Performs range checking.
349 /// If wraparound objects are selected, wraps around x coordinates, with or without lineshift.
350 /// (The y coordinate is not wrapped, as it did not work like that on the c64.)
351 /// Order is a pointer to the CaveObject describing this object which sets this element of the map.
352 /// Thus the editor can identify which cell was created by which object.
353 /// @param x The x coordinate to draw at.
354 /// @param y The y coordinate to draw at.
355 /// @param element The element to draw.
356 /// @param order Pointer to the object which draws this element, or 0 if none.
357 void CaveRendered::store_rc(int x
, int y
, GdElementEnum element
, CaveObject
const *order
) {
358 /* if we do not need to draw, exit now */
362 /* if objects wrap around (mainly in imported caves), correct the coordinates */
363 if (wraparound_objects
) {
365 CaveMapBase::lineshift_wrap_coords_only_x(w
, x
, y
);
367 CaveMapBase::perfect_wrap_coords(w
, h
, x
, y
);
370 /* if the above wraparound code fixed the coordinates, this will always be true. */
371 /* but see the above comment for lineshifting y coordinate */
372 if (x
>=0 && x
<w
&& y
>=0 && y
<h
) {
374 objects_order(x
, y
)=const_cast<CaveObject
*>(order
);
378 /// Creates a map for a playable cave.
379 /// It is in a separate function, so the editor can call it - and there
380 /// is no need to always recreate the full CaveRendered object.
381 /// Also changes the random seed values!
382 /// Must write this in a way so it can be called many times for a single CaveRendered object!
383 /// @param data The stored cave to read the map, objects and random values from
384 void CaveRendered::create_map(CaveStored
const &data
, int level
) {
386 if (data
.map
.empty()) {
387 /* if we have no map, fill with predictable random generator. */
390 /* IF CAVE HAS NO MAP, USE THE RANDOM NUMBER GENERATOR */
391 /* init c64 randomgenerator */
392 if (data
.level_rand
[level
]<0)
393 c64_rand
.set_seed(random
.rand_int_range(0, 256), random
.rand_int_range(0, 256));
395 c64_rand
.set_seed(data
.level_rand
[level
]);
397 /* generate random fill
398 * start from row 1 (0 skipped), and fill also the borders on left and right hand side,
399 * as c64 did. this way works the original random generator the right way.
400 * also, do not fill last row, that is needed for the random seeds to be correct
401 * after filling! predictable slime will use it. */
402 for (int y
=1; y
<h
-1; y
++) {
403 for (int x
=0; x
<w
; x
++) {
406 if (data
.level_rand
[level
]<0)
407 randm
=random
.rand_int_range(0, 256); /* use the much better glib random generator */
409 randm
=c64_rand
.random(); /* use c64 */
411 /* select the element to draw the way it was done on c64 */
412 GdElement element
=data
.initial_fill
;
413 if (randm
<data
.random_fill_probability_1
)
414 element
=data
.random_fill_1
;
415 if (randm
<data
.random_fill_probability_2
)
416 element
=data
.random_fill_2
;
417 if (randm
<data
.random_fill_probability_3
)
418 element
=data
.random_fill_3
;
419 if (randm
<data
.random_fill_probability_4
)
420 element
=data
.random_fill_4
;
426 /* draw initial border */
427 for (int y
=0; y
<h
; y
++) {
428 map(0, y
)=data
.initial_border
;
429 map(w
-1, y
)=data
.initial_border
;
431 for (int x
=0; x
<w
; x
++) {
432 map(x
, 0)=data
.initial_border
;
433 map(x
, h
-1)=data
.initial_border
;
437 /* IF CAVE HAS A MAP, SIMPLY USE IT... no need to fill with random elements */
439 /* initialize c64 predictable random for slime. the values were taken from afl bd, see docs/internals.txt */
440 c64_rand
.set_seed(0, 0x1e);
443 /* render cave objects above random data or map */
444 /* first, set map wraparound type - this is for the get's to work correctly */
446 map
.set_wrap_type(CaveMapBase::LineShift
);
448 map
.set_wrap_type(CaveMapBase::Perfect
);
449 /* then draw objects */
450 objects_order
.fill(0);
451 for (CaveObjectStore::const_iterator it
=data
.objects
.begin(); it
!=data
.objects
.end(); ++it
) {
452 if ((*it
)->seen_on
[rendered_on
])
457 /// Create a new CaveRendered, which is a cave used for game.
458 /// @param data The original CaveStored with objects and maybe no map.
459 /// @param level The level to draw at, 0 is level1, 4 is level5.
460 /// @param seed Random seed.
461 CaveRendered::CaveRendered(CaveStored
const &data
, int level
, int seed
)
465 amoeba_state(GD_AM_SLEEPING
),
466 amoeba_2_state(GD_AM_SLEEPING
),
467 magic_wall_state(GD_MW_DORMANT
),
468 player_state(GD_PL_NOT_YET
) {
473 time
=data
.level_time
[level
];
474 timevalue
=data
.level_timevalue
[level
];
475 diamonds_needed
=data
.level_diamonds
[level
];
476 magic_wall_time
=data
.level_magic_wall_time
[level
];
477 slime_permeability
=data
.level_slime_permeability
[level
];
478 slime_permeability_c64
=data
.level_slime_permeability_c64
[level
];
479 time_bonus
=data
.level_bonus_time
[level
];
480 time_penalty
=data
.level_penalty_time
[level
];
481 amoeba_time
=data
.level_amoeba_time
[level
];
482 amoeba_max_count
=data
.level_amoeba_threshold
[level
];
483 amoeba_2_time
=data
.level_amoeba_2_time
[level
];
484 amoeba_2_max_count
=data
.level_amoeba_2_threshold
[level
];
485 hatching_delay_time
=data
.level_hatching_delay_time
[level
];
486 hatching_delay_frame
=data
.level_hatching_delay_frame
[level
];
487 speed
=data
.level_speed
[level
];
488 ckdelay
=data
.level_ckdelay
[level
];
490 random
.set_seed(render_seed
);
491 objects_order
.resize(w
, h
);
492 create_map(data
, level
);
494 /* if a specific slime seed is requested, change it now, after creating map data */
495 /* if there is -1 in the c64 random seed, it means "leave the values those left here by the cave setup routine" */
496 if (data
.level_slime_seed_c64
[level
]!=-1)
497 c64_rand
.set_seed(data
.level_slime_seed_c64
[level
]/256, data
.level_slime_seed_c64
[level
]%256);
499 /* check if we use c64 ckdelay or milliseconds for timing */
500 if (scheduling
!=GD_SCHEDULING_MILLISECONDS
)
501 /* delay loop based timing... set something for first iteration, then later it will be calculated */
504 gd_cave_correct_visible_size(*this);