20130427
[gdash.git] / src / cave / caverenderedengine.cpp
blob7a9528ca7ca9bf6f07985f6164eff396ca35b557
1 /*
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.
24 #include "config.h"
26 #include <cmath>
28 #include "cave/caverendered.hpp"
29 #include "cave/elementproperties.hpp"
30 #include "settings.hpp"
33 // for gravity and other routines.
34 // these arrays contain the rotated directions.
35 // ccw eighth: counter-clockwise, 1/8 turn (45 degrees)
36 // cw fourth: clockwise, 1/4 turn (90 degrees)
37 static GdDirection const ccw_eighth[] = { MV_STILL, MV_UP_LEFT, MV_UP, MV_UP_RIGHT, MV_RIGHT, MV_DOWN_RIGHT, MV_DOWN, MV_DOWN_LEFT };
38 static GdDirection const ccw_fourth[] = { MV_STILL, MV_LEFT, MV_UP_LEFT, MV_UP, MV_UP_RIGHT, MV_RIGHT, MV_DOWN_RIGHT, MV_DOWN, MV_DOWN_LEFT, MV_LEFT };
39 static GdDirection const cw_eighth[] = { MV_STILL, MV_UP_RIGHT, MV_RIGHT, MV_DOWN_RIGHT, MV_DOWN, MV_DOWN_LEFT, MV_LEFT, MV_UP_LEFT, MV_UP };
40 static GdDirection const cw_fourth[] = { MV_STILL, MV_RIGHT, MV_DOWN_RIGHT, MV_DOWN, MV_DOWN_LEFT, MV_LEFT, MV_UP_LEFT, MV_UP, MV_UP_RIGHT };
41 // 180 degrees turn of a direction
42 static GdDirection const opposite[] = { MV_STILL, MV_DOWN, MV_DOWN_LEFT, MV_LEFT, MV_UP_LEFT, MV_UP, MV_UP_RIGHT, MV_RIGHT, MV_DOWN_RIGHT };
43 // doubling a direction (e.g. right=1,0 2x right=2,0
44 static GdDirection const twice[] = { MV_STILL, MV_UP_2, MV_UP_RIGHT_2, MV_RIGHT_2, MV_DOWN_RIGHT_2, MV_DOWN_2, MV_DOWN_LEFT_2, MV_LEFT_2, MV_UP_LEFT_2 };
47 void CaveRendered::add_particle_set(int x, int y, GdElementEnum particletype) {
48 if (!gd_particle_effects)
49 return;
51 /* movements and sizes can depend on gravity. */
52 /* when stones & diamonds are falling, particle effects are perpendicular
53 * to the direction of gravity (eg. falling vertically, and some extends
54 * horizonally), so gx and gy are deliberately swapped in some expressions below. */
55 double gx = gd_dx[gravity], gy = gd_dy[gravity], agx = fabs(gx), agy = fabs(gy);
56 switch (particletype) {
57 case O_DIRT:
58 particles.push_back(ParticleSet(75, 0.1, 0.15, x + 0.5, y + 0.5, 0.5, 0.5, 0, 0, 1, 1, dirt_particle_color));
59 break;
60 case O_DIRT2:
61 particles.push_back(ParticleSet(75, 0.1, 0.15, x + 0.5, y + 0.5, 0.5, 0.5, 0, 0, 1, 1, dirt_2_particle_color));
62 break;
63 case O_STONE_F:
64 particles.push_back(ParticleSet(75, 0.1, 0.15,
65 x + 0.5 + 0.5 * gx, y + 0.5 + 0.5 * gy, 0.25 + 0.25 * agy, 0.25 + 0.25 * agx,
66 0.5 * gx, 0.5 * gy, 1 + agy, 1 + agx, stone_particle_color));
67 break;
68 case O_MEGA_STONE_F:
69 particles.push_back(ParticleSet(75, 0.1, 0.15,
70 x + 0.5 + 0.5 * gx, y + 0.5 + 0.5 * gy, 0.25 + 0.25 * agy, 0.25 + 0.25 * agx,
71 0.5 * gx, 0.5 * gy, 1 + agy, 1 + agx, mega_stone_particle_color));
72 break;
73 case O_DIAMOND_F:
74 /* falling diamond */
75 particles.push_back(ParticleSet(15, 0.03, 0.5,
76 x + 0.5 + 0.5 * gx, y + 0.5 + 0.5 * gy, 0.25, 0.25,
77 0, 0, 2, 2, diamond_particle_color));
78 break;
79 case O_DIAMOND:
80 /* collecting diamond */
81 particles.push_back(ParticleSet(8, 0.03, 0.5,
82 x + 0.5, y + 0.5, 0.25, 0.25,
83 0, 0, 2, 2, diamond_particle_color));
84 break;
85 case O_EXPLODE_1:
86 /* for explosions, the original place of the particles is a 2x2 cave cell area, but they
87 * expand rapidly. */
88 particles.push_back(ParticleSet(300, 0.05, 0.5, x + 0.5, y + 0.5, 1.0, 1.0, 0, 0, 4, 4, explosion_particle_color));
89 break;
90 case O_PRE_DIA_1:
91 particles.push_back(ParticleSet(300, 0.05, 0.5, x + 0.5, y + 0.5, 1.0, 1.0, 0, 0, 4, 4, diamond_particle_color));
92 break;
93 case O_MAGIC_WALL:
94 // a magic wall creates particles in every frame. so add only very few particles!
95 // rather they should be bright like stars
96 particles.push_back(ParticleSet(3, 0.01, 0.75, x + 0.5, y + 0.5, 0.5, 0.5, 0, 0, 1 + 2 * agx, 1 + 2 * agy, magic_wall_particle_color));
97 break;
98 case O_EXPANDING_WALL:
99 particles.push_back(ParticleSet(75, 0.1, 0.15, x + 0.5, y + 0.5, 0.5, 0.5, 0, 0, 1, 1, expanding_wall_particle_color));
100 break;
101 case O_EXPANDING_STEEL_WALL:
102 particles.push_back(ParticleSet(75, 0.1, 0.15, x + 0.5, y + 0.5, 0.5, 0.5, 0, 0, 1, 1, expanding_steel_wall_particle_color));
103 break;
104 case O_LAVA:
105 // this should look like it's boiling
106 particles.push_back(ParticleSet(10, 0.01, 0.5, x + 0.5, y + 0.5, 0.5, 0.5, 0, 0, 2, 2, lava_particle_color));
107 break;
108 case O_ROCKET_1:
109 particles.push_back(ParticleSet(100, 0.03, 0.25, x + 0.9, y + 0.5, 0.5, 0.2, -4, 0.2, 3, 0.2, explosion_particle_color));
110 break;
111 case O_ROCKET_2:
112 particles.push_back(ParticleSet(100, 0.03, 0.25, x + 0.5, y + 0.1, 0.2, 0.5, 0.2, 4, 0.2, 3, explosion_particle_color));
113 break;
114 case O_ROCKET_3:
115 particles.push_back(ParticleSet(100, 0.03, 0.25, x + 0.1, y + 0.5, 0.5, 0.2, 4, 0.2, 3, 0.2, explosion_particle_color));
116 break;
117 case O_ROCKET_4:
118 particles.push_back(ParticleSet(100, 0.03, 0.25, x + 0.5, y + 0.9, 0.2, 0.5, 0.2, -4, 0.2, 3, explosion_particle_color));
119 break;
120 default:
121 break;
126 /// Remembers to play the sound in the given cave.
127 /// The sound1, sound2 and sound3 variables will be handled by a game implementation.
128 /// This function only remembers to play them. It also checks the precedences.
129 void CaveRendered::sound_play(GdSound sound, int x, int y) {
130 switch (sound) {
131 case GD_S_NONE:
132 return;
133 break;
134 case GD_S_WATER:
135 if (!water_sound) return;
136 break;
137 case GD_S_AMOEBA:
138 if (!amoeba_sound) return;
139 break;
140 case GD_S_MAGIC_WALL:
141 if (!magic_wall_sound) return;
142 break;
143 case GD_S_STONE:
144 if (!stone_sound) return;
145 break;
146 case GD_S_DIAMOND_RANDOM:
147 if (!diamond_sound) return;
148 break;
149 case GD_S_NUT:
150 if (!nut_sound) return;
151 break;
152 case GD_S_NITRO:
153 if (!nitro_sound) return;
154 break;
155 case GD_S_FALLING_WALL:
156 if (!falling_wall_sound) return;
157 break;
158 case GD_S_EXPANDING_WALL:
159 if (!expanding_wall_sound) return;
160 break;
161 case GD_S_BLADDER_SPENDER:
162 if (!bladder_spender_sound) return;
163 break;
164 case GD_S_BLADDER_CONVERT:
165 if (!bladder_convert_sound) return;
166 break;
167 case GD_S_SLIME:
168 if (!slime_sound) return;
169 break;
170 case GD_S_LAVA:
171 if (!lava_sound) return;
172 break;
173 case GD_S_ACID_SPREAD:
174 if (!acid_spread_sound) return;
175 break;
176 case GD_S_BLADDER_MOVE:
177 if (!bladder_sound) return;
178 break;
179 case GD_S_BITER_EAT:
180 if (!biter_sound) return;
181 break;
182 case GD_S_NUT_CRACK:
183 if (!nut_sound) return;
184 break;
186 default:
187 break;
190 SoundWithPos *s;
191 switch (gd_sound_get_channel(sound)) {
192 case 1:
193 s = &sound1;
194 break;
195 case 2:
196 s = &sound2;
197 break;
198 case 3:
199 s = &sound3;
200 break;
201 default:
202 g_assert_not_reached();
203 return;
206 /* amoeba and magic wall _together_ make a different, mixed, "gritty" sound. */
207 /* so if we find a mixing situation (old=amoeba & new=magic or vice versa), do it. */
208 /* also, if we were already mixing, "continue" the mixing, but still check the
209 * distances of sounds below. */
210 if ((s->sound == GD_S_AMOEBA && sound == GD_S_MAGIC_WALL)
211 || (s->sound == GD_S_MAGIC_WALL && sound == GD_S_AMOEBA)
212 || (s->sound == GD_S_AMOEBA_MAGIC))
213 sound = GD_S_AMOEBA_MAGIC;
215 /* sound coordinates relative to player */
216 int dx = x - player_x, dy = y - player_y;
217 /* if higher precedence, or same precedence but closer than the previous one, play. */
218 if (gd_sound_get_precedence(sound) >= gd_sound_get_precedence(s->sound)
219 || (gd_sound_get_precedence(sound) == gd_sound_get_precedence(s->sound)
220 && (dx * dx + dy * dy) < (s->dx * s->dx + s->dy * s->dy))) {
221 *s = SoundWithPos(sound, dx, dy);
226 /// play sound of a given element.
227 void CaveRendered::play_effect_of_element(GdElementEnum element, int x, int y, GdDirectionEnum dir, bool particles) {
228 x += gd_dx[dir];
229 y += gd_dy[dir];
230 /* stone and diamond fall sounds. */
231 switch (element) {
232 case O_WATER:
233 sound_play(GD_S_WATER, x, y);
234 break;
235 case O_AMOEBA:
236 sound_play(GD_S_AMOEBA, x, y);
237 break;
238 case O_MAGIC_WALL:
239 sound_play(GD_S_MAGIC_WALL, x, y);
240 if (particles)
241 add_particle_set(x, y, O_MAGIC_WALL);
242 break;
243 case O_STONE:
244 case O_STONE_F:
245 case O_FLYING_STONE:
246 case O_FLYING_STONE_F:
247 case O_WAITING_STONE:
248 case O_CHASING_STONE:
249 sound_play(GD_S_STONE, x, y);
250 if (particles)
251 add_particle_set(x, y, O_STONE_F);
252 break;
254 case O_MEGA_STONE:
255 case O_MEGA_STONE_F:
256 sound_play(GD_S_STONE, x, y);
257 if (particles)
258 add_particle_set(x, y, O_MEGA_STONE_F);
259 break;
261 case O_DIAMOND:
262 case O_DIAMOND_F:
263 case O_FLYING_DIAMOND:
264 case O_FLYING_DIAMOND_F:
265 sound_play(GD_S_DIAMOND_RANDOM, x, y);
266 if (particles)
267 add_particle_set(x, y, O_DIAMOND_F);
268 break;
270 case O_NUT:
271 case O_NUT_F:
272 sound_play(GD_S_NUT, x, y);
273 break;
275 case O_NITRO_PACK:
276 case O_NITRO_PACK_F:
277 sound_play(GD_S_NITRO, x, y);
278 break;
280 case O_FALLING_WALL:
281 case O_FALLING_WALL_F:
282 sound_play(GD_S_FALLING_WALL, x, y);
283 break;
285 case O_H_EXPANDING_WALL:
286 case O_V_EXPANDING_WALL:
287 case O_EXPANDING_WALL:
288 sound_play(GD_S_EXPANDING_WALL, x, y);
289 if (particles)
290 add_particle_set(x, y, O_EXPANDING_WALL);
291 break;
293 case O_H_EXPANDING_STEEL_WALL:
294 case O_V_EXPANDING_STEEL_WALL:
295 case O_EXPANDING_STEEL_WALL:
296 sound_play(GD_S_EXPANDING_WALL, x, y);
297 if (particles)
298 add_particle_set(x, y, O_EXPANDING_STEEL_WALL);
299 break;
301 case O_BLADDER_SPENDER:
302 sound_play(GD_S_BLADDER_SPENDER, x, y);
303 break;
305 case O_PRE_CLOCK_1:
306 sound_play(GD_S_BLADDER_CONVERT, x, y);
307 break;
309 case O_SLIME:
310 sound_play(GD_S_SLIME, x, y);
311 break;
313 case O_LAVA:
314 sound_play(GD_S_LAVA, x, y);
315 break;
317 case O_ACID:
318 sound_play(GD_S_ACID_SPREAD, x, y);
319 break;
321 case O_BLADDER:
322 sound_play(GD_S_BLADDER_MOVE, x, y);
323 break;
325 case O_BITER_1:
326 case O_BITER_2:
327 case O_BITER_3:
328 case O_BITER_4:
329 sound_play(GD_S_BITER_EAT, x, y);
330 break;
332 case O_DIRT:
333 sound_play(GD_S_WALK_EARTH, x, y);
334 if (particles)
335 add_particle_set(x, y, O_DIRT);
336 break;
338 case O_DIRT2:
339 sound_play(GD_S_WALK_EARTH, x, y);
340 if (particles)
341 add_particle_set(x, y, O_DIRT2);
342 break;
344 case O_DIRT_BALL:
345 case O_DIRT_BALL_F:
346 case O_DIRT_LOOSE:
347 case O_DIRT_LOOSE_F:
348 sound_play(GD_S_DIRT_BALL, x, y);
349 if (particles)
350 add_particle_set(x, y, O_DIRT2);
351 break;
353 default:
354 /* do nothing. */
355 break;
360 void CaveRendered::play_eat_sound_of_element(GdElementEnum element, int x, int y) {
361 switch (element) {
362 case O_DIAMOND_KEY:
363 sound_play(GD_S_DIAMOND_KEY_COLLECT, x, y);
364 break;
365 case O_KEY_1:
366 sound_play(GD_S_KEY_COLLECT, x, y);
367 break;
368 case O_KEY_2:
369 sound_play(GD_S_KEY_COLLECT, x, y);
370 break;
371 case O_KEY_3:
372 sound_play(GD_S_KEY_COLLECT, x, y);
373 break;
374 case O_DOOR_1:
375 sound_play(GD_S_DOOR_OPEN, x, y);
376 break;
377 case O_DOOR_2:
378 sound_play(GD_S_DOOR_OPEN, x, y);
379 break;
380 case O_DOOR_3:
381 sound_play(GD_S_DOOR_OPEN, x, y);
382 break;
383 case O_CREATURE_SWITCH:
384 sound_play(GD_S_SWITCH_CREATURES, x, y);
385 break;
386 case O_EXPANDING_WALL_SWITCH:
387 sound_play(GD_S_SWITCH_EXPANDING, x, y);
388 break;
389 case O_BITER_SWITCH: /* biter change delay */
390 sound_play(GD_S_SWITCH_BITER, x, y);
391 break;
392 case O_REPLICATOR_SWITCH: /* replicator on/off switch */
393 sound_play(GD_S_SWITCH_REPLICATOR, x, y);
394 break;
395 case O_CONVEYOR_SWITCH: /* conveyor belts on/off */
396 sound_play(GD_S_SWITCH_CONVEYOR, x, y);
397 break;
398 case O_CONVEYOR_DIR_SWITCH: /* conveyor belts switch direction */
399 sound_play(GD_S_SWITCH_CONVEYOR, x, y);
400 break;
401 case O_DIRT:
402 case O_DIRT_SLOPED_UP_RIGHT:
403 case O_DIRT_SLOPED_UP_LEFT:
404 case O_DIRT_SLOPED_DOWN_LEFT:
405 case O_DIRT_SLOPED_DOWN_RIGHT:
406 sound_play(GD_S_WALK_EARTH, x, y);
407 break;
408 case O_DIRT2:
409 case O_DIRT_BALL:
410 case O_DIRT_LOOSE:
411 sound_play(GD_S_WALK_EARTH, x, y);
412 break;
413 case O_STEEL_EATABLE:
414 case O_BRICK_EATABLE:
415 sound_play(GD_S_WALK_EARTH, x, y);
416 break;
417 case O_SPACE:
418 case O_LAVA:
419 sound_play(GD_S_WALK_EMPTY, x, y);
420 break;
421 case O_SWEET:
422 sound_play(GD_S_SWEET_COLLECT, x, y);
423 break;
424 case O_PNEUMATIC_HAMMER:
425 sound_play(GD_S_PNEUMATIC_COLLECT, x, y);
426 break;
427 case O_CLOCK:
428 sound_play(GD_S_CLOCK_COLLECT, x, y);
429 break;
430 case O_DIAMOND:
431 case O_FLYING_DIAMOND:
432 sound_play(GD_S_DIAMOND_COLLECT, x, y);
433 break;
434 case O_SKELETON:
435 sound_play(GD_S_SKELETON_COLLECT, x, y);
436 break;
437 default:
438 /* no effect */
439 break;
444 void CaveRendered::play_eat_particle_of_element(GdElementEnum element, int x, int y) {
445 switch (element) {
446 case O_DIRT:
447 case O_DIRT_SLOPED_UP_RIGHT:
448 case O_DIRT_SLOPED_UP_LEFT:
449 case O_DIRT_SLOPED_DOWN_LEFT:
450 case O_DIRT_SLOPED_DOWN_RIGHT:
451 add_particle_set(x, y, O_DIRT);
452 break;
453 case O_DIRT2:
454 case O_DIRT_BALL:
455 case O_DIRT_LOOSE:
456 add_particle_set(x, y, O_DIRT2);
457 break;
458 case O_DIAMOND:
459 case O_FLYING_DIAMOND:
460 add_particle_set(x, y, O_DIAMOND);
461 break;
462 case O_STEEL_EATABLE:
463 case O_BRICK_EATABLE:
464 break;
465 default:
466 /* no effect */
467 break;
472 /// sets timeout sound.
473 void CaveRendered::set_seconds_sound() {
474 /* this is an integer division, so 0 seconds can be 0.5 seconds... */
475 /* also, when this reaches 8, the player still has 8.9999 seconds. so the sound is played at almost t=9s. */
476 switch (time / timing_factor) {
477 case 8:
478 sound_play(GD_S_TIMEOUT_1, player_x, player_y);
479 break;
480 case 7:
481 sound_play(GD_S_TIMEOUT_2, player_x, player_y);
482 break;
483 case 6:
484 sound_play(GD_S_TIMEOUT_3, player_x, player_y);
485 break;
486 case 5:
487 sound_play(GD_S_TIMEOUT_4, player_x, player_y);
488 break;
489 case 4:
490 sound_play(GD_S_TIMEOUT_5, player_x, player_y);
491 break;
492 case 3:
493 sound_play(GD_S_TIMEOUT_6, player_x, player_y);
494 break;
495 case 2:
496 sound_play(GD_S_TIMEOUT_7, player_x, player_y);
497 break;
498 case 1:
499 sound_play(GD_S_TIMEOUT_8, player_x, player_y);
500 break;
501 case 0:
502 sound_play(GD_S_TIMEOUT_9, player_x, player_y);
503 break;
508 /// Returns the element at x,y.
509 inline GdElementEnum CaveRendered::get(int x, int y) const {
510 return map(x, y);
513 /// Returns the element at (x,y)+dir.
514 inline GdElementEnum CaveRendered::get(int x, int y, GdDirectionEnum dir) const {
515 return get(x + gd_dx[dir], y + gd_dy[dir]);
518 /// Returns true, if element at (x,y)+dir explodes if hit by a stone (for example, a firefly).
519 inline bool CaveRendered::explodes_by_hit(int x, int y, GdDirectionEnum dir) const {
520 return (gd_element_properties[get(x, y, dir)].flags & P_EXPLODES_BY_HIT) != 0;
523 /// returns true, if the element is not explodable (for example the steel wall).
524 inline bool CaveRendered::non_explodable(int x, int y) const {
525 return (gd_element_properties[get(x, y)].flags & P_NON_EXPLODABLE) != 0;
528 /// returns true, if the element at (x,y)+dir can be eaten by the amoeba (dirt, space)
529 inline bool CaveRendered::amoeba_eats(int x, int y, GdDirectionEnum dir) const {
530 return (gd_element_properties[get(x, y, dir)].flags & P_AMOEBA_CONSUMES) != 0;
533 /// Returns true if the element is sloped, so stones and diamonds roll down on it.
534 /// For example a stone or brick wall.
535 /// Some elements can be sloped in specific directions only; for example a wall
536 /// like /| is sloped from the up to the left.
537 /// @param x The x coordinate
538 /// @param y The y coordinate
539 /// @param dir The coordinate to move from (x,y), e.g. element at (x,y)+dir is checked.
540 /// @param slop The direction in which the element should be sloped.
541 bool CaveRendered::sloped(int x, int y, GdDirectionEnum dir, GdDirectionEnum slop) const {
542 switch (slop) {
543 case MV_LEFT:
544 return (gd_element_properties[get(x, y, dir)].flags & P_SLOPED_LEFT) != 0;
545 case MV_RIGHT:
546 return (gd_element_properties[get(x, y, dir)].flags & P_SLOPED_RIGHT) != 0;
547 case MV_UP:
548 return (gd_element_properties[get(x, y, dir)].flags & P_SLOPED_UP) != 0;
549 case MV_DOWN:
550 return (gd_element_properties[get(x, y, dir)].flags & P_SLOPED_DOWN) != 0;
551 default:
552 break;
555 return false;
558 /// returns true if the element is sloped for bladder movement (brick=yes, diamond=no, for example)
559 inline bool CaveRendered::sloped_for_bladder(int x, int y, GdDirectionEnum dir) const {
560 return (gd_element_properties[get(x, y, dir)].flags & P_BLADDER_SLOPED) != 0;
563 /// returns true if the element at (x,y)+dir can blow up a fly by touching it.
564 inline bool CaveRendered::blows_up_flies(int x, int y, GdDirectionEnum dir) const {
565 return (gd_element_properties[get(x, y, dir)].flags & P_BLOWS_UP_FLIES) != 0;
568 /// returns true if the element is a counter-clockwise creature
569 inline bool CaveRendered::rotates_ccw(int x, int y) const {
570 return (gd_element_properties[get(x, y)].flags & P_CCW) != 0;
573 /// returns true if the element is a player (normal player, player glued, player with bomb)
574 bool CaveRendered::is_player(int x, int y) const {
575 return (gd_element_properties[get(x, y)].flags & P_PLAYER) != 0;
578 /// returns true if the element at (x,y)+dir is a player (normal player, player glued, player with bomb)
579 bool CaveRendered::is_player(int x, int y, GdDirectionEnum dir) const {
580 return (gd_element_properties[get(x, y, dir)].flags & P_PLAYER) != 0;
583 /// returns true if the element at (x,y)+dir can be hammered.
584 inline bool CaveRendered::can_be_hammered(int x, int y, GdDirectionEnum dir) const {
585 return (gd_element_properties[get(x, y, dir)].flags & P_CAN_BE_HAMMERED) != 0;
588 /// Returns true if the element at (x,y)+dir can be pushed.
589 /// @todo should be inlined.
590 bool CaveRendered::can_be_pushed(int x, int y, GdDirectionEnum dir) const {
591 return (gd_element_properties[get(x, y, dir)].flags & P_CAN_BE_PUSHED) != 0;
594 /// returns true if the element at (x,y) is the first animation stage of an explosion
595 inline bool CaveRendered::is_first_stage_of_explosion(int x, int y) const {
596 return (gd_element_properties[get(x, y)].flags & P_EXPLOSION_FIRST_STAGE) != 0;
599 /// returns true if the element sits on and is moved by the conveyor belt
600 inline bool CaveRendered::moved_by_conveyor_top(int x, int y, GdDirectionEnum dir) const {
601 return (gd_element_properties[get(x, y, dir)].flags & P_MOVED_BY_CONVEYOR_TOP) != 0;
604 /// returns true if the elements floats upwards, and is conveyed by the conveyor belt which is OVER it
605 inline bool CaveRendered::moved_by_conveyor_bottom(int x, int y, GdDirectionEnum dir) const {
606 return (gd_element_properties[get(x, y, dir)].flags & P_MOVED_BY_CONVEYOR_BOTTOM) != 0;
609 /// returns true if the element is a scanned one (needed by the engine)
610 inline bool CaveRendered::is_scanned(int x, int y) const {
611 return is_scanned_element(get(x, y));
614 /// returns true if the element is a scanned one (needed by the engine)
615 inline bool CaveRendered::is_scanned(int x, int y, GdDirectionEnum dir) const {
616 return is_scanned_element(get(x + gd_dx[dir], y + gd_dy[dir]));
619 /// Returns true if neighboring element is "e", or equivalent to "e".
620 /// Dirt is treated in a special way; eg. if is_like_element(O_DIRT) is
621 /// asked, an an O_DIRT2 is there, true is returned.
622 /// Also, lava is special; if is_like_element(O_SPACE) is asked, and
623 /// an O_LAVA is there, true is returned. This way any movement
624 /// is allowed by any creature and player into lava.
625 /// @param x The x coordinate on the map
626 /// @param y The y coordinate on the map
627 /// @param dir The direction to add to (x,y) and check the element at
628 /// @param e The element to compare (x,y)+dir to
629 /// @return True, if they are equivalent
630 bool CaveRendered::is_like_element(int x, int y, GdDirectionEnum dir, GdElementEnum e) const {
631 int examined = get(x, y, dir);
633 /* if it is a dirt-like, change to dirt, so equality will evaluate to true */
634 if (gd_element_properties[examined].flags & P_DIRT)
635 examined = O_DIRT;
636 if (gd_element_properties[e].flags & P_DIRT)
637 e = O_DIRT;
638 /* if the element on the map is a lava, it should be like space */
639 if (examined == O_LAVA)
640 examined = O_SPACE;
641 /* now they are "normalized", compare and return */
642 return e == examined;
645 /// Returns true if the neighboring element (x,y)+dir is a dirt or lava.
646 /// This is a shorthand function to check easily if there is space somewhere.
647 /// Any movement which is possible into space, must also be possible into lava.
648 /// Therefore 'if (map(x,y)==O_SPACE)' must not be used!
649 /// Lava absorbs everything going into it. Everything.
650 /// But it does not "pull" elements; only the things disappear which
651 /// _do_ go directly into it. So if the player steps into the lava,
652 /// he will die. If a dragonfly flies over it, it will not.
653 /// This behavior is implemented in the is_like_space and the store
654 /// functions. is_like_space returns true for the lava, too. The store
655 /// function ignores any store requests into the lava.
656 /// The player_eat_element function will also behave for lava as it does for space.
657 inline bool CaveRendered::is_like_space(int x, int y, GdDirectionEnum dir) const {
658 int e = get(x, y, dir);
659 return e == O_SPACE || e == O_LAVA;
662 /// Returns true, if element at (x,y)+dir is like dirt.
663 /// All dirt types must be equivalent; for example when allowing the player to
664 /// place a bomb in dirt, or when a nitro pack is bouncing on a piece of dirt
665 /// (without exploding).
666 /// Therefore 'if (map(x,y)==O_DIRT)' must not be used!
667 inline bool CaveRendered::is_like_dirt(int x, int y, GdDirectionEnum dir) const {
668 return (gd_element_properties[get(x, y, dir)].flags & P_DIRT) != 0;
672 /// Store an element at a given position; lava absorbs everything.
673 /// If there is a lava originally at the given position, sound is played, and
674 /// the map is NOT changed.
675 /// The element given is changed to its "scanned" state, if there is such.
676 inline void CaveRendered::store(int x, int y, GdElementEnum element, bool disable_particle) {
677 GdElementEnum &cell = map(x, y);
678 if (cell == O_LAVA) {
679 play_effect_of_element(O_LAVA, x, y);
680 return;
682 cell = scanned_pair(element);
686 /// Store an element to (x,y)+dir.
687 inline void CaveRendered::store(int x, int y, GdDirectionEnum dir, GdElementEnum element) {
688 store(x + gd_dx[dir], y + gd_dy[dir], element);
691 /// Store the element to (x,y)+dir, and store a space to (x,y).
692 inline void CaveRendered::move(int x, int y, GdDirectionEnum dir, GdElementEnum element) {
693 store(x, y, dir, element);
694 store(x, y, O_SPACE);
697 /// increment a cave element; can be used for elements which are one after the other, for example bladder1, bladder2, bladder3...
698 /// @todo to be removed
699 inline void CaveRendered::next(int x, int y) {
700 map(x, y) = GdElementEnum(map(x, y) + 1);
703 /// Remove th scanned "bit" from an element.
704 /// To be called only for scanned elements!!!
705 inline void CaveRendered::unscan(int x, int y) {
706 if (is_scanned(x, y))
707 map(x, y) = gd_element_properties[get(x, y)].pair;
711 /// Change the cell at (x,y) to a given explosion type.
712 /// Used by 3x3 explosion functions.
713 /// Take care of non explodable elements.
714 /// Take care of other special cases, like a voodoo dying,
715 /// and a nitro pack explosion triggered.
716 void CaveRendered::cell_explode(int x, int y, GdElementEnum explode_to) {
717 if (non_explodable(x, y))
718 return;
720 if (voodoo_any_hurt_kills_player && get(x, y) == O_VOODOO)
721 voodoo_touched = true;
723 if (get(x, y) == O_VOODOO && !voodoo_disappear_in_explosion)
724 /* voodoo turns into a time penalty */
725 store(x, y, O_TIME_PENALTY);
726 else if (get(x, y) == O_NITRO_PACK || get(x, y) == O_NITRO_PACK_F)
727 /* nitro pack inside an explosion - it is now triggered */
728 store(x, y, O_NITRO_PACK_EXPLODE);
729 else
730 /* for everything else. disable particle effects, as the explosion already generates */
731 store(x, y, explode_to, true /* disable particle effects */);
734 /// A creature at (x,y) explodes to a 3x3 square.
735 void CaveRendered::creature_explode(int x, int y, GdElementEnum explode_to) {
736 /* the processing of an explosion took pretty much time: processing 3x3=9 elements */
737 ckdelay_current += 1200;
738 sound_play(GD_S_EXPLOSION, x, y);
740 for (int yy = y - 1; yy <= y + 1; yy++)
741 for (int xx = x - 1; xx <= x + 1; xx++)
742 cell_explode(xx, yy, explode_to);
745 /// A nitro pack at (x,y) explodes to a 3x3 square.
746 void CaveRendered::nitro_explode(int x, int y) {
747 /* the processing of an explosion took pretty much time: processing 3x3=9 elements */
748 ckdelay_current += 1200;
749 sound_play(GD_S_NITRO_EXPLOSION, x, y);
751 for (int yy = y - 1; yy <= y + 1; yy++)
752 for (int xx = x - 1; xx <= x + 1; xx++)
753 cell_explode(xx, yy, O_NITRO_EXPL_1);
754 /* the current cell is explicitly changed into a nitro expl, as cell_explode changes it to a triggered nitro pack */
755 store(x, y, O_NITRO_EXPL_1);
758 /// A voodoo explodes, leaving a 3x3 steel and a time penalty behind.
759 void CaveRendered::voodoo_explode(int x, int y) {
760 if (voodoo_any_hurt_kills_player)
761 voodoo_touched = true;
763 /* the processing of an explosion took pretty much time: processing 3x3=9 elements */
764 ckdelay_current += 1000;
765 sound_play(GD_S_VOODOO_EXPLOSION, x, y);
767 /* voodoo explodes to 3x3 steel */
768 for (int yy = y - 1; yy <= y + 1; yy++)
769 for (int xx = x - 1; xx <= x + 1; xx++)
770 store(xx, yy, O_PRE_STEEL_1);
771 /* middle is a time penalty (which will be turned into a gravestone) */
772 store(x, y, O_TIME_PENALTY);
775 /// Explode cell at (x,y), but skip voodooo.
776 /// A bomb does not explode the voodoo, neither does the ghost.
777 /// This function checks these, and stores the new element given or not.
778 /// Destroying the voodoo is also controlled by the voodoo_disappear_in_explosion flag.
779 void CaveRendered::cell_explode_skip_voodoo(int x, int y, GdElementEnum expl) {
780 if (non_explodable(x, y))
781 return;
782 /* bomb does not explode voodoo */
783 if (!voodoo_disappear_in_explosion && get(x, y) == O_VOODOO)
784 return;
785 if (voodoo_any_hurt_kills_player && get(x, y) == O_VOODOO)
786 voodoo_touched = true;
787 store(x, y, expl);
790 /// An x shaped ghost explosion.
791 void CaveRendered::ghost_explode(int x, int y) {
792 /* the processing of an explosion took pretty much time: processing 5 elements */
793 ckdelay_current += 650;
794 sound_play(GD_S_GHOST_EXPLOSION, x, y);
796 cell_explode_skip_voodoo(x, y, O_GHOST_EXPL_1);
797 cell_explode_skip_voodoo(x - 1, y - 1, O_GHOST_EXPL_1);
798 cell_explode_skip_voodoo(x + 1, y + 1, O_GHOST_EXPL_1);
799 cell_explode_skip_voodoo(x - 1, y + 1, O_GHOST_EXPL_1);
800 cell_explode_skip_voodoo(x + 1, y - 1, O_GHOST_EXPL_1);
803 /// A + shaped bomb explosion.
804 void CaveRendered::bomb_explode(int x, int y) {
805 /* the processing of an explosion took pretty much time: processing 5 elements */
806 ckdelay_current += 650;
807 sound_play(GD_S_BOMB_EXPLOSION, x, y);
809 cell_explode_skip_voodoo(x, y, O_BOMB_EXPL_1);
810 cell_explode_skip_voodoo(x - 1, y, O_BOMB_EXPL_1);
811 cell_explode_skip_voodoo(x + 1, y, O_BOMB_EXPL_1);
812 cell_explode_skip_voodoo(x, y + 1, O_BOMB_EXPL_1);
813 cell_explode_skip_voodoo(x, y - 1, O_BOMB_EXPL_1);
816 /// Explode the thing at (x,y).
817 /// Checks the element, and selects the correct exploding type accordingly.
818 void CaveRendered::explode(int x, int y) {
819 /* if this remains false throughout the switch() below, particles will be added
820 * in the bottom line of the function. if there are particles already added
821 * somewhere, this is set to true. */
822 bool particles_added = false;
824 switch (get(x, y)) {
825 case O_GHOST:
826 ghost_explode(x, y);
827 break;
829 case O_BOMB_TICK_7:
830 bomb_explode(x, y);
831 break;
833 case O_VOODOO:
834 voodoo_explode(x, y);
835 break;
837 case O_NITRO_PACK:
838 case O_NITRO_PACK_F:
839 case O_NITRO_PACK_EXPLODE:
840 nitro_explode(x, y);
841 break;
843 case O_AMOEBA_2:
844 creature_explode(x, y, O_AMOEBA_2_EXPL_1);
845 break;
847 case O_FALLING_WALL_F:
848 creature_explode(x, y, O_EXPLODE_1);
849 break;
851 case O_ROCKET_1:
852 case O_ROCKET_2:
853 case O_ROCKET_3:
854 case O_ROCKET_4:
855 creature_explode(x, y, O_EXPLODE_1);
856 break;
858 case O_BUTTER_1:
859 case O_BUTTER_2:
860 case O_BUTTER_3:
861 case O_BUTTER_4:
862 add_particle_set(x, y, O_PRE_DIA_1);
863 particles_added = true;
864 creature_explode(x, y, butterfly_explode_to);
865 break;
867 case O_ALT_BUTTER_1:
868 case O_ALT_BUTTER_2:
869 case O_ALT_BUTTER_3:
870 case O_ALT_BUTTER_4:
871 add_particle_set(x, y, O_PRE_DIA_1);
872 particles_added = true;
873 creature_explode(x, y, alt_butterfly_explode_to);
874 break;
876 case O_FIREFLY_1:
877 case O_FIREFLY_2:
878 case O_FIREFLY_3:
879 case O_FIREFLY_4:
880 creature_explode(x, y, firefly_explode_to);
881 break;
883 case O_ALT_FIREFLY_1:
884 case O_ALT_FIREFLY_2:
885 case O_ALT_FIREFLY_3:
886 case O_ALT_FIREFLY_4:
887 creature_explode(x, y, alt_firefly_explode_to);
888 break;
890 case O_PLAYER:
891 case O_PLAYER_BOMB:
892 case O_PLAYER_GLUED:
893 case O_PLAYER_STIRRING:
894 case O_PLAYER_ROCKET_LAUNCHER:
895 case O_PLAYER_PNEUMATIC_LEFT:
896 case O_PLAYER_PNEUMATIC_RIGHT:
897 creature_explode(x, y, O_EXPLODE_1);
898 break;
900 case O_STONEFLY_1:
901 case O_STONEFLY_2:
902 case O_STONEFLY_3:
903 case O_STONEFLY_4:
904 creature_explode(x, y, stonefly_explode_to);
905 break;
907 case O_DRAGONFLY_1:
908 case O_DRAGONFLY_2:
909 case O_DRAGONFLY_3:
910 case O_DRAGONFLY_4:
911 creature_explode(x, y, dragonfly_explode_to);
912 break;
914 default:
915 g_assert_not_reached();
916 break;
919 if (!particles_added)
920 add_particle_set(x, y, O_EXPLODE_1);
923 /// Explode the element at (x,y)+dir.
924 /// A simple wrapper for the explode(int x, int y) function without
925 /// the dir parameter.
926 void CaveRendered::explode(int x, int y, GdDirectionEnum dir) {
927 explode(x + gd_dx[dir], y + gd_dy[dir]);
931 /// The player eats or activates the given element.
932 /// This function does all things that should happen when the
933 /// player eats something - increments score, plays sound etc.
934 /// This function is also used to activate switches, and to collect
935 /// keys.
936 /// It returns the remaining element, which is usually space;
937 /// might be some other thing. (example: DIRT for CLOCK)
938 /// This does NOT take snap_element into consideration.
939 /// @param element Element to eat
940 /// @param x The coordinate of player
941 /// @param y The coordinate of player
942 /// @param dir The direction the player is moving
943 /// @return remaining element
944 GdElementEnum CaveRendered::player_eat_element(GdElementEnum element, int x, int y, GdDirectionEnum dir) {
945 GdElementEnum was_element = element;
946 x += gd_dx[dir];
947 y += gd_dy[dir];
949 switch (element) {
950 case O_DIAMOND_KEY:
951 diamond_key_collected = true;
952 element = O_SPACE;
953 break;
955 /* KEYS AND DOORS */
956 case O_KEY_1:
957 ++key1;
958 element = O_SPACE;
959 break;
960 case O_KEY_2:
961 ++key2;
962 element = O_SPACE;
963 break;
964 case O_KEY_3:
965 ++key3;
966 element = O_SPACE;
967 break;
968 case O_DOOR_1:
969 if (key1 == 0)
970 element = O_NONE;
971 else {
972 --key1;
973 element = O_SPACE;
975 break;
976 case O_DOOR_2:
977 if (key2 == 0)
978 element = O_NONE;
979 else {
980 --key2;
981 element = O_SPACE;
983 break;
984 case O_DOOR_3:
985 if (key3 == 0)
986 element = O_NONE;
987 else {
988 --key3;
989 element = O_SPACE;
991 break;
992 /* SWITCHES */
993 case O_CREATURE_SWITCH: /* creatures change direction. */
994 creatures_backwards = !creatures_backwards;
995 break;
996 case O_EXPANDING_WALL_SWITCH: /* expanding wall change direction. */
997 expanding_wall_changed = !expanding_wall_changed;
998 break;
999 case O_BITER_SWITCH: /* biter change delay */
1000 biter_delay_frame++;
1001 if (biter_delay_frame == 4)
1002 biter_delay_frame = 0;
1003 break;
1004 case O_REPLICATOR_SWITCH: /* replicator on/off switch */
1005 replicators_active = !replicators_active;
1006 break;
1007 case O_CONVEYOR_SWITCH: /* conveyor belts on/off */
1008 conveyor_belts_active = !conveyor_belts_active;
1009 break;
1010 case O_CONVEYOR_DIR_SWITCH: /* conveyor belts switch direction */
1011 conveyor_belts_direction_changed = !conveyor_belts_direction_changed;
1012 break;
1013 case O_GRAVITY_SWITCH:
1014 if (gravity_switch_active && (dir == MV_LEFT || dir == MV_RIGHT || dir == MV_UP || dir == MV_DOWN)) {
1015 sound_play(GD_S_SWITCH_GRAVITY, x, y);
1016 gravity_will_change = gravity_change_time * timing_factor;
1017 gravity_next_direction = dir;
1018 gravity_switch_active = false;
1019 } else
1020 element = O_NONE;
1021 break;
1023 /* USUAL STUFF */
1024 case O_DIRT:
1025 case O_DIRT2:
1026 case O_DIRT_SLOPED_UP_RIGHT:
1027 case O_DIRT_SLOPED_UP_LEFT:
1028 case O_DIRT_SLOPED_DOWN_LEFT:
1029 case O_DIRT_SLOPED_DOWN_RIGHT:
1030 case O_DIRT_BALL:
1031 case O_DIRT_LOOSE:
1032 case O_STEEL_EATABLE:
1033 case O_BRICK_EATABLE:
1034 element = O_SPACE;
1035 break;
1037 case O_SPACE:
1038 case O_LAVA: /* player goes into lava, as if it was space */
1039 element = O_SPACE;
1040 break;
1042 case O_SWEET:
1043 sweet_eaten = true;
1044 element = O_SPACE;
1045 break;
1047 case O_PNEUMATIC_HAMMER:
1048 got_pneumatic_hammer = true;
1049 element = O_SPACE;
1050 break;
1052 case O_CLOCK:
1053 /* bonus time */
1054 time += time_bonus * timing_factor;
1055 if (time > max_time * timing_factor)
1056 time -= max_time * timing_factor;
1057 /* no space, rather a dirt remains there... */
1058 element = O_DIRT;
1059 break;
1061 case O_DIAMOND:
1062 case O_FLYING_DIAMOND:
1063 score += diamond_value;
1064 ++diamonds_collected;
1065 if (diamonds_needed == diamonds_collected) {
1066 gate_open = true;
1067 diamond_value = extra_diamond_value; /* extra is worth more points. */
1068 gate_open_flash = 1;
1069 sound_play(GD_S_CRACK, x, y);
1071 element = O_SPACE;
1072 break;
1073 case O_SKELETON:
1074 ++skeletons_collected;
1075 for (int i = 0; i < skeletons_worth_diamonds; i++)
1076 player_eat_element(O_DIAMOND, x, y, MV_STILL); /* as if player got a diamond */
1077 element = O_SPACE;
1078 break;
1079 case O_OUTBOX:
1080 case O_INVIS_OUTBOX:
1081 player_state = GD_PL_EXITED; /* player now exits the cave! */
1082 element = O_SPACE;
1083 break;
1085 default:
1086 /* non-eatable, does nothing */
1087 element = O_NONE;
1088 break;
1091 if (element != O_NONE) {
1092 play_eat_sound_of_element(was_element, x, y);
1093 play_eat_particle_of_element(was_element, x, y);
1095 return element;
1099 Process a crazy dream-style teleporter.
1100 Called from cave_iterate, for a player or a player_bomb.
1101 Player is standing at px, py, and trying to move in the direction player_move,
1102 where there is a teleporter.
1103 We check the whole cave, from (px+1,py), till we get back to (px,py) (by wrapping
1104 around). The first teleporter we find, and which is suitable, will be the destination.
1105 @param px The coordinate of the player which tries to move into the teleporter, x.
1106 @param py The coordinate of the player, y.
1107 @param player_move The direction he is moving into.
1108 @return True, if the player is teleported, false, if no suitable teleporter found.
1110 bool CaveRendered::do_teleporter(int px, int py, GdDirectionEnum player_move) {
1111 int tx = px;
1112 int ty = py;
1113 bool teleported = false;
1114 do {
1115 /* jump to next element; wrap around columns and rows. */
1116 tx++;
1117 if (tx >= w) {
1118 tx = 0;
1119 ty++;
1120 if (ty >= h)
1121 ty = 0;
1123 /* if we found a teleporter... */
1124 if (get(tx, ty) == O_TELEPORTER && is_like_space(tx, ty, player_move)) {
1125 store(tx, ty, player_move, get(px, py)); /* new player appears near teleporter found */
1126 store(px, py, O_SPACE); /* current player disappears */
1127 sound_play(GD_S_TELEPORTER, tx, ty);
1128 teleported = true; /* success */
1130 } while (!teleported && (tx != px || ty != py)); /* loop until we get back to original coordinates */
1131 return teleported;
1135 Try to push an element.
1136 Also does move the specified _element_, if possible.
1137 Up to the caller to move the _player_itself_, as the movement might be a snap, not a real movement.
1138 @return true if the push is possible.
1140 bool CaveRendered::do_push(int x, int y, GdDirectionEnum player_move, bool player_fire) {
1141 GdElementEnum what = get(x, y, player_move);
1142 GdDirection grav_compat; /* gravity for falling wall, bladder, ... */
1143 if (gravity_affects_all)
1144 grav_compat = gravity;
1145 else
1146 grav_compat = MV_DOWN;
1147 bool result = false;
1149 /* do a switch on what element is being pushed to determine probability. */
1150 switch (what) {
1151 case O_WAITING_STONE:
1152 case O_STONE:
1153 case O_NITRO_PACK:
1154 case O_CHASING_STONE:
1155 case O_MEGA_STONE:
1156 case O_FLYING_STONE:
1157 case O_NUT:
1158 /* pushing some kind of stone or nut*/
1159 /* directions possible: 90degrees cw or ccw to current gravity. */
1160 /* only push if player dir is orthogonal to gravity, ie. gravity down, pushing left&right possible */
1161 if (player_move == ccw_fourth[gravity] || player_move == cw_fourth[gravity]) {
1162 int prob;
1164 prob = 0;
1165 /* different probabilities for different elements. */
1166 /* remember that probabilities are 1/million, stored as integers! */
1167 switch (what) {
1168 case O_WAITING_STONE:
1169 prob = 1000000; /* waiting stones are light, can always push */
1170 break;
1171 case O_CHASING_STONE:
1172 if (sweet_eaten) /* chasing can be pushed if player is turbo */
1173 prob = 1000000; /* with p=1, always push */
1174 break;
1175 case O_MEGA_STONE:
1176 if (mega_stones_pushable_with_sweet && sweet_eaten) /* mega may(!) be pushed if player is turbo */
1177 prob = 1000000; /* p=1, always push */
1178 break;
1179 case O_STONE:
1180 case O_NUT:
1181 case O_FLYING_STONE:
1182 case O_NITRO_PACK:
1183 if (sweet_eaten)
1184 prob = pushing_stone_prob_sweet; /* probability with sweet */
1185 else
1186 prob = pushing_stone_prob; /* probability without sweet. */
1187 break;
1188 default:
1189 g_assert_not_reached();
1190 break;
1193 if (is_like_space(x, y, twice[player_move]) && random.rand_int_range(0, 1000000) < prob) {
1194 /* if decided that he is able to push, */
1195 play_effect_of_element(what, x + gd_dx[player_move], y + gd_dy[player_move]);
1196 /* if pushed a stone, it "bounces". all other elements are simply pushed. */
1197 if (what == O_STONE)
1198 store(x, y, twice[player_move], stone_bouncing_effect);
1199 else
1200 store(x, y, twice[player_move], what);
1201 result = true;
1204 break;
1206 case O_BLADDER:
1207 case O_BLADDER_1:
1208 case O_BLADDER_2:
1209 case O_BLADDER_3:
1210 case O_BLADDER_4:
1211 case O_BLADDER_5:
1212 case O_BLADDER_6:
1213 case O_BLADDER_7:
1214 case O_BLADDER_8:
1215 /* pushing a bladder. keep in mind that after pushing, we always get an O_BLADDER,
1216 * not an O_BLADDER_x. */
1217 /* there is no "delayed" state of a bladder, so we use store_dir_no_scanned! */
1219 /* first check: we cannot push a bladder "up" */
1220 if (player_move != opposite[grav_compat]) {
1221 /* pushing a bladder "down". p=player, o=bladder, 1, 2, 3=directions to check. */
1222 /* player moving in the direction of gravity. */
1223 /* p p g */
1224 /* 2o3 | | */
1225 /* 1 v v */
1226 if (player_move == grav_compat) {
1227 if (is_like_space(x, y, twice[player_move])) /* pushing bladder down */
1228 store(x, y, twice[player_move], O_BLADDER), result = true;
1229 else if (is_like_space(x, y, cw_eighth[grav_compat])) /* if no space to push down, maybe left (down-left to player) */
1230 /* left is "down, turned right (cw)" */
1231 store(x, y, cw_eighth[grav_compat], O_BLADDER), result = true;
1232 else if (is_like_space(x, y, ccw_eighth[grav_compat])) /* if not, maybe right (down-right to player) */
1233 store(x, y, ccw_eighth[grav_compat], O_BLADDER), result = true;
1235 /* pushing a bladder "left". p=player, o=bladder, 1, 2, 3=directions to check. */
1236 /* 3 g */
1237 /* 1op <-p | */
1238 /* 2 v */
1239 else if (player_move == cw_fourth[grav_compat]) {
1240 if (is_like_space(x, y, twice[cw_fourth[grav_compat]])) /* pushing it left */
1241 store(x, y, twice[cw_fourth[grav_compat]], O_BLADDER), result = true;
1242 else if (is_like_space(x, y, cw_eighth[grav_compat])) /* maybe down, and player will move left */
1243 store(x, y, cw_eighth[grav_compat], O_BLADDER), result = true;
1244 else if (is_like_space(x, y, cw_eighth[player_move])) /* maybe up, and player will move left */
1245 store(x, y, cw_eighth[player_move], O_BLADDER), result = true;
1247 /* pushing a bladder "right". p=player, o=bladder, 1, 2, 3=directions to check. */
1248 /* 3 g */
1249 /* po1 p-< | */
1250 /* 2 v */
1251 else if (player_move == ccw_fourth[grav_compat]) {
1252 if (is_like_space(x, y, twice[player_move])) /* pushing it right */
1253 store(x, y, twice[player_move], O_BLADDER), result = true;
1254 else if (is_like_space(x, y, ccw_eighth[grav_compat])) /* maybe down, and player will move right */
1255 store(x, y, ccw_eighth[grav_compat], O_BLADDER), result = true;
1256 else if (is_like_space(x, y, ccw_eighth[player_move])) /* maybe up, and player will move right */
1257 store(x, y, ccw_eighth[player_move], O_BLADDER), result = true;
1260 if (result)
1261 play_effect_of_element(O_BLADDER, x, y);
1263 break;
1265 case O_BOX:
1266 /* a box is only pushed with the fire pressed */
1267 if (player_fire) {
1268 /* but always with 100% probability */
1269 switch (player_move) {
1270 case MV_LEFT:
1271 case MV_RIGHT:
1272 case MV_UP:
1273 case MV_DOWN:
1274 /* pushing in some dir, two steps in that dir - is there space? */
1275 if (is_like_space(x, y, twice[player_move])) {
1276 /* yes, so push. */
1277 store(x, y, twice[player_move], O_BOX);
1278 result = true;
1279 sound_play(GD_S_BOX_PUSH, x, y);
1281 break;
1282 default:
1283 /* push in no other directions possible */
1284 break;
1287 break;
1289 /* pushing of other elements not possible */
1290 default:
1291 break;
1294 return result;
1297 /// clear these to no sound; and they will be set during iteration.
1298 void CaveRendered::clear_sounds() {
1299 sound1 = SoundWithPos(GD_S_NONE, 0, 0);
1300 sound2 = SoundWithPos(GD_S_NONE, 0, 0);
1301 sound3 = SoundWithPos(GD_S_NONE, 0, 0);
1304 /// Try to make an element start falling.
1305 /// When an element starts to fall, there is no particle effect. Only when bouncing and when rolling
1306 /// down sloped things. But sound there is.
1307 /// @param x The x coordinate of the element.
1308 /// @param y The y coordinate of the element.
1309 /// @param falling_direction The direction to start "falling" to.
1310 /// Down (=gravity) for a stone, Up (=opposite of gravity) for a flying stone, for example.
1311 /// @param falling_element The falling pair of the element (O_STONE -> O_STONE_F)
1312 void CaveRendered::do_start_fall(int x, int y, GdDirectionEnum falling_direction, GdElementEnum falling_element) {
1313 if (gravity_disabled)
1314 return;
1316 if (is_like_space(x, y, falling_direction)) { /* beginning to fall */
1317 play_effect_of_element(get(x, y), x, y, MV_STILL, false /* no particles */);
1318 move(x, y, falling_direction, falling_element);
1320 /* check if it is on a sloped element, and it can roll. */
1321 /* for example, sloped wall looks like: */
1322 /* /| */
1323 /* /_| */
1324 /* this is tagged as sloped up&left. */
1325 /* first check if the stone or diamond is coming from "up" (ie. opposite of gravity) */
1326 /* then check the direction to roll (left or right) */
1327 /* this way, gravity can also be pointing right, and the above slope will work as one would expect */
1328 else if (sloped(x, y, falling_direction, opposite[falling_direction])) { /* rolling down, if sitting on a sloped object */
1329 if (sloped(x, y, falling_direction, cw_fourth[falling_direction]) && is_like_space(x, y, cw_fourth[falling_direction]) && is_like_space(x, y, cw_eighth[falling_direction])) {
1330 /* rolling left? - keep in mind that ccw_fourth rotates gravity ccw, so here we use cw_fourth */
1331 play_effect_of_element(get(x, y), x, y, MV_STILL, false /* no particles */);
1332 move(x, y, cw_fourth[falling_direction], falling_element);
1333 } else if (sloped(x, y, falling_direction, ccw_fourth[falling_direction]) && is_like_space(x, y, ccw_fourth[falling_direction]) && is_like_space(x, y, ccw_eighth[falling_direction])) {
1334 /* rolling right? */
1335 play_effect_of_element(get(x, y), x, y, MV_STILL, false /* no particles */);
1336 move(x, y, ccw_fourth[falling_direction], falling_element);
1341 /// When the element at (x,y) is falling in the direction fall_dir,
1342 /// check if it crushes a voodoo below it. If yes, explode the voodoo,
1343 /// and return true. Otherwise return false.
1344 /// @return true if voodoo crushed.
1345 bool CaveRendered::do_fall_try_crush_voodoo(int x, int y, GdDirectionEnum fall_dir) {
1346 if (get(x, y, fall_dir) == O_VOODOO && voodoo_dies_by_stone) {
1347 /* this is a 1stB-style vodo. explodes by stone, collects diamonds */
1348 explode(x, y, fall_dir);
1349 return true;
1350 } else
1351 return false;
1354 /// When the element at (x,y) is falling in the direction fall_dir,
1355 /// check if the voodoo below it can eat it. If yes, the voodoo eats it.
1356 /// @return true if successful, false if voodoo does not eat the element.
1357 bool CaveRendered::do_fall_try_eat_voodoo(int x, int y, GdDirectionEnum fall_dir) {
1358 if (get(x, y, fall_dir) == O_VOODOO && voodoo_collects_diamonds) {
1359 /* this is a 1stB-style voodoo. explodes by stone, collects diamonds */
1360 player_eat_element(O_DIAMOND, x, y, fall_dir); /* as if player got diamond */
1361 store(x, y, O_SPACE); /* diamond disappears */
1362 return true;
1363 } else
1364 return false;
1367 /// Element at (x,y) is falling. Try to crack nut under it.
1368 /// If successful, nut is cracked, and the element is bounced (stops moving).
1369 /// @param fall_dir The direction the element is falling in.
1370 /// @param bouncing The element which it is converted to, if it has cracked a nut.
1371 /// @return True, if nut is cracked.
1372 bool CaveRendered::do_fall_try_crack_nut(int x, int y, GdDirectionEnum fall_dir, GdElementEnum bouncing) {
1373 if (get(x, y, fall_dir) == O_NUT || get(x, y, fall_dir) == O_NUT_F) {
1374 /* stones */
1375 store(x, y, bouncing);
1376 store(x, y, fall_dir, nut_turns_to_when_crushed);
1377 sound_play(GD_S_NUT_CRACK, x, y);
1378 return true;
1379 } else
1380 return false;
1383 /// For a falling element, try if a magic wall is under it.
1384 /// If yes, process element using the magic wall, and return true.
1385 /// @param fall_dir The direction the element is falling to.
1386 /// @param magic The element a magic wall turns it to.
1387 /// @return If The element is processed by the magic wall.
1388 bool CaveRendered::do_fall_try_magic(int x, int y, GdDirectionEnum fall_dir, GdElementEnum magic) {
1389 if (get(x, y, fall_dir) == O_MAGIC_WALL) {
1390 play_effect_of_element(O_DIAMOND, x, y, MV_STILL, false); /* always play diamond sound, but with no particle effect */
1391 if (magic_wall_state == GD_MW_DORMANT)
1392 magic_wall_state = GD_MW_ACTIVE;
1393 if (magic_wall_state == GD_MW_ACTIVE && is_like_space(x, y, twice[fall_dir])) {
1394 /* if magic wall active and place underneath, it turns element into anything the effect says to do. */
1395 store(x, y, twice[fall_dir], magic);
1397 store(x, y, O_SPACE); /* active or non-active or anything, element falling in will always disappear */
1398 if (magic_wall_breakscan && amoeba_state == GD_AM_AWAKE)
1399 convert_amoeba_this_frame = true;
1400 return true;
1401 } else
1402 return false;
1405 /// For a falling element, test if an explodable element is under it; if yes, explode it, and return yes.
1406 /// @return True, if element at (x,y)+fall_dir is exploded.
1407 bool CaveRendered::do_fall_try_crush(int x, int y, GdDirectionEnum fall_dir) {
1408 if (explodes_by_hit(x, y, fall_dir)) {
1409 explode(x, y, fall_dir);
1410 return true;
1411 } else
1412 return false;
1416 * For a falling element, try if a sloped element is under it.
1417 * Move element if possible, or bounce element.
1418 * If there are two directions possible for the element to roll to, left is preferred.
1419 * If no rolling is possible, it is converted to a bouncing element.
1420 * So this always "does something" with the element, and this should be the last
1421 * function to call when checking what happens to a falling element.
1423 void CaveRendered::do_fall_roll_or_stop(int x, int y, GdDirectionEnum fall_dir, GdElementEnum bouncing) {
1424 if (is_like_space(x, y, fall_dir)) { /* falling further */
1425 move(x, y, fall_dir, get(x, y));
1426 return;
1428 /* check if it is on a sloped element, and it can roll. */
1429 /* for example, sloped wall looks like: */
1430 /* /| */
1431 /* /_| */
1432 /* this is tagged as sloped up&left. */
1433 /* first check if the stone or diamond is coming from "up" (ie. opposite of gravity) */
1434 /* then check the direction to roll (left or right) */
1435 /* this way, gravity can also be pointing right, and the above slope will work as one would expect */
1436 if (sloped(x, y, fall_dir, opposite[fall_dir])) { /* sloped element, falling to left or right */
1437 if (sloped(x, y, fall_dir, cw_fourth[fall_dir]) && is_like_space(x, y, cw_eighth[fall_dir]) && is_like_space(x, y, cw_fourth[fall_dir])) {
1438 play_effect_of_element(get(x, y), x, y);
1439 move(x, y, cw_fourth[fall_dir], get(x, y)); /* try to roll left first - cw_fourth is because direction is down!!! left is clockwise to that */
1440 } else if (sloped(x, y, fall_dir, ccw_fourth[fall_dir]) && is_like_space(x, y, ccw_eighth[fall_dir]) && is_like_space(x, y, ccw_fourth[fall_dir])) {
1441 play_effect_of_element(get(x, y), x, y);
1442 move(x, y, ccw_fourth[fall_dir], get(x, y)); /* if not, try to roll right */
1443 } else {
1444 /* cannot roll in any direction, so it stops */
1445 play_effect_of_element(get(x, y), x, y);
1446 store(x, y, bouncing);
1448 return;
1451 /* any other element, stops */
1452 play_effect_of_element(get(x, y), x, y);
1453 store(x, y, bouncing);
1454 return;
1458 /// Process a cave - one iteration.
1459 /// @param player_move The direction the player moves to.
1460 /// @param player_fire True, if the fire button is pressed.
1461 /// @param suicide True, if the suicide button is pressed.
1462 /// @return A new GdDirectionEnum, which might be changed to not have diagonal movements. This is to make stored replays neater.
1463 GdDirectionEnum CaveRendered::iterate(GdDirectionEnum player_move, bool player_fire, bool suicide) {
1464 int ymin, ymax; /* for border scan */
1465 bool amoeba_found_enclosed, amoeba_2_found_enclosed; /* amoeba found to be enclosed. if not, this is cleared */
1466 int amoeba_count, amoeba_2_count; /* counting the number of amoebas. after scan, check if too much */
1467 bool inbox_toggle;
1468 bool start_signal;
1469 GdDirection grav_compat; /* gravity for falling wall, bladder, ... */
1470 /* directions for o_something_1, 2, 3 and 4 (creatures) */
1471 static GdDirection const creature_dir[] = { MV_LEFT, MV_UP, MV_RIGHT, MV_DOWN };
1472 static GdDirection const creature_chdir[] = { MV_RIGHT, MV_DOWN, MV_LEFT, MV_UP };
1473 int time_decrement_sec;
1474 GdElement biter_try[] = { O_DIRT, biter_eat, O_SPACE, O_STONE }; /* biters eating elements preference, they try to go in this order */
1476 clear_sounds();
1478 if (gravity_affects_all)
1479 grav_compat = gravity;
1480 else
1481 grav_compat = MV_DOWN;
1483 /* if diagonal movements not allowed, */
1484 /* horizontal movements have precedence. [BROADRIBB] */
1485 if (!diagonal_movements) {
1486 switch (player_move) {
1487 case MV_UP_RIGHT:
1488 case MV_DOWN_RIGHT:
1489 player_move = MV_RIGHT;
1490 break;
1491 case MV_UP_LEFT:
1492 case MV_DOWN_LEFT:
1493 player_move = MV_LEFT;
1494 break;
1495 default:
1496 /* no correction needed */
1497 break;
1501 /* increment this. if the scan routine comes across player, clears it (sets to zero). */
1502 if (player_seen_ago < 100)
1503 ++player_seen_ago;
1505 if (pneumatic_hammer_active_delay > 0)
1506 --pneumatic_hammer_active_delay;
1508 /* inboxes and outboxes flash with the rhythm of the game, not the display.
1509 * also, a player can be born only from an open, not from a steel-wall-like inbox. */
1510 inbox_flash_toggle = !inbox_flash_toggle;
1511 inbox_toggle = inbox_flash_toggle;
1513 if (gate_open_flash > 0)
1514 --gate_open_flash;
1516 /* score collected this frame */
1517 score = 0;
1519 /* to implement buggy bd1 amoeba+magic wall behaviour */
1520 convert_amoeba_this_frame = false;
1522 /* suicide only kills the active player */
1523 /* player_x, player_y was set by the previous iterate routine, or the cave setup. */
1524 /* we must check if there is a player or not - he may have exploded or something like that */
1525 if (suicide && player_state == GD_PL_LIVING && is_player(player_x, player_y))
1526 store(player_x, player_y, O_EXPLODE_1);
1528 /* check for walls reappearing */
1529 if (hammered_walls_reappear) {
1530 for (int y = 0; y < h; y++)
1531 for (int x = 0; x < w; x++) {
1532 /* timer for the cell > 0? */
1533 if (hammered_reappear(x, y) > 0) {
1534 /* decrease timer */
1535 hammered_reappear(x, y)--;
1536 /* check if it became zero */
1537 if (hammered_reappear(x, y) == 0) {
1538 store(x, y, O_BRICK);
1539 sound_play(GD_S_WALL_REAPPEAR, x, y);
1545 /* variables to check during the scan */
1546 amoeba_found_enclosed = true; /* will be set to false if any of the amoeba is found free. */
1547 amoeba_2_found_enclosed = true;
1548 amoeba_count = 0;
1549 amoeba_2_count = 0;
1550 ckdelay_current = 0;
1551 time_decrement_sec = 0;
1553 /* check whether to scan the first and last line */
1554 if (border_scan_first_and_last) {
1555 ymin = 0;
1556 ymax = h - 1;
1557 } else {
1558 ymin = 1;
1559 ymax = h - 2;
1561 /* the cave scan routine */
1562 for (int y = ymin; y <= ymax; y++)
1563 for (int x = 0; x < w; x++) {
1564 /* if we find a scanned element, change it to the normal one, and that's all. */
1565 /* this is required, for example for chasing stones, which have moved, always passing slime! */
1566 if (is_scanned(x, y)) {
1567 unscan(x, y);
1568 continue;
1571 /* add the ckdelay correction value for every element seen. */
1572 ckdelay_current += gd_element_properties[get(x, y)].ckdelay;
1574 switch (get(x, y)) {
1576 * P L A Y E R S
1578 case O_PLAYER:
1579 if (kill_player) {
1580 explode(x, y);
1581 break;
1583 player_seen_ago = 0;
1584 /* bd4 intermission caves have many players. so if one of them has exited,
1585 * do not change the flag anymore. so this if () is needed */
1586 if (player_state != GD_PL_EXITED)
1587 player_state = GD_PL_LIVING;
1589 /* check for pneumatic hammer things */
1590 /* 1) press fire, 2) have pneumatic hammer 4) space on left or right for hammer 5) stand on something */
1591 if (player_fire && got_pneumatic_hammer && is_like_space(x, y, player_move)
1592 && !is_like_space(x, y, MV_DOWN)) {
1593 if (player_move == MV_LEFT && can_be_hammered(x, y, MV_DOWN_LEFT)) {
1594 pneumatic_hammer_active_delay = pneumatic_hammer_frame;
1595 store(x, y, MV_LEFT, O_PNEUMATIC_ACTIVE_LEFT);
1596 store(x, y, O_PLAYER_PNEUMATIC_LEFT);
1597 break; /* finished. */
1599 if (player_move == MV_RIGHT && can_be_hammered(x, y, MV_DOWN_RIGHT)) {
1600 pneumatic_hammer_active_delay = pneumatic_hammer_frame;
1601 store(x, y, MV_RIGHT, O_PNEUMATIC_ACTIVE_RIGHT);
1602 store(x, y, O_PLAYER_PNEUMATIC_RIGHT);
1603 break; /* finished. */
1607 if (player_move != MV_STILL) {
1608 /* only do every check if he is not moving */
1609 GdElementEnum what = get(x, y, player_move);
1610 GdElementEnum remains = O_NONE; /* o_none in this variable will mean that there is no change. */
1611 bool push;
1613 /* if we are 'eating' a teleporter, and the function returns true (teleporting worked), break here */
1614 if (what == O_TELEPORTER && do_teleporter(x, y, player_move))
1615 break;
1617 /* try to push element; if successful, break */
1618 push = do_push(x, y, player_move, player_fire);
1619 if (push)
1620 remains = O_SPACE;
1621 else
1622 switch (what) {
1623 case O_BOMB:
1624 /* if its a bomb, remember he now has one. */
1625 /* we do not change the "remains" and "what" variables, so that part of the code will be ineffective */
1626 sound_play(GD_S_BOMB_COLLECT, x, y);
1627 store(x, y, player_move, O_SPACE);
1628 if (player_fire)
1629 store(x, y, O_PLAYER_BOMB);
1630 else
1631 move(x, y, player_move, O_PLAYER_BOMB);
1632 break;
1634 case O_ROCKET_LAUNCHER:
1635 /* if its a rocket launcher, remember he now has one. */
1636 /* we do not change the "remains" and "what" variables, so that part of the code will be ineffective */
1637 sound_play(GD_S_BOMB_COLLECT, x, y);
1638 store(x, y, player_move, O_SPACE);
1639 if (player_fire)
1640 store(x, y, O_PLAYER_ROCKET_LAUNCHER);
1641 else
1642 move(x, y, player_move, O_PLAYER_ROCKET_LAUNCHER);
1643 break;
1645 case O_POT:
1646 /* we do not change the "remains" and "what" variables, so that part of the code will be ineffective */
1647 if (!player_fire && !gravity_switch_active && skeletons_collected >= skeletons_needed_for_pot) {
1648 skeletons_collected -= skeletons_needed_for_pot;
1649 move(x, y, player_move, O_PLAYER_STIRRING);
1650 gravity_disabled = true;
1652 break;
1654 default:
1655 /* get element - process others. if cannot get, player_eat_element will return the same */
1656 remains = player_eat_element(what, x, y, player_move);
1657 break;
1660 /* if anything changed, apply the change. */
1661 if (remains != O_NONE) {
1662 /* if snapping anything and we have snapping explosions set. but these is not true for pushing. */
1663 if (remains == O_SPACE && player_fire && !push)
1664 remains = snap_element;
1665 if (remains != O_SPACE || player_fire)
1666 /* if any other element than space, player cannot move. also if pressing fire, will not move. */
1667 store(x, y, player_move, remains);
1668 else
1669 /* if space remains there, the player moves. */
1670 move(x, y, player_move, O_PLAYER);
1674 break;
1676 case O_PLAYER_BOMB:
1677 /* much simpler; cannot snap-push stones */
1678 if (kill_player) {
1679 explode(x, y);
1680 break;
1682 player_seen_ago = 0;
1683 /* bd4 intermission caves have many players. so if one of them has exited,
1684 * do not change the flag anymore. so this if () is needed */
1685 if (player_state != GD_PL_EXITED)
1686 player_state = GD_PL_LIVING;
1688 if (player_move != MV_STILL) { /* if the player does not move, nothing to do */
1689 GdElementEnum what = get(x, y, player_move);
1690 GdElementEnum remains = O_NONE;
1692 if (player_fire) {
1693 /* placing a bomb into empty space or dirt */
1694 if (is_like_space(x, y, player_move) || is_like_dirt(x, y, player_move)) {
1695 store(x, y, player_move, O_BOMB_TICK_1);
1696 /* placed bomb, he is normal player again */
1697 store(x, y, O_PLAYER);
1698 sound_play(GD_S_BOMB_PLACE, x, y);
1700 break;
1703 /* pushing and collecting */
1704 /* if we are 'eating' a teleporter, and the function returns true (teleporting worked), break here */
1705 if (what == O_TELEPORTER && do_teleporter(x, y, player_move))
1706 break;
1708 if (do_push(x, y, player_move, false)) /* player fire is false... */
1709 remains = O_SPACE;
1710 else {
1711 /* get element. if cannot get, player_eat_element will return the same */
1712 remains = player_eat_element(what, x, y, player_move);
1715 /* if something changed, OR there is space, move. */
1716 if (remains != O_NONE) {
1717 /* if anything changed, apply the change. */
1718 move(x, y, player_move, O_PLAYER_BOMB);
1721 break;
1723 case O_PLAYER_ROCKET_LAUNCHER:
1724 /* much simpler; cannot snap-push stones */
1725 if (kill_player) {
1726 explode(x, y);
1727 break;
1729 player_seen_ago = 0;
1730 /* bd4 intermission caves have many players. so if one of them has exited,
1731 * do not change the flag anymore. so this if () is needed */
1732 if (player_state != GD_PL_EXITED)
1733 player_state = GD_PL_LIVING;
1735 /* firing a rocket? */
1736 if (player_move != MV_STILL) { /* if the player does not move, nothing to do */
1737 GdElementEnum what = get(x, y, player_move);
1738 GdElementEnum remains = O_NONE;
1740 /* to fire a rocket, diagonal movement should not be allowed. so either x or y must be zero */
1741 /* placing a bomb into empty space */
1742 if (player_fire) {
1743 if (is_like_space(x, y, player_move)) {
1744 switch (player_move) {
1745 case MV_RIGHT:
1746 store(x, y, player_move, O_ROCKET_1);
1747 if (!infinite_rockets)
1748 store(x, y, O_PLAYER);
1749 break;
1750 case MV_UP:
1751 store(x, y, player_move, O_ROCKET_2);
1752 if (!infinite_rockets)
1753 store(x, y, O_PLAYER);
1754 break;
1755 case MV_LEFT:
1756 store(x, y, player_move, O_ROCKET_3);
1757 if (!infinite_rockets)
1758 store(x, y, O_PLAYER);
1759 break;
1760 case MV_DOWN:
1761 store(x, y, player_move, O_ROCKET_4);
1762 if (!infinite_rockets)
1763 store(x, y, O_PLAYER);
1764 break;
1765 default:
1766 /* cannot fire in other directions */
1767 break;
1769 sound_play(GD_S_BOMB_PLACE, x, y);
1771 /* a player with rocket launcher cannot snap elements, so stop here */
1772 break;
1775 /* pushing and collecting */
1776 /* if we are 'eating' a teleporter, and the function returns true (teleporting worked), break here */
1777 if (what == O_TELEPORTER && do_teleporter(x, y, player_move))
1778 break;
1780 if (do_push(x, y, player_move, false)) /* player fire is false... */
1781 remains = O_SPACE;
1782 else {
1783 /* get element. if cannot get, player_eat_element will return the same */
1784 remains = player_eat_element(what, x, y, player_move);
1787 /* if something changed, OR there is space, move. */
1788 if (remains != O_NONE) {
1789 /* if anything changed, apply the change. */
1790 move(x, y, player_move, O_PLAYER_ROCKET_LAUNCHER);
1793 break;
1795 case O_PLAYER_STIRRING:
1796 if (kill_player) {
1797 explode(x, y);
1798 break;
1800 sound_play(GD_S_STIRRING, x, y); /* stirring sound */
1801 player_seen_ago = 0;
1802 /* bd4 intermission caves have many players. so if one of them has exited,
1803 * do not change the flag anymore. so this if () is needed */
1804 if (player_state != GD_PL_EXITED)
1805 player_state = GD_PL_LIVING;
1806 if (player_fire) {
1807 /* player "exits" stirring the pot by pressing fire */
1808 gravity_disabled = false;
1809 store(x, y, O_PLAYER);
1810 gravity_switch_active = true;
1812 break;
1814 /* player holding pneumatic hammer */
1815 case O_PLAYER_PNEUMATIC_LEFT:
1816 case O_PLAYER_PNEUMATIC_RIGHT:
1817 /* usual player stuff */
1818 if (kill_player) {
1819 explode(x, y);
1820 break;
1822 player_seen_ago = 0;
1823 if (player_state != GD_PL_EXITED)
1824 player_state = GD_PL_LIVING;
1825 if (pneumatic_hammer_active_delay == 0) /* if hammering time is up, becomes a normal player again. */
1826 store(x, y, O_PLAYER);
1827 break;
1829 /* the active pneumatic hammer itself */
1830 case O_PNEUMATIC_ACTIVE_RIGHT:
1831 case O_PNEUMATIC_ACTIVE_LEFT:
1832 if (pneumatic_hammer_active_delay > 0)
1833 sound_play(GD_S_PNEUMATIC_HAMMER, x, y);
1834 if (pneumatic_hammer_active_delay == 0) {
1835 GdElementEnum new_elem;
1837 store(x, y, O_SPACE); /* pneumatic hammer element disappears */
1838 /* which is the new element which appears after that one is hammered? */
1839 new_elem = gd_element_get_hammered(get(x, y, MV_DOWN));
1840 /* if there is a new element, display it */
1841 /* O_NONE might be returned, for example if the element being hammered explodes during hammering (by a nearby explosion) */
1842 if (new_elem != O_NONE) {
1843 store(x, y, MV_DOWN, new_elem);
1845 /* and if walls reappear, remember it in array */
1846 /* y+1 is down */
1847 if (hammered_walls_reappear)
1848 hammered_reappear(x, (y + 1) % h) = hammered_wall_reappear_frame;
1851 break;
1855 * S T O N E S, D I A M O N D S
1857 case O_STONE: /* standing stone */
1858 do_start_fall(x, y, gravity, stone_falling_effect);
1859 break;
1861 case O_MEGA_STONE: /* standing mega_stone */
1862 do_start_fall(x, y, gravity, O_MEGA_STONE_F);
1863 break;
1865 case O_DIAMOND: /* standing diamond */
1866 do_start_fall(x, y, gravity, diamond_falling_effect);
1867 break;
1869 case O_NUT: /* standing nut */
1870 do_start_fall(x, y, gravity, O_NUT_F);
1871 break;
1873 case O_DIRT_BALL: /* standing dirt ball */
1874 do_start_fall(x, y, gravity, O_DIRT_BALL_F);
1875 break;
1877 case O_DIRT_LOOSE: /* standing loose dirt */
1878 do_start_fall(x, y, gravity, O_DIRT_LOOSE_F);
1879 break;
1881 case O_FLYING_STONE: /* standing stone */
1882 do_start_fall(x, y, opposite[gravity], O_FLYING_STONE_F);
1883 break;
1885 case O_FLYING_DIAMOND: /* standing diamond */
1886 do_start_fall(x, y, opposite[gravity], O_FLYING_DIAMOND_F);
1887 break;
1890 * F A L L I N G E L E M E N T S, F L Y I N G S T O N E S, D I A M O N D S
1892 case O_DIRT_BALL_F: /* falling dirt ball */
1893 if (!gravity_disabled)
1894 do_fall_roll_or_stop(x, y, gravity, O_DIRT_BALL);
1895 break;
1897 case O_DIRT_LOOSE_F: /* falling loose dirt */
1898 if (!gravity_disabled)
1899 do_fall_roll_or_stop(x, y, gravity, O_DIRT_LOOSE);
1900 break;
1902 case O_STONE_F: /* falling stone */
1903 if (!gravity_disabled) {
1904 if (do_fall_try_crush_voodoo(x, y, gravity)) break;
1905 if (do_fall_try_crack_nut(x, y, gravity, stone_bouncing_effect)) break;
1906 if (do_fall_try_magic(x, y, gravity, magic_stone_to)) break;
1907 if (do_fall_try_crush(x, y, gravity)) break;
1908 do_fall_roll_or_stop(x, y, gravity, stone_bouncing_effect);
1910 break;
1912 case O_MEGA_STONE_F: /* falling mega */
1913 if (!gravity_disabled) {
1914 if (do_fall_try_crush_voodoo(x, y, gravity)) break;
1915 if (do_fall_try_crack_nut(x, y, gravity, O_MEGA_STONE)) break;
1916 if (do_fall_try_magic(x, y, gravity, magic_mega_stone_to)) break;
1917 if (do_fall_try_crush(x, y, gravity)) break;
1918 do_fall_roll_or_stop(x, y, gravity, O_MEGA_STONE);
1920 break;
1922 case O_DIAMOND_F: /* falling diamond */
1923 if (!gravity_disabled) {
1924 if (do_fall_try_eat_voodoo(x, y, gravity)) break;
1925 if (do_fall_try_magic(x, y, gravity, magic_diamond_to)) break;
1926 if (do_fall_try_crush(x, y, gravity)) break;
1927 do_fall_roll_or_stop(x, y, gravity, diamond_bouncing_effect);
1929 break;
1931 case O_NUT_F: /* falling nut */
1932 if (!gravity_disabled) {
1933 if (do_fall_try_magic(x, y, gravity, magic_nut_to)) break;
1934 if (do_fall_try_crush(x, y, gravity)) break;
1935 do_fall_roll_or_stop(x, y, gravity, O_NUT);
1937 break;
1939 case O_FLYING_STONE_F: /* falling stone */
1940 if (!gravity_disabled) {
1941 GdDirectionEnum fall_dir = opposite[gravity];
1943 if (do_fall_try_crush_voodoo(x, y, fall_dir)) break;
1944 if (do_fall_try_crack_nut(x, y, fall_dir, O_FLYING_STONE)) break;
1945 if (do_fall_try_magic(x, y, fall_dir, magic_flying_stone_to)) break;
1946 if (do_fall_try_crush(x, y, fall_dir)) break;
1947 do_fall_roll_or_stop(x, y, fall_dir, O_FLYING_STONE);
1949 break;
1951 case O_FLYING_DIAMOND_F: /* falling diamond */
1952 if (!gravity_disabled) {
1953 GdDirectionEnum fall_dir = opposite[gravity];
1955 if (do_fall_try_eat_voodoo(x, y, fall_dir)) break;
1956 if (do_fall_try_magic(x, y, fall_dir, magic_flying_diamond_to)) break;
1957 if (do_fall_try_crush(x, y, fall_dir)) break;
1958 do_fall_roll_or_stop(x, y, fall_dir, O_FLYING_DIAMOND);
1960 break;
1964 * N I T R O P A C K
1966 case O_NITRO_PACK: /* standing nitro pack */
1967 do_start_fall(x, y, gravity, O_NITRO_PACK_F);
1968 break;
1970 case O_NITRO_PACK_F: /* falling nitro pack */
1971 if (!gravity_disabled) {
1972 if (is_like_space(x, y, gravity)) /* if space, falling further */
1973 move(x, y, gravity, O_NITRO_PACK_F);
1974 else if (do_fall_try_magic(x, y, gravity, magic_nitro_pack_to)) {
1975 /* try magic wall; if true, function did the work */
1976 } else if (is_like_dirt(x, y, gravity)) {
1977 /* falling on a dirt, it does NOT explode - just stops at its place. */
1978 store(x, y, O_NITRO_PACK);
1979 play_effect_of_element(O_NITRO_PACK, x, y);
1980 } else
1981 /* falling on any other element it explodes */
1982 explode(x, y);
1984 break;
1986 case O_NITRO_PACK_EXPLODE: /* a triggered nitro pack */
1987 explode(x, y);
1988 break;
1992 * C R E A T U R E S
1995 case O_COW_1:
1996 case O_COW_2:
1997 case O_COW_3:
1998 case O_COW_4:
1999 /* if cannot move in any direction, becomes an enclosed cow */
2000 if (!is_like_space(x, y, MV_UP) && !is_like_space(x, y, MV_DOWN)
2001 && !is_like_space(x, y, MV_LEFT) && !is_like_space(x, y, MV_RIGHT))
2002 store(x, y, O_COW_ENCLOSED_1);
2003 else {
2004 /* THIS IS THE CREATURE MOVE thing copied. */
2005 GdDirection const *creature_move;
2006 bool ccw = rotates_ccw(x, y); /* check if default is counterclockwise */
2007 GdElementEnum base; /* base element number (which is like O_***_1) */
2008 int dir, dirn, dirp; /* direction */
2010 base = O_COW_1;
2012 dir = get(x, y) - base; /* facing where */
2013 creature_move = creatures_backwards ? creature_chdir : creature_dir;
2015 /* now change direction if backwards */
2016 if (creatures_backwards)
2017 ccw = !ccw;
2019 if (ccw) {
2020 dirn = (dir + 3) & 3; /* fast turn */
2021 dirp = (dir + 1) & 3; /* slow turn */
2022 } else {
2023 dirn = (dir + 1) & 3; /* fast turn */
2024 dirp = (dir + 3) & 3; /* slow turn */
2027 if (is_like_space(x, y, creature_move[dirn]))
2028 move(x, y, creature_move[dirn], (GdElementEnum)(base + dirn)); /* turn and move to preferred dir */
2029 else if (is_like_space(x, y, creature_move[dir]))
2030 move(x, y, creature_move[dir], (GdElementEnum)(base + dir)); /* go on */
2031 else
2032 store(x, y, GdElementEnum(base + dirp)); /* turn in place if nothing else possible */
2034 break;
2035 /* enclosed cows wait some time before turning to a skeleton */
2036 case O_COW_ENCLOSED_1:
2037 case O_COW_ENCLOSED_2:
2038 case O_COW_ENCLOSED_3:
2039 case O_COW_ENCLOSED_4:
2040 case O_COW_ENCLOSED_5:
2041 case O_COW_ENCLOSED_6:
2042 if (is_like_space(x, y, MV_UP) || is_like_space(x, y, MV_LEFT) || is_like_space(x, y, MV_RIGHT) || is_like_space(x, y, MV_DOWN))
2043 store(x, y, O_COW_1);
2044 else
2045 next(x, y);
2046 break;
2047 case O_COW_ENCLOSED_7:
2048 if (is_like_space(x, y, MV_UP) || is_like_space(x, y, MV_LEFT) || is_like_space(x, y, MV_RIGHT) || is_like_space(x, y, MV_DOWN))
2049 store(x, y, O_COW_1);
2050 else
2051 store(x, y, O_SKELETON);
2052 break;
2054 case O_FIREFLY_1:
2055 case O_FIREFLY_2:
2056 case O_FIREFLY_3:
2057 case O_FIREFLY_4:
2058 case O_ALT_FIREFLY_1:
2059 case O_ALT_FIREFLY_2:
2060 case O_ALT_FIREFLY_3:
2061 case O_ALT_FIREFLY_4:
2062 case O_BUTTER_1:
2063 case O_BUTTER_2:
2064 case O_BUTTER_3:
2065 case O_BUTTER_4:
2066 case O_ALT_BUTTER_1:
2067 case O_ALT_BUTTER_2:
2068 case O_ALT_BUTTER_3:
2069 case O_ALT_BUTTER_4:
2070 case O_STONEFLY_1:
2071 case O_STONEFLY_2:
2072 case O_STONEFLY_3:
2073 case O_STONEFLY_4:
2074 /* check if touches a voodoo */
2075 if (get(x, y, MV_LEFT) == O_VOODOO || get(x, y, MV_RIGHT) == O_VOODOO || get(x, y, MV_UP) == O_VOODOO || get(x, y, MV_DOWN) == O_VOODOO)
2076 voodoo_touched = true;
2077 /* check if touches something bad and should explode (includes voodoo by the flags) */
2078 if (blows_up_flies(x, y, MV_DOWN) || blows_up_flies(x, y, MV_UP)
2079 || blows_up_flies(x, y, MV_LEFT) || blows_up_flies(x, y, MV_RIGHT))
2080 explode(x, y);
2081 /* otherwise move */
2082 else {
2083 GdDirection const *creature_move;
2084 bool ccw = rotates_ccw(x, y); /* check if default is counterclockwise */
2085 GdElementEnum base; /* base element number (which is like O_***_1) */
2086 int dir, dirn, dirp; /* direction */
2088 if (get(x, y) >= O_FIREFLY_1 && get(x, y) <= O_FIREFLY_4)
2089 base = O_FIREFLY_1;
2090 else if (get(x, y) >= O_BUTTER_1 && get(x, y) <= O_BUTTER_4)
2091 base = O_BUTTER_1;
2092 else if (get(x, y) >= O_STONEFLY_1 && get(x, y) <= O_STONEFLY_4)
2093 base = O_STONEFLY_1;
2094 else if (get(x, y) >= O_ALT_FIREFLY_1 && get(x, y) <= O_ALT_FIREFLY_4)
2095 base = O_ALT_FIREFLY_1;
2096 else if (get(x, y) >= O_ALT_BUTTER_1 && get(x, y) <= O_ALT_BUTTER_4)
2097 base = O_ALT_BUTTER_1;
2098 else
2099 g_assert_not_reached();
2101 dir = get(x, y) - base; /* facing where */
2102 creature_move = creatures_backwards ? creature_chdir : creature_dir;
2104 /* now change direction if backwards */
2105 if (creatures_backwards)
2106 ccw = !ccw;
2108 if (ccw) {
2109 dirn = (dir + 3) & 3; /* fast turn */
2110 dirp = (dir + 1) & 3; /* slow turn */
2111 } else {
2112 dirn = (dir + 1) & 3; /* fast turn */
2113 dirp = (dir + 3) & 3; /* slow turn */
2116 if (is_like_space(x, y, creature_move[dirn]))
2117 move(x, y, creature_move[dirn], (GdElementEnum)(base + dirn)); /* turn and move to preferred dir */
2118 else if (is_like_space(x, y, creature_move[dir]))
2119 move(x, y, creature_move[dir], (GdElementEnum)(base + dir)); /* go on */
2120 else
2121 store(x, y, GdElementEnum(base + dirp)); /* turn in place if nothing else possible */
2123 break;
2125 case O_WAITING_STONE:
2126 if (is_like_space(x, y, grav_compat)) { /* beginning to fall */
2127 /* it wakes up. */
2128 move(x, y, grav_compat, O_CHASING_STONE);
2129 } else if (sloped(x, y, grav_compat, opposite[grav_compat])) { /* rolling down a brick wall or a stone */
2130 if (sloped(x, y, grav_compat, cw_fourth[grav_compat]) && is_like_space(x, y, cw_fourth[grav_compat]) && is_like_space(x, y, cw_eighth[grav_compat])) {
2131 /* maybe rolling left - see case O_STONE to understand why we use cw_fourth here */
2132 move(x, y, cw_fourth[grav_compat], O_WAITING_STONE);
2133 } else if (sloped(x, y, grav_compat, ccw_fourth[grav_compat]) && is_like_space(x, y, ccw_fourth[grav_compat]) && is_like_space(x, y, ccw_eighth[grav_compat])) {
2134 /* or maybe right */
2135 move(x, y, ccw_fourth[grav_compat], O_WAITING_STONE);
2138 break;
2140 case O_CHASING_STONE: {
2141 int px = player_x_mem[0];
2142 int py = player_y_mem[0];
2143 bool horizontal = random.rand_boolean();
2144 bool dont_move = false;
2145 int i = 3;
2147 /* try to move... */
2148 while (1) {
2149 if (horizontal) { /*********************************/
2150 /* check for a horizontal movement */
2151 if (px == x) {
2152 /* if coordinates are the same */
2153 i -= 1;
2154 horizontal = !horizontal;
2155 if (i == 2)
2156 continue;
2157 } else {
2158 if (px > x && is_like_space(x, y, MV_RIGHT)) {
2159 move(x, y, MV_RIGHT, O_CHASING_STONE);
2160 dont_move = true;
2161 break;
2162 } else if (px < x && is_like_space(x, y, MV_LEFT)) {
2163 move(x, y, MV_LEFT, O_CHASING_STONE);
2164 dont_move = true;
2165 break;
2166 } else {
2167 i -= 2;
2168 if (i == 1) {
2169 horizontal = !horizontal;
2170 continue;
2174 } else { /********************************/
2175 /* check for a vertical movement */
2176 if (py == y) {
2177 /* if coordinates are the same */
2178 i -= 1;
2179 horizontal = !horizontal;
2180 if (i == 2)
2181 continue;
2182 } else {
2183 if (py > y && is_like_space(x, y, MV_DOWN)) {
2184 move(x, y, MV_DOWN, O_CHASING_STONE);
2185 dont_move = true;
2186 break;
2187 } else if (py < y && is_like_space(x, y, MV_UP)) {
2188 move(x, y, MV_UP, O_CHASING_STONE);
2189 dont_move = true;
2190 break;
2191 } else {
2192 i -= 2;
2193 if (i == 1) {
2194 horizontal = !horizontal;
2195 continue;
2200 if (i != 0)
2201 dont_move = true;
2202 break;
2205 /* if we should move in both directions, but can not move in any, stop. */
2206 if (!dont_move) {
2207 if (horizontal) { /* check for horizontal */
2208 if (x >= px) {
2209 if (is_like_space(x, y, MV_UP) && is_like_space(x, y, MV_UP_LEFT))
2210 move(x, y, MV_UP, O_CHASING_STONE);
2211 else if (is_like_space(x, y, MV_DOWN) && is_like_space(x, y, MV_DOWN_LEFT))
2212 move(x, y, MV_DOWN, O_CHASING_STONE);
2213 } else {
2214 if (is_like_space(x, y, MV_UP) && is_like_space(x, y, MV_UP_RIGHT))
2215 move(x, y, MV_UP, O_CHASING_STONE);
2216 else if (is_like_space(x, y, MV_DOWN) && is_like_space(x, y, MV_DOWN_RIGHT))
2217 move(x, y, MV_DOWN, O_CHASING_STONE);
2219 } else { /* check for vertical */
2220 if (y >= py) {
2221 if (is_like_space(x, y, MV_LEFT) && is_like_space(x, y, MV_UP_LEFT))
2222 move(x, y, MV_LEFT, O_CHASING_STONE);
2223 else if (is_like_space(x, y, MV_RIGHT) && is_like_space(x, y, MV_UP_RIGHT))
2224 move(x, y, MV_RIGHT, O_CHASING_STONE);
2225 } else {
2226 if (is_like_space(x, y, MV_LEFT) && is_like_space(x, y, MV_DOWN_LEFT))
2227 move(x, y, MV_LEFT, O_CHASING_STONE);
2228 else if (is_like_space(x, y, MV_RIGHT) && is_like_space(x, y, MV_DOWN_RIGHT))
2229 move(x, y, MV_RIGHT, O_CHASING_STONE);
2234 break;
2236 case O_REPLICATOR:
2237 if (replicators_wait_frame == 0 && replicators_active && !gravity_disabled) {
2238 /* only replicate, if space is under it. */
2239 /* do not replicate players! */
2240 /* also obeys gravity settings. */
2241 /* only replicate element if it is not a scanned one */
2242 if (is_like_space(x, y, gravity) && !is_player(x, y, opposite[gravity]) && !is_scanned(x, y, opposite[gravity])) {
2243 store(x, y, gravity, get(x, y, opposite[gravity]));
2244 sound_play(GD_S_REPLICATOR, x, y);
2247 break;
2249 case O_BITER_1:
2250 case O_BITER_2:
2251 case O_BITER_3:
2252 case O_BITER_4:
2253 if (biters_wait_frame == 0) {
2254 static GdDirectionEnum biter_move[] = { MV_UP, MV_RIGHT, MV_DOWN, MV_LEFT };
2255 int dir = get(x, y) - O_BITER_1; /* direction, last two bits 0..3 */
2256 int dirn = (dir + 3) & 3;
2257 int dirp = (dir + 1) & 3;
2258 GdElementEnum made_sound_of = O_NONE;
2260 unsigned int i;
2261 for (i = 0; i < G_N_ELEMENTS(biter_try); i++) {
2262 if (is_like_element(x, y, biter_move[dir], biter_try[i])) {
2263 GdDirectionEnum mdir = biter_move[dir];
2264 play_eat_particle_of_element(get(x, y, mdir), x + gd_dx[mdir], y + gd_dy[mdir]);
2265 move(x, y, mdir, GdElementEnum(O_BITER_1 + dir));
2266 if (biter_try[i] != O_SPACE)
2267 made_sound_of = O_BITER_1; /* sound of a biter eating */
2268 break;
2269 } else if (is_like_element(x, y, biter_move[dirn], biter_try[i])) {
2270 GdDirectionEnum mdir = biter_move[dirn];
2271 play_eat_particle_of_element(get(x, y, mdir), x + gd_dx[mdir], y + gd_dy[mdir]);
2272 move(x, y, mdir, GdElementEnum(O_BITER_1 + dirn));
2273 if (biter_try[i] != O_SPACE)
2274 made_sound_of = O_BITER_1; /* sound of a biter eating */
2275 break;
2276 } else if (is_like_element(x, y, biter_move[dirp], biter_try[i])) {
2277 GdDirectionEnum mdir = biter_move[dirp];
2278 play_eat_particle_of_element(get(x, y, mdir), x + gd_dx[mdir], y + gd_dy[mdir]);
2279 move(x, y, mdir, GdElementEnum(O_BITER_1 + dirp));
2280 if (biter_try[i] != O_SPACE)
2281 made_sound_of = O_BITER_1; /* sound of a biter eating */
2282 break;
2285 if (i == G_N_ELEMENTS(biter_try)) {
2286 /* i=number of elements in array: could not move, so just turn */
2287 store(x, y, GdElementEnum(O_BITER_1 + dirp));
2288 } else if (biter_try[i] == O_STONE) {
2289 /* if there was a stone there, where we moved... do not eat stones, just throw them back */
2290 store(x, y, O_STONE);
2291 made_sound_of = O_STONE;
2294 /* if biter did move, we had sound. play it. */
2295 if (made_sound_of != O_NONE)
2296 play_effect_of_element(made_sound_of, x, y);
2298 break;
2300 case O_DRAGONFLY_1:
2301 case O_DRAGONFLY_2:
2302 case O_DRAGONFLY_3:
2303 case O_DRAGONFLY_4:
2304 /* check if touches a voodoo */
2305 if (get(x, y, MV_LEFT) == O_VOODOO || get(x, y, MV_RIGHT) == O_VOODOO || get(x, y, MV_UP) == O_VOODOO || get(x, y, MV_DOWN) == O_VOODOO)
2306 voodoo_touched = true;
2307 /* check if touches something bad and should explode (includes voodoo by the flags) */
2308 if (blows_up_flies(x, y, MV_DOWN) || blows_up_flies(x, y, MV_UP)
2309 || blows_up_flies(x, y, MV_LEFT) || blows_up_flies(x, y, MV_RIGHT))
2310 explode(x, y);
2311 /* otherwise move */
2312 else {
2313 GdDirection const *creature_move;
2314 bool ccw = rotates_ccw(x, y); /* check if default is counterclockwise */
2315 GdElementEnum base = O_DRAGONFLY_1; /* base element number (which is like O_***_1) */
2316 int dir, dirn; /* direction */
2318 dir = get(x, y) - base; /* facing where */
2319 creature_move = creatures_backwards ? creature_chdir : creature_dir;
2321 /* now change direction if backwards */
2322 if (creatures_backwards)
2323 ccw = !ccw;
2325 if (ccw)
2326 dirn = (dir + 3) & 3; /* fast turn */
2327 else
2328 dirn = (dir + 1) & 3; /* fast turn */
2330 /* if can move forward, does so. */
2331 if (is_like_space(x, y, creature_move[dir]))
2332 move(x, y, creature_move[dir], GdElementEnum(base + dir));
2333 else
2334 /* otherwise turns 90 degrees in place. */
2335 store(x, y, GdElementEnum(base + dirn));
2337 break;
2340 case O_BLADDER:
2341 store(x, y, O_BLADDER_1);
2342 break;
2344 case O_BLADDER_1:
2345 case O_BLADDER_2:
2346 case O_BLADDER_3:
2347 case O_BLADDER_4:
2348 case O_BLADDER_5:
2349 case O_BLADDER_6:
2350 case O_BLADDER_7:
2351 case O_BLADDER_8:
2352 /* bladder with any delay state: try to convert to clock. */
2353 if (is_like_element(x, y, opposite[grav_compat], bladder_converts_by)
2354 || is_like_element(x, y, cw_fourth[grav_compat], bladder_converts_by)
2355 || is_like_element(x, y, ccw_fourth[grav_compat], bladder_converts_by)) {
2356 /* if touches the specified element, let it be a clock */
2357 store(x, y, O_PRE_CLOCK_1);
2358 play_effect_of_element(O_PRE_CLOCK_1, x, y); /* plays the bladder convert sound */
2359 } else {
2360 /* is space over the bladder? */
2361 if (is_like_space(x, y, opposite[grav_compat])) {
2362 if (get(x, y) == O_BLADDER_8) {
2363 /* if it is a bladder 8, really move up */
2364 move(x, y, opposite[grav_compat], O_BLADDER_1);
2365 play_effect_of_element(O_BLADDER, x, y);
2366 } else
2367 /* if smaller delay, just increase delay. */
2368 next(x, y);
2369 } else
2370 /* if not space, is something sloped over the bladder? */
2371 if (sloped_for_bladder(x, y, opposite[grav_compat]) && sloped(x, y, opposite[grav_compat], opposite[grav_compat])) {
2372 if (sloped(x, y, opposite[grav_compat], ccw_fourth[opposite[grav_compat]])
2373 && is_like_space(x, y, ccw_fourth[opposite[grav_compat]])
2374 && is_like_space(x, y, ccw_eighth[opposite[grav_compat]])) {
2375 /* rolling up, to left */
2376 if (get(x, y) == O_BLADDER_8) {
2377 /* if it is a bladder 8, really roll */
2378 move(x, y, ccw_fourth[opposite[grav_compat]], O_BLADDER_8);
2379 play_effect_of_element(O_BLADDER, x, y);
2380 } else
2381 /* if smaller delay, just increase delay. */
2382 next(x, y);
2383 } else if (sloped(x, y, opposite[grav_compat], cw_fourth[opposite[grav_compat]])
2384 && is_like_space(x, y, cw_fourth[opposite[grav_compat]])
2385 && is_like_space(x, y, cw_eighth[opposite[grav_compat]])) {
2386 /* rolling up, to left */
2387 if (get(x, y) == O_BLADDER_8) {
2388 /* if it is a bladder 8, really roll */
2389 move(x, y, cw_fourth[opposite[grav_compat]], O_BLADDER_8);
2390 play_effect_of_element(O_BLADDER, x, y);
2391 } else
2392 /* if smaller delay, just increase delay. */
2393 next(x, y);
2396 /* no space, no sloped thing over it - store bladder 1 and that is for now. */
2397 else
2398 store(x, y, O_BLADDER_1);
2400 break;
2402 case O_GHOST:
2403 if (blows_up_flies(x, y, MV_DOWN) || blows_up_flies(x, y, MV_UP)
2404 || blows_up_flies(x, y, MV_LEFT) || blows_up_flies(x, y, MV_RIGHT))
2405 explode(x, y);
2406 else {
2407 int i;
2409 /* the ghost is given four possibilities to move. */
2410 for (i = 0; i < 4; i++) {
2411 static GdDirectionEnum dirs[] = {MV_UP, MV_DOWN, MV_LEFT, MV_RIGHT};
2412 GdDirectionEnum random_dir;
2414 random_dir = dirs[random.rand_int_range(0, G_N_ELEMENTS(dirs))];
2415 if (is_like_space(x, y, random_dir)) {
2416 move(x, y, random_dir, O_GHOST);
2417 break; /* ghost did move -> exit loop */
2421 break;
2425 * A C T I V E E L E M E N T S
2428 case O_AMOEBA:
2429 if (hatched && amoeba_state == GD_AM_AWAKE)
2430 play_effect_of_element(O_AMOEBA, x, y);
2431 /* emulating BD1 amoeba+magic wall bug */
2432 if (convert_amoeba_this_frame && amoeba_found_enclosed) {
2433 store(x, y, amoeba_enclosed_effect);
2434 break;
2436 amoeba_count++;
2437 switch (amoeba_state) {
2438 case GD_AM_TOO_BIG:
2439 store(x, y, amoeba_too_big_effect);
2440 break;
2441 case GD_AM_ENCLOSED:
2442 store(x, y, amoeba_enclosed_effect);
2443 break;
2444 case GD_AM_SLEEPING:
2445 case GD_AM_AWAKE:
2446 /* if no amoeba found during THIS SCAN yet, which was able to grow, check this one. */
2447 if (amoeba_found_enclosed)
2448 /* if still found enclosed, check all four directions, if this one is able to grow. */
2449 if (amoeba_eats(x, y, MV_UP) || amoeba_eats(x, y, MV_DOWN)
2450 || amoeba_eats(x, y, MV_LEFT) || amoeba_eats(x, y, MV_RIGHT)) {
2451 amoeba_found_enclosed = false; /* not enclosed. this is a local (per scan) flag! */
2452 amoeba_state = GD_AM_AWAKE;
2455 /* if alive, check in which dir to grow (or not) */
2456 if (amoeba_state == GD_AM_AWAKE) {
2457 if (random.rand_int_range(0, 1000000) < amoeba_growth_prob) {
2458 switch (random.rand_int_range(0, 4)) { /* decided to grow, choose a random direction. */
2459 case 0: /* let this be up. numbers indifferent. */
2460 if (amoeba_eats(x, y, MV_UP))
2461 store(x, y, MV_UP, O_AMOEBA);
2462 break;
2463 case 1: /* down */
2464 if (amoeba_eats(x, y, MV_DOWN))
2465 store(x, y, MV_DOWN, O_AMOEBA);
2466 break;
2467 case 2: /* left */
2468 if (amoeba_eats(x, y, MV_LEFT))
2469 store(x, y, MV_LEFT, O_AMOEBA);
2470 break;
2471 case 3: /* right */
2472 if (amoeba_eats(x, y, MV_RIGHT))
2473 store(x, y, MV_RIGHT, O_AMOEBA);
2474 break;
2478 break;
2480 break;
2482 case O_AMOEBA_2:
2483 if (hatched && amoeba_2_state == GD_AM_AWAKE)
2484 play_effect_of_element(O_AMOEBA, x, y);
2485 amoeba_2_count++;
2486 /* check if it is touching an amoeba, and explosion is enabled */
2487 if (amoeba_2_explodes_by_amoeba
2488 && (is_like_element(x, y, MV_DOWN, O_AMOEBA) || is_like_element(x, y, MV_UP, O_AMOEBA)
2489 || is_like_element(x, y, MV_LEFT, O_AMOEBA) || is_like_element(x, y, MV_RIGHT, O_AMOEBA)))
2490 explode(x, y);
2491 else
2492 switch (amoeba_2_state) {
2493 case GD_AM_TOO_BIG:
2494 store(x, y, amoeba_2_too_big_effect);
2495 break;
2496 case GD_AM_ENCLOSED:
2497 store(x, y, amoeba_2_enclosed_effect);
2498 break;
2499 case GD_AM_SLEEPING:
2500 case GD_AM_AWAKE:
2501 /* if no amoeba found during THIS SCAN yet, which was able to grow, check this one. */
2502 if (amoeba_2_found_enclosed)
2503 if (amoeba_eats(x, y, MV_UP) || amoeba_eats(x, y, MV_DOWN)
2504 || amoeba_eats(x, y, MV_LEFT) || amoeba_eats(x, y, MV_RIGHT)) {
2505 amoeba_2_found_enclosed = false; /* not enclosed. this is a local (per scan) flag! */
2506 amoeba_2_state = GD_AM_AWAKE;
2509 if (amoeba_2_state == GD_AM_AWAKE) /* if it is alive, decide if it attempts to grow */
2510 if (random.rand_int_range(0, 1000000) < amoeba_2_growth_prob) {
2511 switch (random.rand_int_range(0, 4)) { /* decided to grow, choose a random direction. */
2512 case 0: /* let this be up. numbers indifferent. */
2513 if (amoeba_eats(x, y, MV_UP))
2514 store(x, y, MV_UP, O_AMOEBA_2);
2515 break;
2516 case 1: /* down */
2517 if (amoeba_eats(x, y, MV_DOWN))
2518 store(x, y, MV_DOWN, O_AMOEBA_2);
2519 break;
2520 case 2: /* left */
2521 if (amoeba_eats(x, y, MV_LEFT))
2522 store(x, y, MV_LEFT, O_AMOEBA_2);
2523 break;
2524 case 3: /* right */
2525 if (amoeba_eats(x, y, MV_RIGHT))
2526 store(x, y, MV_RIGHT, O_AMOEBA_2);
2527 break;
2530 break;
2532 break;
2534 case O_ACID:
2535 /* choose randomly, if it spreads */
2536 if (random.rand_int_range(0, 1000000) <= acid_spread_ratio) {
2537 /* the current one explodes */
2538 store(x, y, acid_turns_to);
2539 /* and if neighbours are eaten, put acid there. */
2540 if (is_like_element(x, y, MV_UP, acid_eats_this)) {
2541 store(x, y, MV_UP, O_ACID);
2542 play_effect_of_element(O_ACID, x, y);
2544 if (is_like_element(x, y, MV_DOWN, acid_eats_this)) {
2545 store(x, y, MV_DOWN, O_ACID);
2546 play_effect_of_element(O_ACID, x, y);
2548 if (is_like_element(x, y, MV_LEFT, acid_eats_this)) {
2549 store(x, y, MV_LEFT, O_ACID);
2550 play_effect_of_element(O_ACID, x, y);
2552 if (is_like_element(x, y, MV_RIGHT, acid_eats_this)) {
2553 store(x, y, MV_RIGHT, O_ACID);
2554 play_effect_of_element(O_ACID, x, y);
2557 break;
2559 case O_WATER:
2560 if (!water_does_not_flow_down && is_like_space(x, y, MV_DOWN)) /* emulating the odd behaviour in crdr */
2561 store(x, y, MV_DOWN, O_WATER_1);
2562 if (is_like_space(x, y, MV_UP))
2563 store(x, y, MV_UP, O_WATER_1);
2564 if (is_like_space(x, y, MV_LEFT))
2565 store(x, y, MV_LEFT, O_WATER_1);
2566 if (is_like_space(x, y, MV_RIGHT))
2567 store(x, y, MV_RIGHT, O_WATER_1);
2568 break;
2570 case O_WATER_16:
2571 store(x, y, O_WATER);
2572 break;
2574 case O_H_EXPANDING_WALL:
2575 case O_V_EXPANDING_WALL:
2576 case O_H_EXPANDING_STEEL_WALL:
2577 case O_V_EXPANDING_STEEL_WALL:
2578 /* checks first if direction is changed. */
2579 if (((get(x, y) == O_H_EXPANDING_WALL || get(x, y) == O_H_EXPANDING_STEEL_WALL) && !expanding_wall_changed)
2580 || ((get(x, y) == O_V_EXPANDING_WALL || get(x, y) == O_V_EXPANDING_STEEL_WALL) && expanding_wall_changed)) {
2581 if (is_like_space(x, y, MV_LEFT)) {
2582 store(x, y, MV_LEFT, get(x, y));
2583 play_effect_of_element(get(x, y), x, y, MV_LEFT);
2584 } else if (is_like_space(x, y, MV_RIGHT)) {
2585 store(x, y, MV_RIGHT, get(x, y));
2586 play_effect_of_element(get(x, y), x, y, MV_RIGHT);
2588 } else {
2589 if (is_like_space(x, y, MV_UP)) {
2590 store(x, y, MV_UP, get(x, y));
2591 play_effect_of_element(get(x, y), x, y, MV_UP);
2592 } else if (is_like_space(x, y, MV_DOWN)) {
2593 store(x, y, MV_DOWN, get(x, y));
2594 play_effect_of_element(get(x, y), x, y, MV_DOWN);
2597 break;
2599 case O_EXPANDING_WALL:
2600 case O_EXPANDING_STEEL_WALL:
2601 /* the wall which grows in all four directions. */
2602 if (is_like_space(x, y, MV_LEFT)) {
2603 store(x, y, MV_LEFT, get(x, y));
2604 play_effect_of_element(get(x, y), x, y, MV_LEFT);
2606 if (is_like_space(x, y, MV_RIGHT)) {
2607 store(x, y, MV_RIGHT, get(x, y));
2608 play_effect_of_element(get(x, y), x, y, MV_RIGHT);
2610 if (is_like_space(x, y, MV_UP)) {
2611 store(x, y, MV_UP, get(x, y));
2612 play_effect_of_element(get(x, y), x, y, MV_UP);
2614 if (is_like_space(x, y, MV_DOWN)) {
2615 store(x, y, MV_DOWN, get(x, y));
2616 play_effect_of_element(get(x, y), x, y, MV_DOWN);
2618 break;
2620 case O_SLIME:
2622 * unpredictable: g_rand_int
2623 * predictable: c64 predictable random generator.
2624 * for predictable, a random number is generated, whether or not it is even possible that the stone
2625 * will be able to pass.
2627 if (slime_predictable ? ((c64_rand.random()&slime_permeability_c64) == 0) : random.rand_int_range(0, 1000000) < slime_permeability) {
2628 GdDirectionEnum grav = gravity;
2629 GdDirectionEnum oppos = opposite[gravity];
2631 /* space under the slime? elements may pass from top to bottom then. */
2632 if (is_like_space(x, y, grav)) {
2633 if (get(x, y, oppos) == slime_eats_1) {
2634 store(x, y, grav, slime_converts_1); /* output a falling xy under */
2635 store(x, y, oppos, O_SPACE);
2636 play_effect_of_element(O_SLIME, x, y);
2637 } else if (get(x, y, oppos) == slime_eats_2) {
2638 store(x, y, grav, slime_converts_2);
2639 store(x, y, oppos, O_SPACE);
2640 play_effect_of_element(O_SLIME, x, y);
2641 } else if (get(x, y, oppos) == slime_eats_3) {
2642 store(x, y, grav, slime_converts_3);
2643 store(x, y, oppos, O_SPACE);
2644 play_effect_of_element(O_SLIME, x, y);
2645 } else if (get(x, y, oppos) == O_WAITING_STONE) { /* waiting stones pass without awakening */
2646 store(x, y, grav, O_WAITING_STONE);
2647 store(x, y, oppos, O_SPACE);
2648 play_effect_of_element(O_SLIME, x, y);
2649 } else if (get(x, y, oppos) == O_CHASING_STONE) { /* chasing stones pass */
2650 store(x, y, grav, O_CHASING_STONE);
2651 store(x, y, oppos, O_SPACE);
2652 play_effect_of_element(O_SLIME, x, y);
2654 } else
2655 /* or space over the slime? elements may pass from bottom to up then. */
2656 if (is_like_space(x, y, oppos)) {
2657 if (get(x, y, grav) == O_BLADDER) { /* bladders move UP the slime */
2658 store(x, y, grav, O_SPACE);
2659 store(x, y, oppos, O_BLADDER_1);
2660 play_effect_of_element(O_SLIME, x, y);
2661 } else if (get(x, y, grav) == O_FLYING_STONE) {
2662 store(x, y, grav, O_SPACE);
2663 store(x, y, oppos, O_FLYING_STONE_F);
2664 play_effect_of_element(O_SLIME, x, y);
2665 } else if (get(x, y, grav) == O_FLYING_DIAMOND) {
2666 store(x, y, grav, O_SPACE);
2667 store(x, y, oppos, O_FLYING_DIAMOND_F);
2668 play_effect_of_element(O_SLIME, x, y);
2672 break;
2674 case O_FALLING_WALL:
2675 if (is_like_space(x, y, grav_compat)) {
2676 /* try falling if space under. */
2677 int yy;
2678 for (yy = y + 1; yy < y + h; yy++)
2679 /* yy<y+h is to check everything OVER the wall - since caves wrap around !! */
2680 if (!is_like_space(x, yy))
2681 /* stop cycle when other than space */
2682 break;
2683 /* if scanning stopped by a player... start falling! */
2684 if (get(x, yy) == O_PLAYER || get(x, yy) == O_PLAYER_GLUED || get(x, yy) == O_PLAYER_BOMB) {
2685 move(x, y, grav_compat, O_FALLING_WALL_F);
2686 /* no sound when the falling wall starts falling! */
2689 break;
2691 case O_FALLING_WALL_F:
2692 if (is_player(x, y, grav_compat)) {
2693 /* if player under, it explodes - the falling wall, not the player! */
2694 explode(x, y);
2695 } else if (is_like_space(x, y, grav_compat)) {
2696 /* continue falling */
2697 move(x, y, grav_compat, O_FALLING_WALL_F);
2698 } else {
2699 /* stop falling */
2700 play_effect_of_element(O_FALLING_WALL_F, x, y);
2701 store(x, y, O_FALLING_WALL);
2703 break;
2706 * C O N V E Y O R B E L T S
2708 case O_CONVEYOR_RIGHT:
2709 case O_CONVEYOR_LEFT:
2710 /* only works if gravity is up or down!!! */
2711 /* first, check for gravity and running belts. */
2712 if (!gravity_disabled && conveyor_belts_active) {
2713 GdDirection const *dir;
2714 bool left;
2716 /* decide direction */
2717 left = get(x, y) != O_CONVEYOR_RIGHT;
2718 if (conveyor_belts_direction_changed)
2719 left = !left;
2720 dir = left ? ccw_eighth : cw_eighth;
2722 /* CHECK IF IT CONVEYS THE ELEMENT ABOVE IT */
2723 /* if gravity is normal, and the conveyor belt has something ABOVE which can be moved
2725 the gravity is up, so anything that should float now goes DOWN and touches the conveyor */
2726 if ((gravity == MV_DOWN && moved_by_conveyor_top(x, y, MV_UP))
2727 || (gravity == MV_UP && moved_by_conveyor_bottom(x, y, MV_UP))) {
2728 if (is_like_space(x, y, dir[MV_UP])) {
2729 store(x, y, dir[MV_UP], get(x, y, MV_UP)); /* move */
2730 store(x, y, MV_UP, O_SPACE); /* and place a space. */
2733 /* CHECK IF IT CONVEYS THE ELEMENT BELOW IT */
2734 if ((gravity == MV_UP && moved_by_conveyor_top(x, y, MV_DOWN))
2735 || (gravity == MV_DOWN && moved_by_conveyor_bottom(x, y, MV_DOWN))) {
2736 if (is_like_space(x, y, dir[MV_DOWN])) {
2737 store(x, y, dir[MV_DOWN], get(x, y, MV_DOWN)); /* move */
2738 store(x, y, MV_DOWN, O_SPACE); /* and clear. */
2742 break;
2744 case O_ROCKET_1:
2745 if (is_like_space(x, y, MV_RIGHT)) {
2746 move(x, y, MV_RIGHT, O_ROCKET_1);
2747 add_particle_set(x, y, O_ROCKET_1);
2748 } else
2749 explode(x, y);
2750 break;
2751 case O_ROCKET_2:
2752 if (is_like_space(x, y, MV_UP)) {
2753 move(x, y, MV_UP, O_ROCKET_2);
2754 add_particle_set(x, y, O_ROCKET_2);
2755 } else
2756 explode(x, y);
2757 break;
2758 case O_ROCKET_3:
2759 if (is_like_space(x, y, MV_LEFT)) {
2760 move(x, y, MV_LEFT, O_ROCKET_3);
2761 add_particle_set(x, y, O_ROCKET_3);
2762 } else
2763 explode(x, y);
2764 break;
2765 case O_ROCKET_4:
2766 if (is_like_space(x, y, MV_DOWN)) {
2767 move(x, y, MV_DOWN, O_ROCKET_4);
2768 add_particle_set(x, y, O_ROCKET_4);
2769 } else
2770 explode(x, y);
2771 break;
2774 * S I M P L E C H A N G I N G; E X P L O S I O N S
2776 case O_EXPLODE_3:
2777 store(x, y, explosion_3_effect);
2778 break;
2779 case O_EXPLODE_5:
2780 store(x, y, explosion_effect);
2781 break;
2782 case O_NUT_CRACK_4:
2783 store(x, y, O_DIAMOND);
2784 break;
2785 case O_PRE_DIA_5:
2786 store(x, y, diamond_birth_effect);
2787 break;
2788 case O_PRE_STONE_4:
2789 store(x, y, O_STONE);
2790 break;
2792 case O_NITRO_EXPL_4:
2793 store(x, y, nitro_explosion_effect);
2794 break;
2795 case O_BOMB_EXPL_4:
2796 store(x, y, bomb_explosion_effect);
2797 break;
2798 case O_AMOEBA_2_EXPL_4:
2799 store(x, y, amoeba_2_explosion_effect);
2800 break;
2802 case O_GHOST_EXPL_4: {
2803 static GdElementEnum ghost_explode[] = {
2804 O_SPACE, O_SPACE, O_DIRT, O_DIRT, O_CLOCK, O_CLOCK, O_PRE_OUTBOX,
2805 O_BOMB, O_BOMB, O_PLAYER, O_GHOST, O_BLADDER, O_DIAMOND, O_SWEET,
2806 O_WAITING_STONE, O_BITER_1
2809 store(x, y, ghost_explode[random.rand_int_range(0, G_N_ELEMENTS(ghost_explode))]);
2811 break;
2812 case O_PRE_STEEL_4:
2813 store(x, y, O_STEEL);
2814 break;
2815 case O_PRE_CLOCK_4:
2816 store(x, y, O_CLOCK);
2817 break;
2818 case O_BOMB_TICK_7:
2819 explode(x, y);
2820 break;
2822 case O_TRAPPED_DIAMOND:
2823 if (diamond_key_collected)
2824 store(x, y, O_DIAMOND);
2825 break;
2827 case O_PRE_OUTBOX:
2828 if (gate_open) /* if no more diamonds needed */
2829 store(x, y, O_OUTBOX); /* open outbox */
2830 break;
2831 case O_PRE_INVIS_OUTBOX:
2832 if (gate_open) /* if no more diamonds needed */
2833 store(x, y, O_INVIS_OUTBOX); /* open outbox. invisible one :P */
2834 break;
2835 case O_INBOX:
2836 player_seen_ago = 0;
2837 if (hatched && !inbox_toggle) /* if it is time of birth */
2838 store(x, y, O_PRE_PL_1);
2839 inbox_toggle = !inbox_toggle;
2840 break;
2841 case O_PRE_PL_1:
2842 player_seen_ago = 0;
2843 store(x, y, O_PRE_PL_2);
2844 break;
2845 case O_PRE_PL_2:
2846 player_seen_ago = 0;
2847 store(x, y, O_PRE_PL_3);
2848 break;
2849 case O_PRE_PL_3:
2850 player_seen_ago = 0;
2851 store(x, y, O_PLAYER);
2852 break;
2854 case O_PRE_DIA_1:
2855 case O_PRE_DIA_2:
2856 case O_PRE_DIA_3:
2857 case O_PRE_DIA_4:
2858 case O_PRE_STONE_1:
2859 case O_PRE_STONE_2:
2860 case O_PRE_STONE_3:
2861 case O_BOMB_TICK_1:
2862 case O_BOMB_TICK_2:
2863 case O_BOMB_TICK_3:
2864 case O_BOMB_TICK_4:
2865 case O_BOMB_TICK_5:
2866 case O_BOMB_TICK_6:
2867 case O_PRE_STEEL_1:
2868 case O_PRE_STEEL_2:
2869 case O_PRE_STEEL_3:
2870 case O_BOMB_EXPL_1:
2871 case O_BOMB_EXPL_2:
2872 case O_BOMB_EXPL_3:
2873 case O_NUT_CRACK_1:
2874 case O_NUT_CRACK_2:
2875 case O_NUT_CRACK_3:
2876 case O_GHOST_EXPL_1:
2877 case O_GHOST_EXPL_2:
2878 case O_GHOST_EXPL_3:
2879 case O_EXPLODE_1:
2880 case O_EXPLODE_2:
2881 /* explode 3 is 'effected' */
2882 case O_EXPLODE_4:
2883 case O_PRE_CLOCK_1:
2884 case O_PRE_CLOCK_2:
2885 case O_PRE_CLOCK_3:
2886 case O_NITRO_EXPL_1:
2887 case O_NITRO_EXPL_2:
2888 case O_NITRO_EXPL_3:
2889 case O_AMOEBA_2_EXPL_1:
2890 case O_AMOEBA_2_EXPL_2:
2891 case O_AMOEBA_2_EXPL_3:
2892 /* simply the next identifier */
2893 next(x, y);
2894 break;
2895 case O_WATER_1:
2896 case O_WATER_2:
2897 case O_WATER_3:
2898 case O_WATER_4:
2899 case O_WATER_5:
2900 case O_WATER_6:
2901 case O_WATER_7:
2902 case O_WATER_8:
2903 case O_WATER_9:
2904 case O_WATER_10:
2905 case O_WATER_11:
2906 case O_WATER_12:
2907 case O_WATER_13:
2908 case O_WATER_14:
2909 case O_WATER_15:
2910 sound_play(GD_S_WATER, x, y);
2911 /* simply advance the cell the next identifier */
2912 next(x, y);
2913 break;
2915 case O_BLADDER_SPENDER:
2916 if (is_like_space(x, y, opposite[grav_compat])) {
2917 store(x, y, opposite[grav_compat], O_BLADDER);
2918 store(x, y, O_PRE_STEEL_1);
2919 play_effect_of_element(O_BLADDER_SPENDER, x, y);
2921 break;
2923 case O_MAGIC_WALL:
2924 /* the magic wall effects are handled by the elements which fall
2925 * onto the wall. here only the sound is handled. */
2926 if (magic_wall_state == GD_MW_ACTIVE) {
2927 play_effect_of_element(O_MAGIC_WALL, x, y);
2929 break;
2931 case O_LAVA:
2932 /* lava is handled by the store() routine. here only the visual effect is added. */
2933 add_particle_set(x, y, O_LAVA);
2934 break;
2936 default:
2937 /* other inanimate elements that do nothing */
2938 break;
2941 /* after processing, check the current coordinate, if it became scanned. */
2942 /* the scanned bit can be cleared, as it will not be processed again. */
2943 /* and, it must be cleared, as it should not be scanned; for example, */
2944 /* if it is, a replicator will not replicate it! */
2945 unscan(x, y);
2948 /* POSTPROCESSING */
2950 /* forget "scanned" flags for objects. */
2951 /* also, check for time penalties. */
2952 /* these is something like an effect table, but we do not really use one. */
2953 for (int y = 0; y < h; y++)
2954 for (int x = 0; x < w; x++) {
2955 unscan(x, y);
2956 if (get(x, y) == O_TIME_PENALTY) {
2957 store(x, y, O_GRAVESTONE);
2958 time_decrement_sec += time_penalty; /* there is time penalty for destroying the voodoo */
2962 /* another scan-like routine: */
2963 /* short explosions (for example, in bd1) started with explode_2. */
2964 /* internally we use explode_1; and change it to explode_2 if needed. */
2965 if (short_explosions)
2966 for (int y = 0; y < h; y++)
2967 for (int x = 0; x < w; x++)
2968 if (is_first_stage_of_explosion(x, y)) {
2969 next(x, y); /* select next frame of explosion */
2970 unscan(x, y); /* forget scanned flag immediately */
2973 /* this loop finds the coordinates of the player. needed for scrolling and chasing stone.*/
2974 /* but we only do this, if a living player was found. otherwise "stay" at current coordinates. */
2975 if (player_state == GD_PL_LIVING) {
2976 if (active_is_first_found) {
2977 /* to be 1stb compatible, we do everything backwards. */
2978 for (int y = h - 1; y >= 0; y--)
2979 for (int x = w - 1; x >= 0; x--)
2980 if (is_player(x, y)) {
2981 /* here we remember the coordinates. */
2982 player_x = x;
2983 player_y = y;
2985 } else {
2986 /* as in the original: look for the last one */
2987 for (int y = 0; y < h; y++)
2988 for (int x = 0; x < w; x++)
2989 if (is_player(x, y)) {
2990 /* here we remember the coordinates. */
2991 player_x = x;
2992 player_y = y;
2996 /* record coordinates of player for chasing stone */
2997 for (unsigned i = 0; i < PlayerMemSize - 1; i++) {
2998 player_x_mem[i] = player_x_mem[i + 1];
2999 player_y_mem[i] = player_y_mem[i + 1];
3001 player_x_mem[PlayerMemSize - 1] = player_x;
3002 player_y_mem[PlayerMemSize - 1] = player_y;
3004 /* SCHEDULING */
3005 /* updates based on the calculated explosions and per element ckdelays. */
3006 update_scheduling();
3008 /* CAVE VARIABLES */
3010 /* PLAYER */
3011 if ((player_state == GD_PL_LIVING && player_seen_ago > 15) || kill_player) /* check if player is alive. */
3012 player_state = GD_PL_DIED;
3013 if (voodoo_touched) /* check if any voodoo exploded, and kill players the next scan if that happended. */
3014 kill_player = true;
3016 /* AMOEBA */
3017 if (amoeba_state == GD_AM_AWAKE) {
3018 /* check flags after evaluating. */
3019 if (amoeba_count >= amoeba_max_count)
3020 amoeba_state = GD_AM_TOO_BIG;
3021 if (amoeba_found_enclosed)
3022 amoeba_state = GD_AM_ENCLOSED;
3024 /* amoeba can also be turned into diamond by magic wall */
3025 if (magic_wall_stops_amoeba && magic_wall_state == GD_MW_ACTIVE)
3026 amoeba_state = GD_AM_ENCLOSED;
3027 /* AMOEBA 2 */
3028 if (amoeba_2_state == GD_AM_AWAKE) {
3029 /* check flags after evaluating. */
3030 if (amoeba_2_count >= amoeba_2_max_count)
3031 amoeba_2_state = GD_AM_TOO_BIG;
3032 if (amoeba_2_found_enclosed)
3033 amoeba_2_state = GD_AM_ENCLOSED;
3035 /* amoeba 2 can also be turned into diamond by magic wall */
3036 if (magic_wall_stops_amoeba && magic_wall_state == GD_MW_ACTIVE)
3037 amoeba_2_state = GD_AM_ENCLOSED;
3040 /* now check times. --------------------------- */
3041 /* decrement time if a voodoo was killed. */
3042 time -= time_decrement_sec * timing_factor;
3043 if (time < 0)
3044 time = 0;
3046 /* only decrement time when player is already born. */
3047 if (hatched) {
3048 int secondsbefore = time / timing_factor;
3049 time -= speed;
3050 if (time <= 0)
3051 time = 0;
3052 int secondsafter = time / timing_factor;
3053 if (secondsbefore != secondsafter)
3054 set_seconds_sound();
3056 /* for statistics */
3057 time_elapsed += speed;
3059 /* a gravity switch was activated; seconds counting down */
3060 if (gravity_will_change > 0) {
3061 gravity_will_change -= speed;
3062 if (gravity_will_change < 0)
3063 gravity_will_change = 0;
3065 if (gravity_will_change == 0) {
3066 gravity = gravity_next_direction;
3067 if (gravity_change_sound)
3068 sound_play(GD_S_GRAVITY_CHANGE, player_x, player_y); /* takes precedence over amoeba and magic wall sound */
3072 /* creatures direction automatically change */
3073 if (creatures_direction_will_change > 0) {
3074 creatures_direction_will_change -= speed;
3075 if (creatures_direction_will_change < 0)
3076 creatures_direction_will_change = 0;
3078 if (creatures_direction_will_change == 0) {
3079 if (creature_direction_auto_change_sound)
3080 sound_play(GD_S_SWITCH_CREATURES, player_x, player_y);
3081 creatures_backwards = !creatures_backwards;
3082 creatures_direction_will_change = creatures_direction_auto_change_time * timing_factor;
3086 /* magic wall; if active&wait or not wait for hatching */
3087 if (magic_wall_state == GD_MW_ACTIVE && (hatched || !magic_timer_wait_for_hatching)) {
3088 magic_wall_time -= speed;
3089 if (magic_wall_time < 0)
3090 magic_wall_time = 0;
3091 if (magic_wall_time == 0)
3092 magic_wall_state = GD_MW_EXPIRED;
3094 /* we may wait for hatching, when starting amoeba */
3095 if (amoeba_timer_started_immediately || (amoeba_state == GD_AM_AWAKE && (hatched || !amoeba_timer_wait_for_hatching))) {
3096 amoeba_time -= speed;
3097 if (amoeba_time < 0)
3098 amoeba_time = 0;
3099 if (amoeba_time == 0)
3100 amoeba_growth_prob = amoeba_fast_growth_prob;
3102 /* we may wait for hatching, when starting amoeba */
3103 if (amoeba_timer_started_immediately || (amoeba_2_state == GD_AM_AWAKE && (hatched || !amoeba_timer_wait_for_hatching))) {
3104 amoeba_2_time -= speed;
3105 if (amoeba_2_time < 0)
3106 amoeba_2_time = 0;
3107 if (amoeba_2_time == 0)
3108 amoeba_2_growth_prob = amoeba_2_fast_growth_prob;
3111 /* check for player hatching. */
3112 start_signal = false;
3113 /* if not the c64 scheduling, but the correct frametime is used, hatching delay should always be decremented. */
3114 /* otherwise, the if (millisecs...) condition below will set this. */
3115 if (scheduling == GD_SCHEDULING_MILLISECONDS) { /* NON-C64 scheduling */
3116 if (hatching_delay_frame > 0) {
3117 --hatching_delay_frame; /* for milliseconds-based, non-c64 schedulings, hatching delay means frames. */
3118 if (hatching_delay_frame == 0)
3119 start_signal = true;
3121 } else { /* C64 scheduling */
3122 if (hatching_delay_time > 0) {
3123 hatching_delay_time -= speed; /* for c64 schedulings, hatching delay means milliseconds. */
3124 if (hatching_delay_time <= 0) {
3125 hatching_delay_time = 0;
3126 start_signal = true;
3131 /* if decremented hatching, and it became zero: */
3132 if (start_signal) { /* THIS IS THE CAVE START SIGNAL */
3133 hatched = true; /* record that now the cave is in its normal state */
3135 count_diamonds(); /* if diamonds needed is below zero, we count the available diamonds now. */
3137 /* setup direction auto change */
3138 if (creatures_direction_auto_change_time) {
3139 creatures_direction_will_change = creatures_direction_auto_change_time * timing_factor;
3141 if (creatures_direction_auto_change_on_start)
3142 creatures_backwards = !creatures_backwards;
3145 if (player_state == GD_PL_NOT_YET)
3146 player_state = GD_PL_LIVING;
3148 sound_play(GD_S_CRACK, player_x, player_y);
3151 /* for biters */
3152 if (biters_wait_frame == 0)
3153 biters_wait_frame = biter_delay_frame;
3154 else
3155 --biters_wait_frame;
3156 /* replicators delay */
3157 if (replicators_wait_frame == 0)
3158 replicators_wait_frame = replicator_delay_frame;
3159 else
3160 --replicators_wait_frame;
3163 /* LAST THOUGTS */
3165 /* check if cave failed by timeout */
3166 if (player_state != GD_PL_TIMEOUT && time == 0) {
3167 player_state = GD_PL_TIMEOUT;
3168 clear_sounds();
3169 sound_play(GD_S_TIMEOUT, player_x, player_y);
3172 /* set these for drawing. */
3173 last_direction = player_move;
3174 /* here we remember last movements for animation. this is needed here, as animation
3175 is in sync with the game, not the keyboard directly. (for example, after exiting
3176 the cave, the player was "running" in the original, till bonus points were counted
3177 for remaining time and so on. */
3178 if (player_move == MV_LEFT || player_move == MV_UP_LEFT || player_move == MV_DOWN_LEFT)
3179 last_horizontal_direction = MV_LEFT;
3180 if (player_move == MV_RIGHT || player_move == MV_UP_RIGHT || player_move == MV_DOWN_RIGHT)
3181 last_horizontal_direction = MV_RIGHT;
3183 // return direction of movement of player, which might be changed if no diagonal movements.
3184 return player_move;