20130313
[gdash.git] / src / cave / caverendered.cpp
blobec674c676703459ef88860226d00143dcf6a2c5d
1 /*
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.
17 #include "config.h"
19 #include <cstdlib>
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++) {
36 switch (map(x, y)) {
37 case O_FIREFLY_1:
38 case O_FIREFLY_2:
39 case O_FIREFLY_3:
40 case O_FIREFLY_4:
41 has_firefly=true;
42 break;
43 case O_BUTTER_1:
44 case O_BUTTER_2:
45 case O_BUTTER_3:
46 case O_BUTTER_4:
47 has_butterfly=true;
48 break;
49 case O_AMOEBA:
50 has_amoeba=true;
51 break;
52 case O_SLIME:
53 has_slime=true;
54 break;
55 default:
56 /* other animated elements are not important,
57 * because they were not present in bd2. */
58 break;
61 ckdelay_extra_for_animation=0;
62 if (has_amoeba)
63 ckdelay_extra_for_animation+=2600;
64 if (has_firefly)
65 ckdelay_extra_for_animation+=2600;
66 if (has_butterfly)
67 ckdelay_extra_for_animation+=2600;
68 if (has_amoeba)
69 ckdelay_extra_for_animation+=2600;
70 if (has_slime)
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) {
86 player_x=x;
87 player_y=y;
89 } else {
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) {
94 player_x=x;
95 player_y=y;
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;
105 time*=timing_factor;
106 magic_wall_time*=timing_factor;
107 amoeba_time*=timing_factor;
108 amoeba_2_time*=timing_factor;
109 hatching_delay_time*=timing_factor;
111 /* setup maps */
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 */
116 if (lineshift)
117 map.set_wrap_type(CaveMapBase::LineShift);
118 else
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++)
131 switch (map(x, y)) {
132 case O_DIAMOND:
133 case O_DIAMOND_F:
134 case O_FLYING_DIAMOND:
135 case O_FLYING_DIAMOND_F:
136 diamonds_needed++;
137 break;
138 case O_SKELETON:
139 diamonds_needed+=skeletons_worth_diamonds;
140 break;
141 default:
142 break;
144 if (diamonds_needed<0)
145 /* if still below zero, let this be 0, so gate will be open immediately */
146 diamonds_needed=0;
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
156 /// increasing that.
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. */
196 int temp;
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;
204 else
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]);
211 if (animcycle&2) {
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;
218 int draw;
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;
226 else
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;
231 else
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 */
242 /* visual effects */
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++) {
257 int draw;
259 if (covered(x, y)) /* if covered, real element is not important */
260 draw=gd_element_properties[O_COVERED].image_game;
261 else
262 draw=elemdrawing[map(x,y)];
264 /* if negative, animated. */
265 if (draw<0)
266 draw=-draw+animcycle;
267 /* flash */
268 if (gate_open_flash)
269 draw+=NUM_OF_CELLS;
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;
295 b+=a;
297 a%=65521;
298 b%=65521;
303 /// Calculate adler checksum for a single rendered cave
304 unsigned gd_cave_adler_checksum(const CaveRendered &cave) {
305 unsigned a=1;
306 unsigned b=0;
308 gd_cave_adler_checksum_more(cave, a, b);
309 return (b<<16)+a;
312 int gd_cave_check_replays(CaveStored &cave, bool report, bool remove, bool repair) {
313 int wrong=0;
314 for (std::list<CaveReplay>::iterator it=cave.replays.begin(); it!=cave.replays.end(); ++it) {
315 CaveReplay &replay=*it;
316 GdInt checksum;
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)
324 wrong++;
326 if (replay.checksum==0 || repair) {
327 /* if no checksum found, add one. or if repair requested, overwrite old one. */
328 replay.checksum=checksum;
329 } else {
330 /* if has a checksum, compare with this one. */
331 if (replay.checksum!=checksum) {
332 replay.wrong_checksum=true;
334 if (report)
335 gd_warning(CPrintf("%s: replay played by %s at %s is invalid") % cave.name % replay.player_name % replay.date);
337 if (remove)
338 cave.replays.erase(it);
343 return wrong;
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 */
359 if (element==O_NONE)
360 return;
362 /* if objects wrap around (mainly in imported caves), correct the coordinates */
363 if (wraparound_objects) {
364 if (lineshift)
365 CaveMapBase::lineshift_wrap_coords_only_x(w, x, y);
366 else
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) {
373 map(x, y)=element;
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) {
385 rendered_on=level;
386 if (data.map.empty()) {
387 /* if we have no map, fill with predictable random generator. */
388 map.set_size(w, h);
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));
394 else
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++) {
404 int randm;
406 if (data.level_rand[level]<0)
407 randm=random.rand_int_range(0, 256); /* use the much better glib random generator */
408 else
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;
422 map(x, y)=element;
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;
436 else {
437 /* IF CAVE HAS A MAP, SIMPLY USE IT... no need to fill with random elements */
438 map=data.map;
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 */
445 if (lineshift)
446 map.set_wrap_type(CaveMapBase::LineShift);
447 else
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])
453 (*it)->draw(*this);
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)
463 CaveBase(data),
464 objects_order(),
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) {
469 rendered_on=level;
471 render_seed=seed;
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 */
502 speed=120;
504 gd_cave_correct_visible_size(*this);