1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_INIT_PLAYER 1
27 #define DEBUG_PLAYER_ACTIONS 0
30 #define USE_NEW_AMOEBA_CODE FALSE
33 #define USE_QUICKSAND_BD_ROCK_BUGFIX 0
34 #define USE_QUICKSAND_IMPACT_BUGFIX 0
35 #define USE_DELAYED_GFX_REDRAW 0
36 #define USE_NEW_PLAYER_ASSIGNMENTS 1
38 #if USE_DELAYED_GFX_REDRAW
39 #define TEST_DrawLevelField(x, y) \
40 GfxRedraw[x][y] |= GFX_REDRAW_TILE
41 #define TEST_DrawLevelFieldCrumbled(x, y) \
42 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED
43 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
44 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS
45 #define TEST_DrawTwinkleOnField(x, y) \
46 GfxRedraw[x][y] |= GFX_REDRAW_TILE_TWINKLED
48 #define TEST_DrawLevelField(x, y) \
50 #define TEST_DrawLevelFieldCrumbled(x, y) \
51 DrawLevelFieldCrumbled(x, y)
52 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
53 DrawLevelFieldCrumbledNeighbours(x, y)
54 #define TEST_DrawTwinkleOnField(x, y) \
55 DrawTwinkleOnField(x, y)
65 #define MP_NO_ACTION 0
68 #define MP_DONT_RUN_INTO (MP_MOVING | MP_ACTION)
72 #define SCROLL_GO_ON 1
74 // for Bang()/Explode()
75 #define EX_PHASE_START 0
76 #define EX_TYPE_NONE 0
77 #define EX_TYPE_NORMAL (1 << 0)
78 #define EX_TYPE_CENTER (1 << 1)
79 #define EX_TYPE_BORDER (1 << 2)
80 #define EX_TYPE_CROSS (1 << 3)
81 #define EX_TYPE_DYNA (1 << 4)
82 #define EX_TYPE_SINGLE_TILE (EX_TYPE_CENTER | EX_TYPE_BORDER)
84 #define PANEL_OFF() (game.panel.active == FALSE)
85 #define PANEL_DEACTIVATED(p) ((p)->x < 0 || (p)->y < 0 || PANEL_OFF())
86 #define PANEL_XPOS(p) (DX + ALIGNED_TEXT_XPOS(p))
87 #define PANEL_YPOS(p) (DY + ALIGNED_TEXT_YPOS(p))
89 // game panel display and control definitions
90 #define GAME_PANEL_LEVEL_NUMBER 0
91 #define GAME_PANEL_GEMS 1
92 #define GAME_PANEL_INVENTORY_COUNT 2
93 #define GAME_PANEL_INVENTORY_FIRST_1 3
94 #define GAME_PANEL_INVENTORY_FIRST_2 4
95 #define GAME_PANEL_INVENTORY_FIRST_3 5
96 #define GAME_PANEL_INVENTORY_FIRST_4 6
97 #define GAME_PANEL_INVENTORY_FIRST_5 7
98 #define GAME_PANEL_INVENTORY_FIRST_6 8
99 #define GAME_PANEL_INVENTORY_FIRST_7 9
100 #define GAME_PANEL_INVENTORY_FIRST_8 10
101 #define GAME_PANEL_INVENTORY_LAST_1 11
102 #define GAME_PANEL_INVENTORY_LAST_2 12
103 #define GAME_PANEL_INVENTORY_LAST_3 13
104 #define GAME_PANEL_INVENTORY_LAST_4 14
105 #define GAME_PANEL_INVENTORY_LAST_5 15
106 #define GAME_PANEL_INVENTORY_LAST_6 16
107 #define GAME_PANEL_INVENTORY_LAST_7 17
108 #define GAME_PANEL_INVENTORY_LAST_8 18
109 #define GAME_PANEL_KEY_1 19
110 #define GAME_PANEL_KEY_2 20
111 #define GAME_PANEL_KEY_3 21
112 #define GAME_PANEL_KEY_4 22
113 #define GAME_PANEL_KEY_5 23
114 #define GAME_PANEL_KEY_6 24
115 #define GAME_PANEL_KEY_7 25
116 #define GAME_PANEL_KEY_8 26
117 #define GAME_PANEL_KEY_WHITE 27
118 #define GAME_PANEL_KEY_WHITE_COUNT 28
119 #define GAME_PANEL_SCORE 29
120 #define GAME_PANEL_HIGHSCORE 30
121 #define GAME_PANEL_TIME 31
122 #define GAME_PANEL_TIME_HH 32
123 #define GAME_PANEL_TIME_MM 33
124 #define GAME_PANEL_TIME_SS 34
125 #define GAME_PANEL_TIME_ANIM 35
126 #define GAME_PANEL_HEALTH 36
127 #define GAME_PANEL_HEALTH_ANIM 37
128 #define GAME_PANEL_FRAME 38
129 #define GAME_PANEL_SHIELD_NORMAL 39
130 #define GAME_PANEL_SHIELD_NORMAL_TIME 40
131 #define GAME_PANEL_SHIELD_DEADLY 41
132 #define GAME_PANEL_SHIELD_DEADLY_TIME 42
133 #define GAME_PANEL_EXIT 43
134 #define GAME_PANEL_EMC_MAGIC_BALL 44
135 #define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 45
136 #define GAME_PANEL_LIGHT_SWITCH 46
137 #define GAME_PANEL_LIGHT_SWITCH_TIME 47
138 #define GAME_PANEL_TIMEGATE_SWITCH 48
139 #define GAME_PANEL_TIMEGATE_SWITCH_TIME 49
140 #define GAME_PANEL_SWITCHGATE_SWITCH 50
141 #define GAME_PANEL_EMC_LENSES 51
142 #define GAME_PANEL_EMC_LENSES_TIME 52
143 #define GAME_PANEL_EMC_MAGNIFIER 53
144 #define GAME_PANEL_EMC_MAGNIFIER_TIME 54
145 #define GAME_PANEL_BALLOON_SWITCH 55
146 #define GAME_PANEL_DYNABOMB_NUMBER 56
147 #define GAME_PANEL_DYNABOMB_SIZE 57
148 #define GAME_PANEL_DYNABOMB_POWER 58
149 #define GAME_PANEL_PENGUINS 59
150 #define GAME_PANEL_SOKOBAN_OBJECTS 60
151 #define GAME_PANEL_SOKOBAN_FIELDS 61
152 #define GAME_PANEL_ROBOT_WHEEL 62
153 #define GAME_PANEL_CONVEYOR_BELT_1 63
154 #define GAME_PANEL_CONVEYOR_BELT_2 64
155 #define GAME_PANEL_CONVEYOR_BELT_3 65
156 #define GAME_PANEL_CONVEYOR_BELT_4 66
157 #define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 67
158 #define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 68
159 #define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 69
160 #define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 70
161 #define GAME_PANEL_MAGIC_WALL 71
162 #define GAME_PANEL_MAGIC_WALL_TIME 72
163 #define GAME_PANEL_GRAVITY_STATE 73
164 #define GAME_PANEL_GRAPHIC_1 74
165 #define GAME_PANEL_GRAPHIC_2 75
166 #define GAME_PANEL_GRAPHIC_3 76
167 #define GAME_PANEL_GRAPHIC_4 77
168 #define GAME_PANEL_GRAPHIC_5 78
169 #define GAME_PANEL_GRAPHIC_6 79
170 #define GAME_PANEL_GRAPHIC_7 80
171 #define GAME_PANEL_GRAPHIC_8 81
172 #define GAME_PANEL_ELEMENT_1 82
173 #define GAME_PANEL_ELEMENT_2 83
174 #define GAME_PANEL_ELEMENT_3 84
175 #define GAME_PANEL_ELEMENT_4 85
176 #define GAME_PANEL_ELEMENT_5 86
177 #define GAME_PANEL_ELEMENT_6 87
178 #define GAME_PANEL_ELEMENT_7 88
179 #define GAME_PANEL_ELEMENT_8 89
180 #define GAME_PANEL_ELEMENT_COUNT_1 90
181 #define GAME_PANEL_ELEMENT_COUNT_2 91
182 #define GAME_PANEL_ELEMENT_COUNT_3 92
183 #define GAME_PANEL_ELEMENT_COUNT_4 93
184 #define GAME_PANEL_ELEMENT_COUNT_5 94
185 #define GAME_PANEL_ELEMENT_COUNT_6 95
186 #define GAME_PANEL_ELEMENT_COUNT_7 96
187 #define GAME_PANEL_ELEMENT_COUNT_8 97
188 #define GAME_PANEL_CE_SCORE_1 98
189 #define GAME_PANEL_CE_SCORE_2 99
190 #define GAME_PANEL_CE_SCORE_3 100
191 #define GAME_PANEL_CE_SCORE_4 101
192 #define GAME_PANEL_CE_SCORE_5 102
193 #define GAME_PANEL_CE_SCORE_6 103
194 #define GAME_PANEL_CE_SCORE_7 104
195 #define GAME_PANEL_CE_SCORE_8 105
196 #define GAME_PANEL_CE_SCORE_1_ELEMENT 106
197 #define GAME_PANEL_CE_SCORE_2_ELEMENT 107
198 #define GAME_PANEL_CE_SCORE_3_ELEMENT 108
199 #define GAME_PANEL_CE_SCORE_4_ELEMENT 109
200 #define GAME_PANEL_CE_SCORE_5_ELEMENT 110
201 #define GAME_PANEL_CE_SCORE_6_ELEMENT 111
202 #define GAME_PANEL_CE_SCORE_7_ELEMENT 112
203 #define GAME_PANEL_CE_SCORE_8_ELEMENT 113
204 #define GAME_PANEL_PLAYER_NAME 114
205 #define GAME_PANEL_LEVEL_NAME 115
206 #define GAME_PANEL_LEVEL_AUTHOR 116
208 #define NUM_GAME_PANEL_CONTROLS 117
210 struct GamePanelOrderInfo
216 static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
218 struct GamePanelControlInfo
222 struct TextPosInfo *pos;
225 int graphic, graphic_active;
227 int value, last_value;
228 int frame, last_frame;
233 static struct GamePanelControlInfo game_panel_controls[] =
236 GAME_PANEL_LEVEL_NUMBER,
237 &game.panel.level_number,
246 GAME_PANEL_INVENTORY_COUNT,
247 &game.panel.inventory_count,
251 GAME_PANEL_INVENTORY_FIRST_1,
252 &game.panel.inventory_first[0],
256 GAME_PANEL_INVENTORY_FIRST_2,
257 &game.panel.inventory_first[1],
261 GAME_PANEL_INVENTORY_FIRST_3,
262 &game.panel.inventory_first[2],
266 GAME_PANEL_INVENTORY_FIRST_4,
267 &game.panel.inventory_first[3],
271 GAME_PANEL_INVENTORY_FIRST_5,
272 &game.panel.inventory_first[4],
276 GAME_PANEL_INVENTORY_FIRST_6,
277 &game.panel.inventory_first[5],
281 GAME_PANEL_INVENTORY_FIRST_7,
282 &game.panel.inventory_first[6],
286 GAME_PANEL_INVENTORY_FIRST_8,
287 &game.panel.inventory_first[7],
291 GAME_PANEL_INVENTORY_LAST_1,
292 &game.panel.inventory_last[0],
296 GAME_PANEL_INVENTORY_LAST_2,
297 &game.panel.inventory_last[1],
301 GAME_PANEL_INVENTORY_LAST_3,
302 &game.panel.inventory_last[2],
306 GAME_PANEL_INVENTORY_LAST_4,
307 &game.panel.inventory_last[3],
311 GAME_PANEL_INVENTORY_LAST_5,
312 &game.panel.inventory_last[4],
316 GAME_PANEL_INVENTORY_LAST_6,
317 &game.panel.inventory_last[5],
321 GAME_PANEL_INVENTORY_LAST_7,
322 &game.panel.inventory_last[6],
326 GAME_PANEL_INVENTORY_LAST_8,
327 &game.panel.inventory_last[7],
371 GAME_PANEL_KEY_WHITE,
372 &game.panel.key_white,
376 GAME_PANEL_KEY_WHITE_COUNT,
377 &game.panel.key_white_count,
386 GAME_PANEL_HIGHSCORE,
387 &game.panel.highscore,
411 GAME_PANEL_TIME_ANIM,
412 &game.panel.time_anim,
415 IMG_GFX_GAME_PANEL_TIME_ANIM,
416 IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
424 GAME_PANEL_HEALTH_ANIM,
425 &game.panel.health_anim,
428 IMG_GFX_GAME_PANEL_HEALTH_ANIM,
429 IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
437 GAME_PANEL_SHIELD_NORMAL,
438 &game.panel.shield_normal,
442 GAME_PANEL_SHIELD_NORMAL_TIME,
443 &game.panel.shield_normal_time,
447 GAME_PANEL_SHIELD_DEADLY,
448 &game.panel.shield_deadly,
452 GAME_PANEL_SHIELD_DEADLY_TIME,
453 &game.panel.shield_deadly_time,
462 GAME_PANEL_EMC_MAGIC_BALL,
463 &game.panel.emc_magic_ball,
467 GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
468 &game.panel.emc_magic_ball_switch,
472 GAME_PANEL_LIGHT_SWITCH,
473 &game.panel.light_switch,
477 GAME_PANEL_LIGHT_SWITCH_TIME,
478 &game.panel.light_switch_time,
482 GAME_PANEL_TIMEGATE_SWITCH,
483 &game.panel.timegate_switch,
487 GAME_PANEL_TIMEGATE_SWITCH_TIME,
488 &game.panel.timegate_switch_time,
492 GAME_PANEL_SWITCHGATE_SWITCH,
493 &game.panel.switchgate_switch,
497 GAME_PANEL_EMC_LENSES,
498 &game.panel.emc_lenses,
502 GAME_PANEL_EMC_LENSES_TIME,
503 &game.panel.emc_lenses_time,
507 GAME_PANEL_EMC_MAGNIFIER,
508 &game.panel.emc_magnifier,
512 GAME_PANEL_EMC_MAGNIFIER_TIME,
513 &game.panel.emc_magnifier_time,
517 GAME_PANEL_BALLOON_SWITCH,
518 &game.panel.balloon_switch,
522 GAME_PANEL_DYNABOMB_NUMBER,
523 &game.panel.dynabomb_number,
527 GAME_PANEL_DYNABOMB_SIZE,
528 &game.panel.dynabomb_size,
532 GAME_PANEL_DYNABOMB_POWER,
533 &game.panel.dynabomb_power,
538 &game.panel.penguins,
542 GAME_PANEL_SOKOBAN_OBJECTS,
543 &game.panel.sokoban_objects,
547 GAME_PANEL_SOKOBAN_FIELDS,
548 &game.panel.sokoban_fields,
552 GAME_PANEL_ROBOT_WHEEL,
553 &game.panel.robot_wheel,
557 GAME_PANEL_CONVEYOR_BELT_1,
558 &game.panel.conveyor_belt[0],
562 GAME_PANEL_CONVEYOR_BELT_2,
563 &game.panel.conveyor_belt[1],
567 GAME_PANEL_CONVEYOR_BELT_3,
568 &game.panel.conveyor_belt[2],
572 GAME_PANEL_CONVEYOR_BELT_4,
573 &game.panel.conveyor_belt[3],
577 GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
578 &game.panel.conveyor_belt_switch[0],
582 GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
583 &game.panel.conveyor_belt_switch[1],
587 GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
588 &game.panel.conveyor_belt_switch[2],
592 GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
593 &game.panel.conveyor_belt_switch[3],
597 GAME_PANEL_MAGIC_WALL,
598 &game.panel.magic_wall,
602 GAME_PANEL_MAGIC_WALL_TIME,
603 &game.panel.magic_wall_time,
607 GAME_PANEL_GRAVITY_STATE,
608 &game.panel.gravity_state,
612 GAME_PANEL_GRAPHIC_1,
613 &game.panel.graphic[0],
617 GAME_PANEL_GRAPHIC_2,
618 &game.panel.graphic[1],
622 GAME_PANEL_GRAPHIC_3,
623 &game.panel.graphic[2],
627 GAME_PANEL_GRAPHIC_4,
628 &game.panel.graphic[3],
632 GAME_PANEL_GRAPHIC_5,
633 &game.panel.graphic[4],
637 GAME_PANEL_GRAPHIC_6,
638 &game.panel.graphic[5],
642 GAME_PANEL_GRAPHIC_7,
643 &game.panel.graphic[6],
647 GAME_PANEL_GRAPHIC_8,
648 &game.panel.graphic[7],
652 GAME_PANEL_ELEMENT_1,
653 &game.panel.element[0],
657 GAME_PANEL_ELEMENT_2,
658 &game.panel.element[1],
662 GAME_PANEL_ELEMENT_3,
663 &game.panel.element[2],
667 GAME_PANEL_ELEMENT_4,
668 &game.panel.element[3],
672 GAME_PANEL_ELEMENT_5,
673 &game.panel.element[4],
677 GAME_PANEL_ELEMENT_6,
678 &game.panel.element[5],
682 GAME_PANEL_ELEMENT_7,
683 &game.panel.element[6],
687 GAME_PANEL_ELEMENT_8,
688 &game.panel.element[7],
692 GAME_PANEL_ELEMENT_COUNT_1,
693 &game.panel.element_count[0],
697 GAME_PANEL_ELEMENT_COUNT_2,
698 &game.panel.element_count[1],
702 GAME_PANEL_ELEMENT_COUNT_3,
703 &game.panel.element_count[2],
707 GAME_PANEL_ELEMENT_COUNT_4,
708 &game.panel.element_count[3],
712 GAME_PANEL_ELEMENT_COUNT_5,
713 &game.panel.element_count[4],
717 GAME_PANEL_ELEMENT_COUNT_6,
718 &game.panel.element_count[5],
722 GAME_PANEL_ELEMENT_COUNT_7,
723 &game.panel.element_count[6],
727 GAME_PANEL_ELEMENT_COUNT_8,
728 &game.panel.element_count[7],
732 GAME_PANEL_CE_SCORE_1,
733 &game.panel.ce_score[0],
737 GAME_PANEL_CE_SCORE_2,
738 &game.panel.ce_score[1],
742 GAME_PANEL_CE_SCORE_3,
743 &game.panel.ce_score[2],
747 GAME_PANEL_CE_SCORE_4,
748 &game.panel.ce_score[3],
752 GAME_PANEL_CE_SCORE_5,
753 &game.panel.ce_score[4],
757 GAME_PANEL_CE_SCORE_6,
758 &game.panel.ce_score[5],
762 GAME_PANEL_CE_SCORE_7,
763 &game.panel.ce_score[6],
767 GAME_PANEL_CE_SCORE_8,
768 &game.panel.ce_score[7],
772 GAME_PANEL_CE_SCORE_1_ELEMENT,
773 &game.panel.ce_score_element[0],
777 GAME_PANEL_CE_SCORE_2_ELEMENT,
778 &game.panel.ce_score_element[1],
782 GAME_PANEL_CE_SCORE_3_ELEMENT,
783 &game.panel.ce_score_element[2],
787 GAME_PANEL_CE_SCORE_4_ELEMENT,
788 &game.panel.ce_score_element[3],
792 GAME_PANEL_CE_SCORE_5_ELEMENT,
793 &game.panel.ce_score_element[4],
797 GAME_PANEL_CE_SCORE_6_ELEMENT,
798 &game.panel.ce_score_element[5],
802 GAME_PANEL_CE_SCORE_7_ELEMENT,
803 &game.panel.ce_score_element[6],
807 GAME_PANEL_CE_SCORE_8_ELEMENT,
808 &game.panel.ce_score_element[7],
812 GAME_PANEL_PLAYER_NAME,
813 &game.panel.player_name,
817 GAME_PANEL_LEVEL_NAME,
818 &game.panel.level_name,
822 GAME_PANEL_LEVEL_AUTHOR,
823 &game.panel.level_author,
834 // values for delayed check of falling and moving elements and for collision
835 #define CHECK_DELAY_MOVING 3
836 #define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
837 #define CHECK_DELAY_COLLISION 2
838 #define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
840 // values for initial player move delay (initial delay counter value)
841 #define INITIAL_MOVE_DELAY_OFF -1
842 #define INITIAL_MOVE_DELAY_ON 0
844 // values for player movement speed (which is in fact a delay value)
845 #define MOVE_DELAY_MIN_SPEED 32
846 #define MOVE_DELAY_NORMAL_SPEED 8
847 #define MOVE_DELAY_HIGH_SPEED 4
848 #define MOVE_DELAY_MAX_SPEED 1
850 #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
851 #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
853 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
854 #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
856 // values for scroll positions
857 #define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
858 (x) > SBX_Right + MIDPOSX ? SBX_Right :\
860 #define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
861 (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
864 // values for other actions
865 #define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
866 #define MOVE_STEPSIZE_MIN (1)
867 #define MOVE_STEPSIZE_MAX (TILEX)
869 #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
870 #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
872 #define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
874 #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
875 RND(element_info[e].push_delay_random))
876 #define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
877 RND(element_info[e].drop_delay_random))
878 #define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
879 RND(element_info[e].move_delay_random))
880 #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
881 (element_info[e].move_delay_random))
882 #define GET_NEW_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
883 RND(element_info[e].step_delay_random))
884 #define GET_MAX_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
885 (element_info[e].step_delay_random))
886 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
887 RND(element_info[e].ce_value_random_initial))
888 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
889 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
890 RND((c)->delay_random * (c)->delay_frames))
891 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
892 RND((c)->delay_random))
895 #define GET_VALID_RUNTIME_ELEMENT(e) \
896 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
898 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
899 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
900 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
901 (be) + (e) - EL_SELF)
903 #define GET_PLAYER_FROM_BITS(p) \
904 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
906 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
907 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
908 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
909 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
910 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
911 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
912 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
913 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
914 RESOLVED_REFERENCE_ELEMENT(be, e) : \
917 #define CAN_GROW_INTO(e) \
918 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
920 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
921 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
924 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
925 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
926 (CAN_MOVE_INTO_ACID(e) && \
927 Tile[x][y] == EL_ACID) || \
930 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
931 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
932 (CAN_MOVE_INTO_ACID(e) && \
933 Tile[x][y] == EL_ACID) || \
936 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
937 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
939 (CAN_MOVE_INTO_ACID(e) && \
940 Tile[x][y] == EL_ACID) || \
941 (DONT_COLLIDE_WITH(e) && \
943 !PLAYER_ENEMY_PROTECTED(x, y))))
945 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
946 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
948 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
949 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
951 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
952 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
954 #define ANDROID_CAN_CLONE_FIELD(x, y) \
955 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
956 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
958 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
959 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
961 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
962 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
964 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
965 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
967 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
968 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
970 #define PIG_CAN_ENTER_FIELD(e, x, y) \
971 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
973 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
974 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
975 Tile[x][y] == EL_EM_EXIT_OPEN || \
976 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
977 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
978 IS_FOOD_PENGUIN(Tile[x][y])))
979 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
980 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
982 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
983 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
985 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
986 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
988 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
989 (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
990 Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
992 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
994 #define CE_ENTER_FIELD_COND(e, x, y) \
995 (!IS_PLAYER(x, y) && \
996 IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
998 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
999 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
1001 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
1002 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1004 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1005 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1006 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1007 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1009 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1011 // game button identifiers
1012 #define GAME_CTRL_ID_STOP 0
1013 #define GAME_CTRL_ID_PAUSE 1
1014 #define GAME_CTRL_ID_PLAY 2
1015 #define GAME_CTRL_ID_UNDO 3
1016 #define GAME_CTRL_ID_REDO 4
1017 #define GAME_CTRL_ID_SAVE 5
1018 #define GAME_CTRL_ID_PAUSE2 6
1019 #define GAME_CTRL_ID_LOAD 7
1020 #define GAME_CTRL_ID_PANEL_STOP 8
1021 #define GAME_CTRL_ID_PANEL_PAUSE 9
1022 #define GAME_CTRL_ID_PANEL_PLAY 10
1023 #define GAME_CTRL_ID_TOUCH_STOP 11
1024 #define GAME_CTRL_ID_TOUCH_PAUSE 12
1025 #define SOUND_CTRL_ID_MUSIC 13
1026 #define SOUND_CTRL_ID_LOOPS 14
1027 #define SOUND_CTRL_ID_SIMPLE 15
1028 #define SOUND_CTRL_ID_PANEL_MUSIC 16
1029 #define SOUND_CTRL_ID_PANEL_LOOPS 17
1030 #define SOUND_CTRL_ID_PANEL_SIMPLE 18
1032 #define NUM_GAME_BUTTONS 19
1035 // forward declaration for internal use
1037 static void CreateField(int, int, int);
1039 static void ResetGfxAnimation(int, int);
1041 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1042 static void AdvanceFrameAndPlayerCounters(int);
1044 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1045 static boolean MovePlayer(struct PlayerInfo *, int, int);
1046 static void ScrollPlayer(struct PlayerInfo *, int);
1047 static void ScrollScreen(struct PlayerInfo *, int);
1049 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1050 static boolean DigFieldByCE(int, int, int);
1051 static boolean SnapField(struct PlayerInfo *, int, int);
1052 static boolean DropElement(struct PlayerInfo *);
1054 static void InitBeltMovement(void);
1055 static void CloseAllOpenTimegates(void);
1056 static void CheckGravityMovement(struct PlayerInfo *);
1057 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1058 static void KillPlayerUnlessEnemyProtected(int, int);
1059 static void KillPlayerUnlessExplosionProtected(int, int);
1061 static void CheckNextToConditions(int, int);
1062 static void TestIfPlayerNextToCustomElement(int, int);
1063 static void TestIfPlayerTouchesCustomElement(int, int);
1064 static void TestIfElementNextToCustomElement(int, int);
1065 static void TestIfElementTouchesCustomElement(int, int);
1066 static void TestIfElementHitsCustomElement(int, int, int);
1068 static void HandleElementChange(int, int, int);
1069 static void ExecuteCustomElementAction(int, int, int, int);
1070 static boolean ChangeElement(int, int, int, int);
1072 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
1073 #define CheckTriggeredElementChange(x, y, e, ev) \
1074 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
1075 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1076 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1077 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1078 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1079 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1080 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1081 #define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
1082 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1084 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1085 #define CheckElementChange(x, y, e, te, ev) \
1086 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1087 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1088 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1089 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1090 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1091 #define CheckElementChangeByMouse(x, y, e, ev, s) \
1092 CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
1094 static void PlayLevelSound(int, int, int);
1095 static void PlayLevelSoundNearest(int, int, int);
1096 static void PlayLevelSoundAction(int, int, int);
1097 static void PlayLevelSoundElementAction(int, int, int, int);
1098 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1099 static void PlayLevelSoundActionIfLoop(int, int, int);
1100 static void StopLevelSoundActionIfLoop(int, int, int);
1101 static void PlayLevelMusic(void);
1102 static void FadeLevelSoundsAndMusic(void);
1104 static void HandleGameButtons(struct GadgetInfo *);
1106 int AmoebaNeighbourNr(int, int);
1107 void AmoebaToDiamond(int, int);
1108 void ContinueMoving(int, int);
1109 void Bang(int, int);
1110 void InitMovDir(int, int);
1111 void InitAmoebaNr(int, int);
1112 void NewHighScore(int, boolean);
1114 void TestIfGoodThingHitsBadThing(int, int, int);
1115 void TestIfBadThingHitsGoodThing(int, int, int);
1116 void TestIfPlayerTouchesBadThing(int, int);
1117 void TestIfPlayerRunsIntoBadThing(int, int, int);
1118 void TestIfBadThingTouchesPlayer(int, int);
1119 void TestIfBadThingRunsIntoPlayer(int, int, int);
1120 void TestIfFriendTouchesBadThing(int, int);
1121 void TestIfBadThingTouchesFriend(int, int);
1122 void TestIfBadThingTouchesOtherBadThing(int, int);
1123 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1125 void KillPlayer(struct PlayerInfo *);
1126 void BuryPlayer(struct PlayerInfo *);
1127 void RemovePlayer(struct PlayerInfo *);
1128 void ExitPlayer(struct PlayerInfo *);
1130 static int getInvisibleActiveFromInvisibleElement(int);
1131 static int getInvisibleFromInvisibleActiveElement(int);
1133 static void TestFieldAfterSnapping(int, int, int, int, int);
1135 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1137 // for detection of endless loops, caused by custom element programming
1138 // (using maximal playfield width x 10 is just a rough approximation)
1139 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1141 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1143 if (recursion_loop_detected) \
1146 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1148 recursion_loop_detected = TRUE; \
1149 recursion_loop_element = (e); \
1152 recursion_loop_depth++; \
1155 #define RECURSION_LOOP_DETECTION_END() \
1157 recursion_loop_depth--; \
1160 static int recursion_loop_depth;
1161 static boolean recursion_loop_detected;
1162 static boolean recursion_loop_element;
1164 static int map_player_action[MAX_PLAYERS];
1167 // ----------------------------------------------------------------------------
1168 // definition of elements that automatically change to other elements after
1169 // a specified time, eventually calling a function when changing
1170 // ----------------------------------------------------------------------------
1172 // forward declaration for changer functions
1173 static void InitBuggyBase(int, int);
1174 static void WarnBuggyBase(int, int);
1176 static void InitTrap(int, int);
1177 static void ActivateTrap(int, int);
1178 static void ChangeActiveTrap(int, int);
1180 static void InitRobotWheel(int, int);
1181 static void RunRobotWheel(int, int);
1182 static void StopRobotWheel(int, int);
1184 static void InitTimegateWheel(int, int);
1185 static void RunTimegateWheel(int, int);
1187 static void InitMagicBallDelay(int, int);
1188 static void ActivateMagicBall(int, int);
1190 struct ChangingElementInfo
1195 void (*pre_change_function)(int x, int y);
1196 void (*change_function)(int x, int y);
1197 void (*post_change_function)(int x, int y);
1200 static struct ChangingElementInfo change_delay_list[] =
1235 EL_STEEL_EXIT_OPENING,
1243 EL_STEEL_EXIT_CLOSING,
1244 EL_STEEL_EXIT_CLOSED,
1267 EL_EM_STEEL_EXIT_OPENING,
1268 EL_EM_STEEL_EXIT_OPEN,
1275 EL_EM_STEEL_EXIT_CLOSING,
1299 EL_SWITCHGATE_OPENING,
1307 EL_SWITCHGATE_CLOSING,
1308 EL_SWITCHGATE_CLOSED,
1315 EL_TIMEGATE_OPENING,
1323 EL_TIMEGATE_CLOSING,
1332 EL_ACID_SPLASH_LEFT,
1340 EL_ACID_SPLASH_RIGHT,
1349 EL_SP_BUGGY_BASE_ACTIVATING,
1356 EL_SP_BUGGY_BASE_ACTIVATING,
1357 EL_SP_BUGGY_BASE_ACTIVE,
1364 EL_SP_BUGGY_BASE_ACTIVE,
1388 EL_ROBOT_WHEEL_ACTIVE,
1396 EL_TIMEGATE_SWITCH_ACTIVE,
1404 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1405 EL_DC_TIMEGATE_SWITCH,
1412 EL_EMC_MAGIC_BALL_ACTIVE,
1413 EL_EMC_MAGIC_BALL_ACTIVE,
1420 EL_EMC_SPRING_BUMPER_ACTIVE,
1421 EL_EMC_SPRING_BUMPER,
1428 EL_DIAGONAL_SHRINKING,
1436 EL_DIAGONAL_GROWING,
1457 int push_delay_fixed, push_delay_random;
1461 { EL_SPRING, 0, 0 },
1462 { EL_BALLOON, 0, 0 },
1464 { EL_SOKOBAN_OBJECT, 2, 0 },
1465 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1466 { EL_SATELLITE, 2, 0 },
1467 { EL_SP_DISK_YELLOW, 2, 0 },
1469 { EL_UNDEFINED, 0, 0 },
1477 move_stepsize_list[] =
1479 { EL_AMOEBA_DROP, 2 },
1480 { EL_AMOEBA_DROPPING, 2 },
1481 { EL_QUICKSAND_FILLING, 1 },
1482 { EL_QUICKSAND_EMPTYING, 1 },
1483 { EL_QUICKSAND_FAST_FILLING, 2 },
1484 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1485 { EL_MAGIC_WALL_FILLING, 2 },
1486 { EL_MAGIC_WALL_EMPTYING, 2 },
1487 { EL_BD_MAGIC_WALL_FILLING, 2 },
1488 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1489 { EL_DC_MAGIC_WALL_FILLING, 2 },
1490 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1492 { EL_UNDEFINED, 0 },
1500 collect_count_list[] =
1503 { EL_BD_DIAMOND, 1 },
1504 { EL_EMERALD_YELLOW, 1 },
1505 { EL_EMERALD_RED, 1 },
1506 { EL_EMERALD_PURPLE, 1 },
1508 { EL_SP_INFOTRON, 1 },
1512 { EL_UNDEFINED, 0 },
1520 access_direction_list[] =
1522 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1523 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1524 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1525 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1526 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1527 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1528 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1529 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1530 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1531 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1532 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1534 { EL_SP_PORT_LEFT, MV_RIGHT },
1535 { EL_SP_PORT_RIGHT, MV_LEFT },
1536 { EL_SP_PORT_UP, MV_DOWN },
1537 { EL_SP_PORT_DOWN, MV_UP },
1538 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1539 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1540 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1541 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1542 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1543 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1544 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1545 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1546 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1547 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1548 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1549 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1550 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1551 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1552 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1554 { EL_UNDEFINED, MV_NONE }
1557 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1559 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1560 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1561 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1562 IS_JUST_CHANGING(x, y))
1564 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1566 // static variables for playfield scan mode (scanning forward or backward)
1567 static int playfield_scan_start_x = 0;
1568 static int playfield_scan_start_y = 0;
1569 static int playfield_scan_delta_x = 1;
1570 static int playfield_scan_delta_y = 1;
1572 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1573 (y) >= 0 && (y) <= lev_fieldy - 1; \
1574 (y) += playfield_scan_delta_y) \
1575 for ((x) = playfield_scan_start_x; \
1576 (x) >= 0 && (x) <= lev_fieldx - 1; \
1577 (x) += playfield_scan_delta_x)
1580 void DEBUG_SetMaximumDynamite(void)
1584 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1585 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1586 local_player->inventory_element[local_player->inventory_size++] =
1591 static void InitPlayfieldScanModeVars(void)
1593 if (game.use_reverse_scan_direction)
1595 playfield_scan_start_x = lev_fieldx - 1;
1596 playfield_scan_start_y = lev_fieldy - 1;
1598 playfield_scan_delta_x = -1;
1599 playfield_scan_delta_y = -1;
1603 playfield_scan_start_x = 0;
1604 playfield_scan_start_y = 0;
1606 playfield_scan_delta_x = 1;
1607 playfield_scan_delta_y = 1;
1611 static void InitPlayfieldScanMode(int mode)
1613 game.use_reverse_scan_direction =
1614 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1616 InitPlayfieldScanModeVars();
1619 static int get_move_delay_from_stepsize(int move_stepsize)
1622 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1624 // make sure that stepsize value is always a power of 2
1625 move_stepsize = (1 << log_2(move_stepsize));
1627 return TILEX / move_stepsize;
1630 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1633 int player_nr = player->index_nr;
1634 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1635 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1637 // do no immediately change move delay -- the player might just be moving
1638 player->move_delay_value_next = move_delay;
1640 // information if player can move must be set separately
1641 player->cannot_move = cannot_move;
1645 player->move_delay = game.initial_move_delay[player_nr];
1646 player->move_delay_value = game.initial_move_delay_value[player_nr];
1648 player->move_delay_value_next = -1;
1650 player->move_delay_reset_counter = 0;
1654 void GetPlayerConfig(void)
1656 GameFrameDelay = setup.game_frame_delay;
1658 if (!audio.sound_available)
1659 setup.sound_simple = FALSE;
1661 if (!audio.loops_available)
1662 setup.sound_loops = FALSE;
1664 if (!audio.music_available)
1665 setup.sound_music = FALSE;
1667 if (!video.fullscreen_available)
1668 setup.fullscreen = FALSE;
1670 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1672 SetAudioMode(setup.sound);
1675 int GetElementFromGroupElement(int element)
1677 if (IS_GROUP_ELEMENT(element))
1679 struct ElementGroupInfo *group = element_info[element].group;
1680 int last_anim_random_frame = gfx.anim_random_frame;
1683 if (group->choice_mode == ANIM_RANDOM)
1684 gfx.anim_random_frame = RND(group->num_elements_resolved);
1686 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1687 group->choice_mode, 0,
1690 if (group->choice_mode == ANIM_RANDOM)
1691 gfx.anim_random_frame = last_anim_random_frame;
1693 group->choice_pos++;
1695 element = group->element_resolved[element_pos];
1701 static void IncrementSokobanFieldsNeeded(void)
1703 if (level.sb_fields_needed)
1704 game.sokoban_fields_still_needed++;
1707 static void IncrementSokobanObjectsNeeded(void)
1709 if (level.sb_objects_needed)
1710 game.sokoban_objects_still_needed++;
1713 static void DecrementSokobanFieldsNeeded(void)
1715 if (game.sokoban_fields_still_needed > 0)
1716 game.sokoban_fields_still_needed--;
1719 static void DecrementSokobanObjectsNeeded(void)
1721 if (game.sokoban_objects_still_needed > 0)
1722 game.sokoban_objects_still_needed--;
1725 static void InitPlayerField(int x, int y, int element, boolean init_game)
1727 if (element == EL_SP_MURPHY)
1731 if (stored_player[0].present)
1733 Tile[x][y] = EL_SP_MURPHY_CLONE;
1739 stored_player[0].initial_element = element;
1740 stored_player[0].use_murphy = TRUE;
1742 if (!level.use_artwork_element[0])
1743 stored_player[0].artwork_element = EL_SP_MURPHY;
1746 Tile[x][y] = EL_PLAYER_1;
1752 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1753 int jx = player->jx, jy = player->jy;
1755 player->present = TRUE;
1757 player->block_last_field = (element == EL_SP_MURPHY ?
1758 level.sp_block_last_field :
1759 level.block_last_field);
1761 // ---------- initialize player's last field block delay ------------------
1763 // always start with reliable default value (no adjustment needed)
1764 player->block_delay_adjustment = 0;
1766 // special case 1: in Supaplex, Murphy blocks last field one more frame
1767 if (player->block_last_field && element == EL_SP_MURPHY)
1768 player->block_delay_adjustment = 1;
1770 // special case 2: in game engines before 3.1.1, blocking was different
1771 if (game.use_block_last_field_bug)
1772 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1774 if (!network.enabled || player->connected_network)
1776 player->active = TRUE;
1778 // remove potentially duplicate players
1779 if (StorePlayer[jx][jy] == Tile[x][y])
1780 StorePlayer[jx][jy] = 0;
1782 StorePlayer[x][y] = Tile[x][y];
1784 #if DEBUG_INIT_PLAYER
1785 Debug("game:init:player", "- player element %d activated",
1786 player->element_nr);
1787 Debug("game:init:player", " (local player is %d and currently %s)",
1788 local_player->element_nr,
1789 local_player->active ? "active" : "not active");
1793 Tile[x][y] = EL_EMPTY;
1795 player->jx = player->last_jx = x;
1796 player->jy = player->last_jy = y;
1799 // always check if player was just killed and should be reanimated
1801 int player_nr = GET_PLAYER_NR(element);
1802 struct PlayerInfo *player = &stored_player[player_nr];
1804 if (player->active && player->killed)
1805 player->reanimated = TRUE; // if player was just killed, reanimate him
1809 static void InitField(int x, int y, boolean init_game)
1811 int element = Tile[x][y];
1820 InitPlayerField(x, y, element, init_game);
1823 case EL_SOKOBAN_FIELD_PLAYER:
1824 element = Tile[x][y] = EL_PLAYER_1;
1825 InitField(x, y, init_game);
1827 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1828 InitField(x, y, init_game);
1831 case EL_SOKOBAN_FIELD_EMPTY:
1832 IncrementSokobanFieldsNeeded();
1835 case EL_SOKOBAN_OBJECT:
1836 IncrementSokobanObjectsNeeded();
1840 if (x < lev_fieldx-1 && Tile[x+1][y] == EL_ACID)
1841 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1842 else if (x > 0 && Tile[x-1][y] == EL_ACID)
1843 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1844 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT)
1845 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1846 else if (y > 0 && Tile[x][y-1] == EL_ACID)
1847 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1848 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1849 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1858 case EL_SPACESHIP_RIGHT:
1859 case EL_SPACESHIP_UP:
1860 case EL_SPACESHIP_LEFT:
1861 case EL_SPACESHIP_DOWN:
1862 case EL_BD_BUTTERFLY:
1863 case EL_BD_BUTTERFLY_RIGHT:
1864 case EL_BD_BUTTERFLY_UP:
1865 case EL_BD_BUTTERFLY_LEFT:
1866 case EL_BD_BUTTERFLY_DOWN:
1868 case EL_BD_FIREFLY_RIGHT:
1869 case EL_BD_FIREFLY_UP:
1870 case EL_BD_FIREFLY_LEFT:
1871 case EL_BD_FIREFLY_DOWN:
1872 case EL_PACMAN_RIGHT:
1874 case EL_PACMAN_LEFT:
1875 case EL_PACMAN_DOWN:
1877 case EL_YAMYAM_LEFT:
1878 case EL_YAMYAM_RIGHT:
1880 case EL_YAMYAM_DOWN:
1881 case EL_DARK_YAMYAM:
1884 case EL_SP_SNIKSNAK:
1885 case EL_SP_ELECTRON:
1891 case EL_SPRING_LEFT:
1892 case EL_SPRING_RIGHT:
1896 case EL_AMOEBA_FULL:
1901 case EL_AMOEBA_DROP:
1902 if (y == lev_fieldy - 1)
1904 Tile[x][y] = EL_AMOEBA_GROWING;
1905 Store[x][y] = EL_AMOEBA_WET;
1909 case EL_DYNAMITE_ACTIVE:
1910 case EL_SP_DISK_RED_ACTIVE:
1911 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1912 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1913 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1914 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1915 MovDelay[x][y] = 96;
1918 case EL_EM_DYNAMITE_ACTIVE:
1919 MovDelay[x][y] = 32;
1923 game.lights_still_needed++;
1927 game.friends_still_needed++;
1932 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1935 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1936 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1937 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1938 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1939 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1940 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1941 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1942 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1943 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1944 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1945 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1946 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1949 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1950 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1951 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1953 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1955 game.belt_dir[belt_nr] = belt_dir;
1956 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1958 else // more than one switch -- set it like the first switch
1960 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1965 case EL_LIGHT_SWITCH_ACTIVE:
1967 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1970 case EL_INVISIBLE_STEELWALL:
1971 case EL_INVISIBLE_WALL:
1972 case EL_INVISIBLE_SAND:
1973 if (game.light_time_left > 0 ||
1974 game.lenses_time_left > 0)
1975 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1978 case EL_EMC_MAGIC_BALL:
1979 if (game.ball_active)
1980 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1983 case EL_EMC_MAGIC_BALL_SWITCH:
1984 if (game.ball_active)
1985 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1988 case EL_TRIGGER_PLAYER:
1989 case EL_TRIGGER_ELEMENT:
1990 case EL_TRIGGER_CE_VALUE:
1991 case EL_TRIGGER_CE_SCORE:
1993 case EL_ANY_ELEMENT:
1994 case EL_CURRENT_CE_VALUE:
1995 case EL_CURRENT_CE_SCORE:
2012 // reference elements should not be used on the playfield
2013 Tile[x][y] = EL_EMPTY;
2017 if (IS_CUSTOM_ELEMENT(element))
2019 if (CAN_MOVE(element))
2022 if (!element_info[element].use_last_ce_value || init_game)
2023 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2025 else if (IS_GROUP_ELEMENT(element))
2027 Tile[x][y] = GetElementFromGroupElement(element);
2029 InitField(x, y, init_game);
2031 else if (IS_EMPTY_ELEMENT(element))
2033 GfxElementEmpty[x][y] = element;
2034 Tile[x][y] = EL_EMPTY;
2041 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2044 static void InitField_WithBug1(int x, int y, boolean init_game)
2046 InitField(x, y, init_game);
2048 // not needed to call InitMovDir() -- already done by InitField()!
2049 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2050 CAN_MOVE(Tile[x][y]))
2054 static void InitField_WithBug2(int x, int y, boolean init_game)
2056 int old_element = Tile[x][y];
2058 InitField(x, y, init_game);
2060 // not needed to call InitMovDir() -- already done by InitField()!
2061 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2062 CAN_MOVE(old_element) &&
2063 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2066 /* this case is in fact a combination of not less than three bugs:
2067 first, it calls InitMovDir() for elements that can move, although this is
2068 already done by InitField(); then, it checks the element that was at this
2069 field _before_ the call to InitField() (which can change it); lastly, it
2070 was not called for "mole with direction" elements, which were treated as
2071 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2075 static int get_key_element_from_nr(int key_nr)
2077 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2078 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2079 EL_EM_KEY_1 : EL_KEY_1);
2081 return key_base_element + key_nr;
2084 static int get_next_dropped_element(struct PlayerInfo *player)
2086 return (player->inventory_size > 0 ?
2087 player->inventory_element[player->inventory_size - 1] :
2088 player->inventory_infinite_element != EL_UNDEFINED ?
2089 player->inventory_infinite_element :
2090 player->dynabombs_left > 0 ?
2091 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2095 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2097 // pos >= 0: get element from bottom of the stack;
2098 // pos < 0: get element from top of the stack
2102 int min_inventory_size = -pos;
2103 int inventory_pos = player->inventory_size - min_inventory_size;
2104 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2106 return (player->inventory_size >= min_inventory_size ?
2107 player->inventory_element[inventory_pos] :
2108 player->inventory_infinite_element != EL_UNDEFINED ?
2109 player->inventory_infinite_element :
2110 player->dynabombs_left >= min_dynabombs_left ?
2111 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2116 int min_dynabombs_left = pos + 1;
2117 int min_inventory_size = pos + 1 - player->dynabombs_left;
2118 int inventory_pos = pos - player->dynabombs_left;
2120 return (player->inventory_infinite_element != EL_UNDEFINED ?
2121 player->inventory_infinite_element :
2122 player->dynabombs_left >= min_dynabombs_left ?
2123 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2124 player->inventory_size >= min_inventory_size ?
2125 player->inventory_element[inventory_pos] :
2130 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2132 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2133 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2136 if (gpo1->sort_priority != gpo2->sort_priority)
2137 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2139 compare_result = gpo1->nr - gpo2->nr;
2141 return compare_result;
2144 int getPlayerInventorySize(int player_nr)
2146 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2147 return game_em.ply[player_nr]->dynamite;
2148 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2149 return game_sp.red_disk_count;
2151 return stored_player[player_nr].inventory_size;
2154 static void InitGameControlValues(void)
2158 for (i = 0; game_panel_controls[i].nr != -1; i++)
2160 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2161 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2162 struct TextPosInfo *pos = gpc->pos;
2164 int type = gpc->type;
2168 Error("'game_panel_controls' structure corrupted at %d", i);
2170 Fail("this should not happen -- please debug");
2173 // force update of game controls after initialization
2174 gpc->value = gpc->last_value = -1;
2175 gpc->frame = gpc->last_frame = -1;
2176 gpc->gfx_frame = -1;
2178 // determine panel value width for later calculation of alignment
2179 if (type == TYPE_INTEGER || type == TYPE_STRING)
2181 pos->width = pos->size * getFontWidth(pos->font);
2182 pos->height = getFontHeight(pos->font);
2184 else if (type == TYPE_ELEMENT)
2186 pos->width = pos->size;
2187 pos->height = pos->size;
2190 // fill structure for game panel draw order
2192 gpo->sort_priority = pos->sort_priority;
2195 // sort game panel controls according to sort_priority and control number
2196 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2197 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2200 static void UpdatePlayfieldElementCount(void)
2202 boolean use_element_count = FALSE;
2205 // first check if it is needed at all to calculate playfield element count
2206 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2207 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2208 use_element_count = TRUE;
2210 if (!use_element_count)
2213 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2214 element_info[i].element_count = 0;
2216 SCAN_PLAYFIELD(x, y)
2218 element_info[Tile[x][y]].element_count++;
2221 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2222 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2223 if (IS_IN_GROUP(j, i))
2224 element_info[EL_GROUP_START + i].element_count +=
2225 element_info[j].element_count;
2228 static void UpdateGameControlValues(void)
2231 int time = (game.LevelSolved ?
2232 game.LevelSolved_CountingTime :
2233 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2235 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2236 game_sp.time_played :
2237 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2238 game_mm.energy_left :
2239 game.no_time_limit ? TimePlayed : TimeLeft);
2240 int score = (game.LevelSolved ?
2241 game.LevelSolved_CountingScore :
2242 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2243 game_em.lev->score :
2244 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2246 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2249 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2250 game_em.lev->gems_needed :
2251 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2252 game_sp.infotrons_still_needed :
2253 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2254 game_mm.kettles_still_needed :
2255 game.gems_still_needed);
2256 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2257 game_em.lev->gems_needed > 0 :
2258 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2259 game_sp.infotrons_still_needed > 0 :
2260 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2261 game_mm.kettles_still_needed > 0 ||
2262 game_mm.lights_still_needed > 0 :
2263 game.gems_still_needed > 0 ||
2264 game.sokoban_fields_still_needed > 0 ||
2265 game.sokoban_objects_still_needed > 0 ||
2266 game.lights_still_needed > 0);
2267 int health = (game.LevelSolved ?
2268 game.LevelSolved_CountingHealth :
2269 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2270 MM_HEALTH(game_mm.laser_overload_value) :
2272 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2274 UpdatePlayfieldElementCount();
2276 // update game panel control values
2278 // used instead of "level_nr" (for network games)
2279 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2280 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2282 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2283 for (i = 0; i < MAX_NUM_KEYS; i++)
2284 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2285 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2286 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2288 if (game.centered_player_nr == -1)
2290 for (i = 0; i < MAX_PLAYERS; i++)
2292 // only one player in Supaplex game engine
2293 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2296 for (k = 0; k < MAX_NUM_KEYS; k++)
2298 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2300 if (game_em.ply[i]->keys & (1 << k))
2301 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2302 get_key_element_from_nr(k);
2304 else if (stored_player[i].key[k])
2305 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2306 get_key_element_from_nr(k);
2309 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2310 getPlayerInventorySize(i);
2312 if (stored_player[i].num_white_keys > 0)
2313 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2316 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2317 stored_player[i].num_white_keys;
2322 int player_nr = game.centered_player_nr;
2324 for (k = 0; k < MAX_NUM_KEYS; k++)
2326 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2328 if (game_em.ply[player_nr]->keys & (1 << k))
2329 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2330 get_key_element_from_nr(k);
2332 else if (stored_player[player_nr].key[k])
2333 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2334 get_key_element_from_nr(k);
2337 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2338 getPlayerInventorySize(player_nr);
2340 if (stored_player[player_nr].num_white_keys > 0)
2341 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2343 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2344 stored_player[player_nr].num_white_keys;
2347 // re-arrange keys on game panel, if needed or if defined by style settings
2348 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2350 int nr = GAME_PANEL_KEY_1 + i;
2351 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2352 struct TextPosInfo *pos = gpc->pos;
2354 // skip check if key is not in the player's inventory
2355 if (gpc->value == EL_EMPTY)
2358 // check if keys should be arranged on panel from left to right
2359 if (pos->style == STYLE_LEFTMOST_POSITION)
2361 // check previous key positions (left from current key)
2362 for (k = 0; k < i; k++)
2364 int nr_new = GAME_PANEL_KEY_1 + k;
2366 if (game_panel_controls[nr_new].value == EL_EMPTY)
2368 game_panel_controls[nr_new].value = gpc->value;
2369 gpc->value = EL_EMPTY;
2376 // check if "undefined" keys can be placed at some other position
2377 if (pos->x == -1 && pos->y == -1)
2379 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2381 // 1st try: display key at the same position as normal or EM keys
2382 if (game_panel_controls[nr_new].value == EL_EMPTY)
2384 game_panel_controls[nr_new].value = gpc->value;
2388 // 2nd try: display key at the next free position in the key panel
2389 for (k = 0; k < STD_NUM_KEYS; k++)
2391 nr_new = GAME_PANEL_KEY_1 + k;
2393 if (game_panel_controls[nr_new].value == EL_EMPTY)
2395 game_panel_controls[nr_new].value = gpc->value;
2404 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2406 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2407 get_inventory_element_from_pos(local_player, i);
2408 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2409 get_inventory_element_from_pos(local_player, -i - 1);
2412 game_panel_controls[GAME_PANEL_SCORE].value = score;
2413 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2415 game_panel_controls[GAME_PANEL_TIME].value = time;
2417 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2418 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2419 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2421 if (level.time == 0)
2422 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2424 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2426 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2427 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2429 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2431 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2432 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2434 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2435 local_player->shield_normal_time_left;
2436 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2437 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2439 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2440 local_player->shield_deadly_time_left;
2442 game_panel_controls[GAME_PANEL_EXIT].value =
2443 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2445 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2446 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2447 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2448 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2449 EL_EMC_MAGIC_BALL_SWITCH);
2451 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2452 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2453 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2454 game.light_time_left;
2456 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2457 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2458 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2459 game.timegate_time_left;
2461 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2462 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2464 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2465 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2466 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2467 game.lenses_time_left;
2469 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2470 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2471 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2472 game.magnify_time_left;
2474 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2475 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2476 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2477 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2478 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2479 EL_BALLOON_SWITCH_NONE);
2481 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2482 local_player->dynabomb_count;
2483 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2484 local_player->dynabomb_size;
2485 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2486 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2488 game_panel_controls[GAME_PANEL_PENGUINS].value =
2489 game.friends_still_needed;
2491 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2492 game.sokoban_objects_still_needed;
2493 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2494 game.sokoban_fields_still_needed;
2496 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2497 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2499 for (i = 0; i < NUM_BELTS; i++)
2501 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2502 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2503 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2504 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2505 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2508 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2509 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2510 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2511 game.magic_wall_time_left;
2513 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2514 local_player->gravity;
2516 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2517 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2519 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2520 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2521 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2522 game.panel.element[i].id : EL_UNDEFINED);
2524 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2525 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2526 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2527 element_info[game.panel.element_count[i].id].element_count : 0);
2529 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2530 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2531 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2532 element_info[game.panel.ce_score[i].id].collect_score : 0);
2534 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2535 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2536 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2537 element_info[game.panel.ce_score_element[i].id].collect_score :
2540 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2541 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2542 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2544 // update game panel control frames
2546 for (i = 0; game_panel_controls[i].nr != -1; i++)
2548 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2550 if (gpc->type == TYPE_ELEMENT)
2552 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2554 int last_anim_random_frame = gfx.anim_random_frame;
2555 int element = gpc->value;
2556 int graphic = el2panelimg(element);
2557 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2558 sync_random_frame : INIT_GFX_RANDOM());
2560 if (gpc->value != gpc->last_value)
2563 gpc->gfx_random = init_gfx_random;
2569 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2570 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2571 gpc->gfx_random = init_gfx_random;
2574 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2575 gfx.anim_random_frame = gpc->gfx_random;
2577 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2578 gpc->gfx_frame = element_info[element].collect_score;
2580 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2582 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2583 gfx.anim_random_frame = last_anim_random_frame;
2586 else if (gpc->type == TYPE_GRAPHIC)
2588 if (gpc->graphic != IMG_UNDEFINED)
2590 int last_anim_random_frame = gfx.anim_random_frame;
2591 int graphic = gpc->graphic;
2592 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2593 sync_random_frame : INIT_GFX_RANDOM());
2595 if (gpc->value != gpc->last_value)
2598 gpc->gfx_random = init_gfx_random;
2604 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2605 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2606 gpc->gfx_random = init_gfx_random;
2609 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2610 gfx.anim_random_frame = gpc->gfx_random;
2612 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2614 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2615 gfx.anim_random_frame = last_anim_random_frame;
2621 static void DisplayGameControlValues(void)
2623 boolean redraw_panel = FALSE;
2626 for (i = 0; game_panel_controls[i].nr != -1; i++)
2628 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2630 if (PANEL_DEACTIVATED(gpc->pos))
2633 if (gpc->value == gpc->last_value &&
2634 gpc->frame == gpc->last_frame)
2637 redraw_panel = TRUE;
2643 // copy default game door content to main double buffer
2645 // !!! CHECK AGAIN !!!
2646 SetPanelBackground();
2647 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2648 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2650 // redraw game control buttons
2651 RedrawGameButtons();
2653 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2655 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2657 int nr = game_panel_order[i].nr;
2658 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2659 struct TextPosInfo *pos = gpc->pos;
2660 int type = gpc->type;
2661 int value = gpc->value;
2662 int frame = gpc->frame;
2663 int size = pos->size;
2664 int font = pos->font;
2665 boolean draw_masked = pos->draw_masked;
2666 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2668 if (PANEL_DEACTIVATED(pos))
2671 if (pos->class == get_hash_from_key("extra_panel_items") &&
2672 !setup.prefer_extra_panel_items)
2675 gpc->last_value = value;
2676 gpc->last_frame = frame;
2678 if (type == TYPE_INTEGER)
2680 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2681 nr == GAME_PANEL_TIME)
2683 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2685 if (use_dynamic_size) // use dynamic number of digits
2687 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2688 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2689 int size2 = size1 + 1;
2690 int font1 = pos->font;
2691 int font2 = pos->font_alt;
2693 size = (value < value_change ? size1 : size2);
2694 font = (value < value_change ? font1 : font2);
2698 // correct text size if "digits" is zero or less
2700 size = strlen(int2str(value, size));
2702 // dynamically correct text alignment
2703 pos->width = size * getFontWidth(font);
2705 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2706 int2str(value, size), font, mask_mode);
2708 else if (type == TYPE_ELEMENT)
2710 int element, graphic;
2714 int dst_x = PANEL_XPOS(pos);
2715 int dst_y = PANEL_YPOS(pos);
2717 if (value != EL_UNDEFINED && value != EL_EMPTY)
2720 graphic = el2panelimg(value);
2723 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2724 element, EL_NAME(element), size);
2727 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2730 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2733 width = graphic_info[graphic].width * size / TILESIZE;
2734 height = graphic_info[graphic].height * size / TILESIZE;
2737 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2740 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2744 else if (type == TYPE_GRAPHIC)
2746 int graphic = gpc->graphic;
2747 int graphic_active = gpc->graphic_active;
2751 int dst_x = PANEL_XPOS(pos);
2752 int dst_y = PANEL_YPOS(pos);
2753 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2754 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2756 if (graphic != IMG_UNDEFINED && !skip)
2758 if (pos->style == STYLE_REVERSE)
2759 value = 100 - value;
2761 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2763 if (pos->direction & MV_HORIZONTAL)
2765 width = graphic_info[graphic_active].width * value / 100;
2766 height = graphic_info[graphic_active].height;
2768 if (pos->direction == MV_LEFT)
2770 src_x += graphic_info[graphic_active].width - width;
2771 dst_x += graphic_info[graphic_active].width - width;
2776 width = graphic_info[graphic_active].width;
2777 height = graphic_info[graphic_active].height * value / 100;
2779 if (pos->direction == MV_UP)
2781 src_y += graphic_info[graphic_active].height - height;
2782 dst_y += graphic_info[graphic_active].height - height;
2787 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2790 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2793 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2795 if (pos->direction & MV_HORIZONTAL)
2797 if (pos->direction == MV_RIGHT)
2804 dst_x = PANEL_XPOS(pos);
2807 width = graphic_info[graphic].width - width;
2811 if (pos->direction == MV_DOWN)
2818 dst_y = PANEL_YPOS(pos);
2821 height = graphic_info[graphic].height - height;
2825 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2828 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2832 else if (type == TYPE_STRING)
2834 boolean active = (value != 0);
2835 char *state_normal = "off";
2836 char *state_active = "on";
2837 char *state = (active ? state_active : state_normal);
2838 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2839 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2840 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2841 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2843 if (nr == GAME_PANEL_GRAVITY_STATE)
2845 int font1 = pos->font; // (used for normal state)
2846 int font2 = pos->font_alt; // (used for active state)
2848 font = (active ? font2 : font1);
2857 // don't truncate output if "chars" is zero or less
2860 // dynamically correct text alignment
2861 pos->width = size * getFontWidth(font);
2864 s_cut = getStringCopyN(s, size);
2866 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2867 s_cut, font, mask_mode);
2873 redraw_mask |= REDRAW_DOOR_1;
2876 SetGameStatus(GAME_MODE_PLAYING);
2879 void UpdateAndDisplayGameControlValues(void)
2881 if (tape.deactivate_display)
2884 UpdateGameControlValues();
2885 DisplayGameControlValues();
2888 void UpdateGameDoorValues(void)
2890 UpdateGameControlValues();
2893 void DrawGameDoorValues(void)
2895 DisplayGameControlValues();
2899 // ============================================================================
2901 // ----------------------------------------------------------------------------
2902 // initialize game engine due to level / tape version number
2903 // ============================================================================
2905 static void InitGameEngine(void)
2907 int i, j, k, l, x, y;
2909 // set game engine from tape file when re-playing, else from level file
2910 game.engine_version = (tape.playing ? tape.engine_version :
2911 level.game_version);
2913 // set single or multi-player game mode (needed for re-playing tapes)
2914 game.team_mode = setup.team_mode;
2918 int num_players = 0;
2920 for (i = 0; i < MAX_PLAYERS; i++)
2921 if (tape.player_participates[i])
2924 // multi-player tapes contain input data for more than one player
2925 game.team_mode = (num_players > 1);
2929 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2930 level.game_version);
2931 Debug("game:init:level", " tape.file_version == %06d",
2933 Debug("game:init:level", " tape.game_version == %06d",
2935 Debug("game:init:level", " tape.engine_version == %06d",
2936 tape.engine_version);
2937 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2938 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2941 // --------------------------------------------------------------------------
2942 // set flags for bugs and changes according to active game engine version
2943 // --------------------------------------------------------------------------
2947 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2949 Bug was introduced in version:
2952 Bug was fixed in version:
2956 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2957 but the property "can fall" was missing, which caused some levels to be
2958 unsolvable. This was fixed in version 4.2.0.0.
2960 Affected levels/tapes:
2961 An example for a tape that was fixed by this bugfix is tape 029 from the
2962 level set "rnd_sam_bateman".
2963 The wrong behaviour will still be used for all levels or tapes that were
2964 created/recorded with it. An example for this is tape 023 from the level
2965 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2968 boolean use_amoeba_dropping_cannot_fall_bug =
2969 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2970 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2972 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2973 tape.game_version < VERSION_IDENT(4,2,0,0)));
2976 Summary of bugfix/change:
2977 Fixed move speed of elements entering or leaving magic wall.
2979 Fixed/changed in version:
2983 Before 2.0.1, move speed of elements entering or leaving magic wall was
2984 twice as fast as it is now.
2985 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2987 Affected levels/tapes:
2988 The first condition is generally needed for all levels/tapes before version
2989 2.0.1, which might use the old behaviour before it was changed; known tapes
2990 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2991 The second condition is an exception from the above case and is needed for
2992 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2993 above, but before it was known that this change would break tapes like the
2994 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2995 although the engine version while recording maybe was before 2.0.1. There
2996 are a lot of tapes that are affected by this exception, like tape 006 from
2997 the level set "rnd_conor_mancone".
3000 boolean use_old_move_stepsize_for_magic_wall =
3001 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
3003 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3004 tape.game_version < VERSION_IDENT(4,2,0,0)));
3007 Summary of bugfix/change:
3008 Fixed handling for custom elements that change when pushed by the player.
3010 Fixed/changed in version:
3014 Before 3.1.0, custom elements that "change when pushing" changed directly
3015 after the player started pushing them (until then handled in "DigField()").
3016 Since 3.1.0, these custom elements are not changed until the "pushing"
3017 move of the element is finished (now handled in "ContinueMoving()").
3019 Affected levels/tapes:
3020 The first condition is generally needed for all levels/tapes before version
3021 3.1.0, which might use the old behaviour before it was changed; known tapes
3022 that are affected are some tapes from the level set "Walpurgis Gardens" by
3024 The second condition is an exception from the above case and is needed for
3025 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3026 above (including some development versions of 3.1.0), but before it was
3027 known that this change would break tapes like the above and was fixed in
3028 3.1.1, so that the changed behaviour was active although the engine version
3029 while recording maybe was before 3.1.0. There is at least one tape that is
3030 affected by this exception, which is the tape for the one-level set "Bug
3031 Machine" by Juergen Bonhagen.
3034 game.use_change_when_pushing_bug =
3035 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3037 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3038 tape.game_version < VERSION_IDENT(3,1,1,0)));
3041 Summary of bugfix/change:
3042 Fixed handling for blocking the field the player leaves when moving.
3044 Fixed/changed in version:
3048 Before 3.1.1, when "block last field when moving" was enabled, the field
3049 the player is leaving when moving was blocked for the time of the move,
3050 and was directly unblocked afterwards. This resulted in the last field
3051 being blocked for exactly one less than the number of frames of one player
3052 move. Additionally, even when blocking was disabled, the last field was
3053 blocked for exactly one frame.
3054 Since 3.1.1, due to changes in player movement handling, the last field
3055 is not blocked at all when blocking is disabled. When blocking is enabled,
3056 the last field is blocked for exactly the number of frames of one player
3057 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3058 last field is blocked for exactly one more than the number of frames of
3061 Affected levels/tapes:
3062 (!!! yet to be determined -- probably many !!!)
3065 game.use_block_last_field_bug =
3066 (game.engine_version < VERSION_IDENT(3,1,1,0));
3068 /* various special flags and settings for native Emerald Mine game engine */
3070 game_em.use_single_button =
3071 (game.engine_version > VERSION_IDENT(4,0,0,2));
3073 game_em.use_snap_key_bug =
3074 (game.engine_version < VERSION_IDENT(4,0,1,0));
3076 game_em.use_random_bug =
3077 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3079 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3081 game_em.use_old_explosions = use_old_em_engine;
3082 game_em.use_old_android = use_old_em_engine;
3083 game_em.use_old_push_elements = use_old_em_engine;
3084 game_em.use_old_push_into_acid = use_old_em_engine;
3086 game_em.use_wrap_around = !use_old_em_engine;
3088 // --------------------------------------------------------------------------
3090 // set maximal allowed number of custom element changes per game frame
3091 game.max_num_changes_per_frame = 1;
3093 // default scan direction: scan playfield from top/left to bottom/right
3094 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3096 // dynamically adjust element properties according to game engine version
3097 InitElementPropertiesEngine(game.engine_version);
3099 // ---------- initialize special element properties -------------------------
3101 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3102 if (use_amoeba_dropping_cannot_fall_bug)
3103 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3105 // ---------- initialize player's initial move delay ------------------------
3107 // dynamically adjust player properties according to level information
3108 for (i = 0; i < MAX_PLAYERS; i++)
3109 game.initial_move_delay_value[i] =
3110 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3112 // dynamically adjust player properties according to game engine version
3113 for (i = 0; i < MAX_PLAYERS; i++)
3114 game.initial_move_delay[i] =
3115 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3116 game.initial_move_delay_value[i] : 0);
3118 // ---------- initialize player's initial push delay ------------------------
3120 // dynamically adjust player properties according to game engine version
3121 game.initial_push_delay_value =
3122 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3124 // ---------- initialize changing elements ----------------------------------
3126 // initialize changing elements information
3127 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3129 struct ElementInfo *ei = &element_info[i];
3131 // this pointer might have been changed in the level editor
3132 ei->change = &ei->change_page[0];
3134 if (!IS_CUSTOM_ELEMENT(i))
3136 ei->change->target_element = EL_EMPTY_SPACE;
3137 ei->change->delay_fixed = 0;
3138 ei->change->delay_random = 0;
3139 ei->change->delay_frames = 1;
3142 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3144 ei->has_change_event[j] = FALSE;
3146 ei->event_page_nr[j] = 0;
3147 ei->event_page[j] = &ei->change_page[0];
3151 // add changing elements from pre-defined list
3152 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3154 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3155 struct ElementInfo *ei = &element_info[ch_delay->element];
3157 ei->change->target_element = ch_delay->target_element;
3158 ei->change->delay_fixed = ch_delay->change_delay;
3160 ei->change->pre_change_function = ch_delay->pre_change_function;
3161 ei->change->change_function = ch_delay->change_function;
3162 ei->change->post_change_function = ch_delay->post_change_function;
3164 ei->change->can_change = TRUE;
3165 ei->change->can_change_or_has_action = TRUE;
3167 ei->has_change_event[CE_DELAY] = TRUE;
3169 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3170 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3173 // ---------- initialize internal run-time variables ------------------------
3175 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3177 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3179 for (j = 0; j < ei->num_change_pages; j++)
3181 ei->change_page[j].can_change_or_has_action =
3182 (ei->change_page[j].can_change |
3183 ei->change_page[j].has_action);
3187 // add change events from custom element configuration
3188 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3190 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3192 for (j = 0; j < ei->num_change_pages; j++)
3194 if (!ei->change_page[j].can_change_or_has_action)
3197 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3199 // only add event page for the first page found with this event
3200 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3202 ei->has_change_event[k] = TRUE;
3204 ei->event_page_nr[k] = j;
3205 ei->event_page[k] = &ei->change_page[j];
3211 // ---------- initialize reference elements in change conditions ------------
3213 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3215 int element = EL_CUSTOM_START + i;
3216 struct ElementInfo *ei = &element_info[element];
3218 for (j = 0; j < ei->num_change_pages; j++)
3220 int trigger_element = ei->change_page[j].initial_trigger_element;
3222 if (trigger_element >= EL_PREV_CE_8 &&
3223 trigger_element <= EL_NEXT_CE_8)
3224 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3226 ei->change_page[j].trigger_element = trigger_element;
3230 // ---------- initialize run-time trigger player and element ----------------
3232 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3234 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3236 for (j = 0; j < ei->num_change_pages; j++)
3238 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3239 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3240 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3241 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3242 ei->change_page[j].actual_trigger_ce_value = 0;
3243 ei->change_page[j].actual_trigger_ce_score = 0;
3247 // ---------- initialize trigger events -------------------------------------
3249 // initialize trigger events information
3250 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3251 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3252 trigger_events[i][j] = FALSE;
3254 // add trigger events from element change event properties
3255 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3257 struct ElementInfo *ei = &element_info[i];
3259 for (j = 0; j < ei->num_change_pages; j++)
3261 if (!ei->change_page[j].can_change_or_has_action)
3264 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3266 int trigger_element = ei->change_page[j].trigger_element;
3268 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3270 if (ei->change_page[j].has_event[k])
3272 if (IS_GROUP_ELEMENT(trigger_element))
3274 struct ElementGroupInfo *group =
3275 element_info[trigger_element].group;
3277 for (l = 0; l < group->num_elements_resolved; l++)
3278 trigger_events[group->element_resolved[l]][k] = TRUE;
3280 else if (trigger_element == EL_ANY_ELEMENT)
3281 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3282 trigger_events[l][k] = TRUE;
3284 trigger_events[trigger_element][k] = TRUE;
3291 // ---------- initialize push delay -----------------------------------------
3293 // initialize push delay values to default
3294 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3296 if (!IS_CUSTOM_ELEMENT(i))
3298 // set default push delay values (corrected since version 3.0.7-1)
3299 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3301 element_info[i].push_delay_fixed = 2;
3302 element_info[i].push_delay_random = 8;
3306 element_info[i].push_delay_fixed = 8;
3307 element_info[i].push_delay_random = 8;
3312 // set push delay value for certain elements from pre-defined list
3313 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3315 int e = push_delay_list[i].element;
3317 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3318 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3321 // set push delay value for Supaplex elements for newer engine versions
3322 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3324 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3326 if (IS_SP_ELEMENT(i))
3328 // set SP push delay to just enough to push under a falling zonk
3329 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3331 element_info[i].push_delay_fixed = delay;
3332 element_info[i].push_delay_random = 0;
3337 // ---------- initialize move stepsize --------------------------------------
3339 // initialize move stepsize values to default
3340 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3341 if (!IS_CUSTOM_ELEMENT(i))
3342 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3344 // set move stepsize value for certain elements from pre-defined list
3345 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3347 int e = move_stepsize_list[i].element;
3349 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3351 // set move stepsize value for certain elements for older engine versions
3352 if (use_old_move_stepsize_for_magic_wall)
3354 if (e == EL_MAGIC_WALL_FILLING ||
3355 e == EL_MAGIC_WALL_EMPTYING ||
3356 e == EL_BD_MAGIC_WALL_FILLING ||
3357 e == EL_BD_MAGIC_WALL_EMPTYING)
3358 element_info[e].move_stepsize *= 2;
3362 // ---------- initialize collect score --------------------------------------
3364 // initialize collect score values for custom elements from initial value
3365 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3366 if (IS_CUSTOM_ELEMENT(i))
3367 element_info[i].collect_score = element_info[i].collect_score_initial;
3369 // ---------- initialize collect count --------------------------------------
3371 // initialize collect count values for non-custom elements
3372 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3373 if (!IS_CUSTOM_ELEMENT(i))
3374 element_info[i].collect_count_initial = 0;
3376 // add collect count values for all elements from pre-defined list
3377 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3378 element_info[collect_count_list[i].element].collect_count_initial =
3379 collect_count_list[i].count;
3381 // ---------- initialize access direction -----------------------------------
3383 // initialize access direction values to default (access from every side)
3384 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3385 if (!IS_CUSTOM_ELEMENT(i))
3386 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3388 // set access direction value for certain elements from pre-defined list
3389 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3390 element_info[access_direction_list[i].element].access_direction =
3391 access_direction_list[i].direction;
3393 // ---------- initialize explosion content ----------------------------------
3394 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3396 if (IS_CUSTOM_ELEMENT(i))
3399 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3401 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3403 element_info[i].content.e[x][y] =
3404 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3405 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3406 i == EL_PLAYER_3 ? EL_EMERALD :
3407 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3408 i == EL_MOLE ? EL_EMERALD_RED :
3409 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3410 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3411 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3412 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3413 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3414 i == EL_WALL_EMERALD ? EL_EMERALD :
3415 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3416 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3417 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3418 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3419 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3420 i == EL_WALL_PEARL ? EL_PEARL :
3421 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3426 // ---------- initialize recursion detection --------------------------------
3427 recursion_loop_depth = 0;
3428 recursion_loop_detected = FALSE;
3429 recursion_loop_element = EL_UNDEFINED;
3431 // ---------- initialize graphics engine ------------------------------------
3432 game.scroll_delay_value =
3433 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3434 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3435 !setup.forced_scroll_delay ? 0 :
3436 setup.scroll_delay ? setup.scroll_delay_value : 0);
3437 game.scroll_delay_value =
3438 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3440 // ---------- initialize game engine snapshots ------------------------------
3441 for (i = 0; i < MAX_PLAYERS; i++)
3442 game.snapshot.last_action[i] = 0;
3443 game.snapshot.changed_action = FALSE;
3444 game.snapshot.collected_item = FALSE;
3445 game.snapshot.mode =
3446 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3447 SNAPSHOT_MODE_EVERY_STEP :
3448 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3449 SNAPSHOT_MODE_EVERY_MOVE :
3450 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3451 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3452 game.snapshot.save_snapshot = FALSE;
3454 // ---------- initialize level time for Supaplex engine ---------------------
3455 // Supaplex levels with time limit currently unsupported -- should be added
3456 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3459 // ---------- initialize flags for handling game actions --------------------
3461 // set flags for game actions to default values
3462 game.use_key_actions = TRUE;
3463 game.use_mouse_actions = FALSE;
3465 // when using Mirror Magic game engine, handle mouse events only
3466 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3468 game.use_key_actions = FALSE;
3469 game.use_mouse_actions = TRUE;
3472 // check for custom elements with mouse click events
3473 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3475 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3477 int element = EL_CUSTOM_START + i;
3479 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3480 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3481 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3482 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3483 game.use_mouse_actions = TRUE;
3488 static int get_num_special_action(int element, int action_first,
3491 int num_special_action = 0;
3494 for (i = action_first; i <= action_last; i++)
3496 boolean found = FALSE;
3498 for (j = 0; j < NUM_DIRECTIONS; j++)
3499 if (el_act_dir2img(element, i, j) !=
3500 el_act_dir2img(element, ACTION_DEFAULT, j))
3504 num_special_action++;
3509 return num_special_action;
3513 // ============================================================================
3515 // ----------------------------------------------------------------------------
3516 // initialize and start new game
3517 // ============================================================================
3519 #if DEBUG_INIT_PLAYER
3520 static void DebugPrintPlayerStatus(char *message)
3527 Debug("game:init:player", "%s:", message);
3529 for (i = 0; i < MAX_PLAYERS; i++)
3531 struct PlayerInfo *player = &stored_player[i];
3533 Debug("game:init:player",
3534 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3538 player->connected_locally,
3539 player->connected_network,
3541 (local_player == player ? " (local player)" : ""));
3548 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3549 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3550 int fade_mask = REDRAW_FIELD;
3552 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3553 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3554 int initial_move_dir = MV_DOWN;
3557 // required here to update video display before fading (FIX THIS)
3558 DrawMaskedBorder(REDRAW_DOOR_2);
3560 if (!game.restart_level)
3561 CloseDoor(DOOR_CLOSE_1);
3563 SetGameStatus(GAME_MODE_PLAYING);
3565 if (level_editor_test_game)
3566 FadeSkipNextFadeOut();
3568 FadeSetEnterScreen();
3571 fade_mask = REDRAW_ALL;
3573 FadeLevelSoundsAndMusic();
3575 ExpireSoundLoops(TRUE);
3579 if (level_editor_test_game)
3580 FadeSkipNextFadeIn();
3582 // needed if different viewport properties defined for playing
3583 ChangeViewportPropertiesIfNeeded();
3587 DrawCompleteVideoDisplay();
3589 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3592 InitGameControlValues();
3596 // initialize tape actions from game when recording tape
3597 tape.use_key_actions = game.use_key_actions;
3598 tape.use_mouse_actions = game.use_mouse_actions;
3600 // initialize visible playfield size when recording tape (for team mode)
3601 tape.scr_fieldx = SCR_FIELDX;
3602 tape.scr_fieldy = SCR_FIELDY;
3605 // don't play tapes over network
3606 network_playing = (network.enabled && !tape.playing);
3608 for (i = 0; i < MAX_PLAYERS; i++)
3610 struct PlayerInfo *player = &stored_player[i];
3612 player->index_nr = i;
3613 player->index_bit = (1 << i);
3614 player->element_nr = EL_PLAYER_1 + i;
3616 player->present = FALSE;
3617 player->active = FALSE;
3618 player->mapped = FALSE;
3620 player->killed = FALSE;
3621 player->reanimated = FALSE;
3622 player->buried = FALSE;
3625 player->effective_action = 0;
3626 player->programmed_action = 0;
3627 player->snap_action = 0;
3629 player->mouse_action.lx = 0;
3630 player->mouse_action.ly = 0;
3631 player->mouse_action.button = 0;
3632 player->mouse_action.button_hint = 0;
3634 player->effective_mouse_action.lx = 0;
3635 player->effective_mouse_action.ly = 0;
3636 player->effective_mouse_action.button = 0;
3637 player->effective_mouse_action.button_hint = 0;
3639 for (j = 0; j < MAX_NUM_KEYS; j++)
3640 player->key[j] = FALSE;
3642 player->num_white_keys = 0;
3644 player->dynabomb_count = 0;
3645 player->dynabomb_size = 1;
3646 player->dynabombs_left = 0;
3647 player->dynabomb_xl = FALSE;
3649 player->MovDir = initial_move_dir;
3652 player->GfxDir = initial_move_dir;
3653 player->GfxAction = ACTION_DEFAULT;
3655 player->StepFrame = 0;
3657 player->initial_element = player->element_nr;
3658 player->artwork_element =
3659 (level.use_artwork_element[i] ? level.artwork_element[i] :
3660 player->element_nr);
3661 player->use_murphy = FALSE;
3663 player->block_last_field = FALSE; // initialized in InitPlayerField()
3664 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3666 player->gravity = level.initial_player_gravity[i];
3668 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3670 player->actual_frame_counter = 0;
3672 player->step_counter = 0;
3674 player->last_move_dir = initial_move_dir;
3676 player->is_active = FALSE;
3678 player->is_waiting = FALSE;
3679 player->is_moving = FALSE;
3680 player->is_auto_moving = FALSE;
3681 player->is_digging = FALSE;
3682 player->is_snapping = FALSE;
3683 player->is_collecting = FALSE;
3684 player->is_pushing = FALSE;
3685 player->is_switching = FALSE;
3686 player->is_dropping = FALSE;
3687 player->is_dropping_pressed = FALSE;
3689 player->is_bored = FALSE;
3690 player->is_sleeping = FALSE;
3692 player->was_waiting = TRUE;
3693 player->was_moving = FALSE;
3694 player->was_snapping = FALSE;
3695 player->was_dropping = FALSE;
3697 player->force_dropping = FALSE;
3699 player->frame_counter_bored = -1;
3700 player->frame_counter_sleeping = -1;
3702 player->anim_delay_counter = 0;
3703 player->post_delay_counter = 0;
3705 player->dir_waiting = initial_move_dir;
3706 player->action_waiting = ACTION_DEFAULT;
3707 player->last_action_waiting = ACTION_DEFAULT;
3708 player->special_action_bored = ACTION_DEFAULT;
3709 player->special_action_sleeping = ACTION_DEFAULT;
3711 player->switch_x = -1;
3712 player->switch_y = -1;
3714 player->drop_x = -1;
3715 player->drop_y = -1;
3717 player->show_envelope = 0;
3719 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3721 player->push_delay = -1; // initialized when pushing starts
3722 player->push_delay_value = game.initial_push_delay_value;
3724 player->drop_delay = 0;
3725 player->drop_pressed_delay = 0;
3727 player->last_jx = -1;
3728 player->last_jy = -1;
3732 player->shield_normal_time_left = 0;
3733 player->shield_deadly_time_left = 0;
3735 player->last_removed_element = EL_UNDEFINED;
3737 player->inventory_infinite_element = EL_UNDEFINED;
3738 player->inventory_size = 0;
3740 if (level.use_initial_inventory[i])
3742 for (j = 0; j < level.initial_inventory_size[i]; j++)
3744 int element = level.initial_inventory_content[i][j];
3745 int collect_count = element_info[element].collect_count_initial;
3748 if (!IS_CUSTOM_ELEMENT(element))
3751 if (collect_count == 0)
3752 player->inventory_infinite_element = element;
3754 for (k = 0; k < collect_count; k++)
3755 if (player->inventory_size < MAX_INVENTORY_SIZE)
3756 player->inventory_element[player->inventory_size++] = element;
3760 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3761 SnapField(player, 0, 0);
3763 map_player_action[i] = i;
3766 network_player_action_received = FALSE;
3768 // initial null action
3769 if (network_playing)
3770 SendToServer_MovePlayer(MV_NONE);
3775 TimeLeft = level.time;
3778 ScreenMovDir = MV_NONE;
3782 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3784 game.robot_wheel_x = -1;
3785 game.robot_wheel_y = -1;
3790 game.all_players_gone = FALSE;
3792 game.LevelSolved = FALSE;
3793 game.GameOver = FALSE;
3795 game.GamePlayed = !tape.playing;
3797 game.LevelSolved_GameWon = FALSE;
3798 game.LevelSolved_GameEnd = FALSE;
3799 game.LevelSolved_SaveTape = FALSE;
3800 game.LevelSolved_SaveScore = FALSE;
3802 game.LevelSolved_CountingTime = 0;
3803 game.LevelSolved_CountingScore = 0;
3804 game.LevelSolved_CountingHealth = 0;
3806 game.panel.active = TRUE;
3808 game.no_time_limit = (level.time == 0);
3810 game.yamyam_content_nr = 0;
3811 game.robot_wheel_active = FALSE;
3812 game.magic_wall_active = FALSE;
3813 game.magic_wall_time_left = 0;
3814 game.light_time_left = 0;
3815 game.timegate_time_left = 0;
3816 game.switchgate_pos = 0;
3817 game.wind_direction = level.wind_direction_initial;
3819 game.time_final = 0;
3820 game.score_time_final = 0;
3823 game.score_final = 0;
3825 game.health = MAX_HEALTH;
3826 game.health_final = MAX_HEALTH;
3828 game.gems_still_needed = level.gems_needed;
3829 game.sokoban_fields_still_needed = 0;
3830 game.sokoban_objects_still_needed = 0;
3831 game.lights_still_needed = 0;
3832 game.players_still_needed = 0;
3833 game.friends_still_needed = 0;
3835 game.lenses_time_left = 0;
3836 game.magnify_time_left = 0;
3838 game.ball_active = level.ball_active_initial;
3839 game.ball_content_nr = 0;
3841 game.explosions_delayed = TRUE;
3843 game.envelope_active = FALSE;
3845 for (i = 0; i < NUM_BELTS; i++)
3847 game.belt_dir[i] = MV_NONE;
3848 game.belt_dir_nr[i] = 3; // not moving, next moving left
3851 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3852 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3854 #if DEBUG_INIT_PLAYER
3855 DebugPrintPlayerStatus("Player status at level initialization");
3858 SCAN_PLAYFIELD(x, y)
3860 Tile[x][y] = Last[x][y] = level.field[x][y];
3861 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3862 ChangeDelay[x][y] = 0;
3863 ChangePage[x][y] = -1;
3864 CustomValue[x][y] = 0; // initialized in InitField()
3865 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3867 WasJustMoving[x][y] = 0;
3868 WasJustFalling[x][y] = 0;
3869 CheckCollision[x][y] = 0;
3870 CheckImpact[x][y] = 0;
3872 Pushed[x][y] = FALSE;
3874 ChangeCount[x][y] = 0;
3875 ChangeEvent[x][y] = -1;
3877 ExplodePhase[x][y] = 0;
3878 ExplodeDelay[x][y] = 0;
3879 ExplodeField[x][y] = EX_TYPE_NONE;
3881 RunnerVisit[x][y] = 0;
3882 PlayerVisit[x][y] = 0;
3885 GfxRandom[x][y] = INIT_GFX_RANDOM();
3886 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
3887 GfxElement[x][y] = EL_UNDEFINED;
3888 GfxElementEmpty[x][y] = EL_EMPTY;
3889 GfxAction[x][y] = ACTION_DEFAULT;
3890 GfxDir[x][y] = MV_NONE;
3891 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3894 SCAN_PLAYFIELD(x, y)
3896 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3898 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3901 InitField(x, y, TRUE);
3903 ResetGfxAnimation(x, y);
3908 for (i = 0; i < MAX_PLAYERS; i++)
3910 struct PlayerInfo *player = &stored_player[i];
3912 // set number of special actions for bored and sleeping animation
3913 player->num_special_action_bored =
3914 get_num_special_action(player->artwork_element,
3915 ACTION_BORING_1, ACTION_BORING_LAST);
3916 player->num_special_action_sleeping =
3917 get_num_special_action(player->artwork_element,
3918 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3921 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3922 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3924 // initialize type of slippery elements
3925 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3927 if (!IS_CUSTOM_ELEMENT(i))
3929 // default: elements slip down either to the left or right randomly
3930 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3932 // SP style elements prefer to slip down on the left side
3933 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3934 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3936 // BD style elements prefer to slip down on the left side
3937 if (game.emulation == EMU_BOULDERDASH)
3938 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3942 // initialize explosion and ignition delay
3943 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3945 if (!IS_CUSTOM_ELEMENT(i))
3948 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3949 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3950 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3951 int last_phase = (num_phase + 1) * delay;
3952 int half_phase = (num_phase / 2) * delay;
3954 element_info[i].explosion_delay = last_phase - 1;
3955 element_info[i].ignition_delay = half_phase;
3957 if (i == EL_BLACK_ORB)
3958 element_info[i].ignition_delay = 1;
3962 // correct non-moving belts to start moving left
3963 for (i = 0; i < NUM_BELTS; i++)
3964 if (game.belt_dir[i] == MV_NONE)
3965 game.belt_dir_nr[i] = 3; // not moving, next moving left
3967 #if USE_NEW_PLAYER_ASSIGNMENTS
3968 // use preferred player also in local single-player mode
3969 if (!network.enabled && !game.team_mode)
3971 int new_index_nr = setup.network_player_nr;
3973 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3975 for (i = 0; i < MAX_PLAYERS; i++)
3976 stored_player[i].connected_locally = FALSE;
3978 stored_player[new_index_nr].connected_locally = TRUE;
3982 for (i = 0; i < MAX_PLAYERS; i++)
3984 stored_player[i].connected = FALSE;
3986 // in network game mode, the local player might not be the first player
3987 if (stored_player[i].connected_locally)
3988 local_player = &stored_player[i];
3991 if (!network.enabled)
3992 local_player->connected = TRUE;
3996 for (i = 0; i < MAX_PLAYERS; i++)
3997 stored_player[i].connected = tape.player_participates[i];
3999 else if (network.enabled)
4001 // add team mode players connected over the network (needed for correct
4002 // assignment of player figures from level to locally playing players)
4004 for (i = 0; i < MAX_PLAYERS; i++)
4005 if (stored_player[i].connected_network)
4006 stored_player[i].connected = TRUE;
4008 else if (game.team_mode)
4010 // try to guess locally connected team mode players (needed for correct
4011 // assignment of player figures from level to locally playing players)
4013 for (i = 0; i < MAX_PLAYERS; i++)
4014 if (setup.input[i].use_joystick ||
4015 setup.input[i].key.left != KSYM_UNDEFINED)
4016 stored_player[i].connected = TRUE;
4019 #if DEBUG_INIT_PLAYER
4020 DebugPrintPlayerStatus("Player status after level initialization");
4023 #if DEBUG_INIT_PLAYER
4024 Debug("game:init:player", "Reassigning players ...");
4027 // check if any connected player was not found in playfield
4028 for (i = 0; i < MAX_PLAYERS; i++)
4030 struct PlayerInfo *player = &stored_player[i];
4032 if (player->connected && !player->present)
4034 struct PlayerInfo *field_player = NULL;
4036 #if DEBUG_INIT_PLAYER
4037 Debug("game:init:player",
4038 "- looking for field player for player %d ...", i + 1);
4041 // assign first free player found that is present in the playfield
4043 // first try: look for unmapped playfield player that is not connected
4044 for (j = 0; j < MAX_PLAYERS; j++)
4045 if (field_player == NULL &&
4046 stored_player[j].present &&
4047 !stored_player[j].mapped &&
4048 !stored_player[j].connected)
4049 field_player = &stored_player[j];
4051 // second try: look for *any* unmapped playfield player
4052 for (j = 0; j < MAX_PLAYERS; j++)
4053 if (field_player == NULL &&
4054 stored_player[j].present &&
4055 !stored_player[j].mapped)
4056 field_player = &stored_player[j];
4058 if (field_player != NULL)
4060 int jx = field_player->jx, jy = field_player->jy;
4062 #if DEBUG_INIT_PLAYER
4063 Debug("game:init:player", "- found player %d",
4064 field_player->index_nr + 1);
4067 player->present = FALSE;
4068 player->active = FALSE;
4070 field_player->present = TRUE;
4071 field_player->active = TRUE;
4074 player->initial_element = field_player->initial_element;
4075 player->artwork_element = field_player->artwork_element;
4077 player->block_last_field = field_player->block_last_field;
4078 player->block_delay_adjustment = field_player->block_delay_adjustment;
4081 StorePlayer[jx][jy] = field_player->element_nr;
4083 field_player->jx = field_player->last_jx = jx;
4084 field_player->jy = field_player->last_jy = jy;
4086 if (local_player == player)
4087 local_player = field_player;
4089 map_player_action[field_player->index_nr] = i;
4091 field_player->mapped = TRUE;
4093 #if DEBUG_INIT_PLAYER
4094 Debug("game:init:player", "- map_player_action[%d] == %d",
4095 field_player->index_nr + 1, i + 1);
4100 if (player->connected && player->present)
4101 player->mapped = TRUE;
4104 #if DEBUG_INIT_PLAYER
4105 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4110 // check if any connected player was not found in playfield
4111 for (i = 0; i < MAX_PLAYERS; i++)
4113 struct PlayerInfo *player = &stored_player[i];
4115 if (player->connected && !player->present)
4117 for (j = 0; j < MAX_PLAYERS; j++)
4119 struct PlayerInfo *field_player = &stored_player[j];
4120 int jx = field_player->jx, jy = field_player->jy;
4122 // assign first free player found that is present in the playfield
4123 if (field_player->present && !field_player->connected)
4125 player->present = TRUE;
4126 player->active = TRUE;
4128 field_player->present = FALSE;
4129 field_player->active = FALSE;
4131 player->initial_element = field_player->initial_element;
4132 player->artwork_element = field_player->artwork_element;
4134 player->block_last_field = field_player->block_last_field;
4135 player->block_delay_adjustment = field_player->block_delay_adjustment;
4137 StorePlayer[jx][jy] = player->element_nr;
4139 player->jx = player->last_jx = jx;
4140 player->jy = player->last_jy = jy;
4150 Debug("game:init:player", "local_player->present == %d",
4151 local_player->present);
4154 // set focus to local player for network games, else to all players
4155 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4156 game.centered_player_nr_next = game.centered_player_nr;
4157 game.set_centered_player = FALSE;
4158 game.set_centered_player_wrap = FALSE;
4160 if (network_playing && tape.recording)
4162 // store client dependent player focus when recording network games
4163 tape.centered_player_nr_next = game.centered_player_nr_next;
4164 tape.set_centered_player = TRUE;
4169 // when playing a tape, eliminate all players who do not participate
4171 #if USE_NEW_PLAYER_ASSIGNMENTS
4173 if (!game.team_mode)
4175 for (i = 0; i < MAX_PLAYERS; i++)
4177 if (stored_player[i].active &&
4178 !tape.player_participates[map_player_action[i]])
4180 struct PlayerInfo *player = &stored_player[i];
4181 int jx = player->jx, jy = player->jy;
4183 #if DEBUG_INIT_PLAYER
4184 Debug("game:init:player", "Removing player %d at (%d, %d)",
4188 player->active = FALSE;
4189 StorePlayer[jx][jy] = 0;
4190 Tile[jx][jy] = EL_EMPTY;
4197 for (i = 0; i < MAX_PLAYERS; i++)
4199 if (stored_player[i].active &&
4200 !tape.player_participates[i])
4202 struct PlayerInfo *player = &stored_player[i];
4203 int jx = player->jx, jy = player->jy;
4205 player->active = FALSE;
4206 StorePlayer[jx][jy] = 0;
4207 Tile[jx][jy] = EL_EMPTY;
4212 else if (!network.enabled && !game.team_mode) // && !tape.playing
4214 // when in single player mode, eliminate all but the local player
4216 for (i = 0; i < MAX_PLAYERS; i++)
4218 struct PlayerInfo *player = &stored_player[i];
4220 if (player->active && player != local_player)
4222 int jx = player->jx, jy = player->jy;
4224 player->active = FALSE;
4225 player->present = FALSE;
4227 StorePlayer[jx][jy] = 0;
4228 Tile[jx][jy] = EL_EMPTY;
4233 for (i = 0; i < MAX_PLAYERS; i++)
4234 if (stored_player[i].active)
4235 game.players_still_needed++;
4237 if (level.solved_by_one_player)
4238 game.players_still_needed = 1;
4240 // when recording the game, store which players take part in the game
4243 #if USE_NEW_PLAYER_ASSIGNMENTS
4244 for (i = 0; i < MAX_PLAYERS; i++)
4245 if (stored_player[i].connected)
4246 tape.player_participates[i] = TRUE;
4248 for (i = 0; i < MAX_PLAYERS; i++)
4249 if (stored_player[i].active)
4250 tape.player_participates[i] = TRUE;
4254 #if DEBUG_INIT_PLAYER
4255 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4258 if (BorderElement == EL_EMPTY)
4261 SBX_Right = lev_fieldx - SCR_FIELDX;
4263 SBY_Lower = lev_fieldy - SCR_FIELDY;
4268 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4270 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4273 if (full_lev_fieldx <= SCR_FIELDX)
4274 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4275 if (full_lev_fieldy <= SCR_FIELDY)
4276 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4278 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4280 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4283 // if local player not found, look for custom element that might create
4284 // the player (make some assumptions about the right custom element)
4285 if (!local_player->present)
4287 int start_x = 0, start_y = 0;
4288 int found_rating = 0;
4289 int found_element = EL_UNDEFINED;
4290 int player_nr = local_player->index_nr;
4292 SCAN_PLAYFIELD(x, y)
4294 int element = Tile[x][y];
4299 if (level.use_start_element[player_nr] &&
4300 level.start_element[player_nr] == element &&
4307 found_element = element;
4310 if (!IS_CUSTOM_ELEMENT(element))
4313 if (CAN_CHANGE(element))
4315 for (i = 0; i < element_info[element].num_change_pages; i++)
4317 // check for player created from custom element as single target
4318 content = element_info[element].change_page[i].target_element;
4319 is_player = IS_PLAYER_ELEMENT(content);
4321 if (is_player && (found_rating < 3 ||
4322 (found_rating == 3 && element < found_element)))
4328 found_element = element;
4333 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4335 // check for player created from custom element as explosion content
4336 content = element_info[element].content.e[xx][yy];
4337 is_player = IS_PLAYER_ELEMENT(content);
4339 if (is_player && (found_rating < 2 ||
4340 (found_rating == 2 && element < found_element)))
4342 start_x = x + xx - 1;
4343 start_y = y + yy - 1;
4346 found_element = element;
4349 if (!CAN_CHANGE(element))
4352 for (i = 0; i < element_info[element].num_change_pages; i++)
4354 // check for player created from custom element as extended target
4356 element_info[element].change_page[i].target_content.e[xx][yy];
4358 is_player = IS_PLAYER_ELEMENT(content);
4360 if (is_player && (found_rating < 1 ||
4361 (found_rating == 1 && element < found_element)))
4363 start_x = x + xx - 1;
4364 start_y = y + yy - 1;
4367 found_element = element;
4373 scroll_x = SCROLL_POSITION_X(start_x);
4374 scroll_y = SCROLL_POSITION_Y(start_y);
4378 scroll_x = SCROLL_POSITION_X(local_player->jx);
4379 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4382 // !!! FIX THIS (START) !!!
4383 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4385 InitGameEngine_EM();
4387 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4389 InitGameEngine_SP();
4391 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4393 InitGameEngine_MM();
4397 DrawLevel(REDRAW_FIELD);
4400 // after drawing the level, correct some elements
4401 if (game.timegate_time_left == 0)
4402 CloseAllOpenTimegates();
4405 // blit playfield from scroll buffer to normal back buffer for fading in
4406 BlitScreenToBitmap(backbuffer);
4407 // !!! FIX THIS (END) !!!
4409 DrawMaskedBorder(fade_mask);
4414 // full screen redraw is required at this point in the following cases:
4415 // - special editor door undrawn when game was started from level editor
4416 // - drawing area (playfield) was changed and has to be removed completely
4417 redraw_mask = REDRAW_ALL;
4421 if (!game.restart_level)
4423 // copy default game door content to main double buffer
4425 // !!! CHECK AGAIN !!!
4426 SetPanelBackground();
4427 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4428 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4431 SetPanelBackground();
4432 SetDrawBackgroundMask(REDRAW_DOOR_1);
4434 UpdateAndDisplayGameControlValues();
4436 if (!game.restart_level)
4442 CreateGameButtons();
4447 // copy actual game door content to door double buffer for OpenDoor()
4448 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4450 OpenDoor(DOOR_OPEN_ALL);
4452 KeyboardAutoRepeatOffUnlessAutoplay();
4454 #if DEBUG_INIT_PLAYER
4455 DebugPrintPlayerStatus("Player status (final)");
4464 if (!game.restart_level && !tape.playing)
4466 LevelStats_incPlayed(level_nr);
4468 SaveLevelSetup_SeriesInfo();
4471 game.restart_level = FALSE;
4472 game.restart_game_message = NULL;
4474 game.request_active = FALSE;
4475 game.request_active_or_moving = FALSE;
4477 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4478 InitGameActions_MM();
4480 SaveEngineSnapshotToListInitial();
4482 if (!game.restart_level)
4484 PlaySound(SND_GAME_STARTING);
4486 if (setup.sound_music)
4490 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4493 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4494 int actual_player_x, int actual_player_y)
4496 // this is used for non-R'n'D game engines to update certain engine values
4498 // needed to determine if sounds are played within the visible screen area
4499 scroll_x = actual_scroll_x;
4500 scroll_y = actual_scroll_y;
4502 // needed to get player position for "follow finger" playing input method
4503 local_player->jx = actual_player_x;
4504 local_player->jy = actual_player_y;
4507 void InitMovDir(int x, int y)
4509 int i, element = Tile[x][y];
4510 static int xy[4][2] =
4517 static int direction[3][4] =
4519 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4520 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4521 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4530 Tile[x][y] = EL_BUG;
4531 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4534 case EL_SPACESHIP_RIGHT:
4535 case EL_SPACESHIP_UP:
4536 case EL_SPACESHIP_LEFT:
4537 case EL_SPACESHIP_DOWN:
4538 Tile[x][y] = EL_SPACESHIP;
4539 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4542 case EL_BD_BUTTERFLY_RIGHT:
4543 case EL_BD_BUTTERFLY_UP:
4544 case EL_BD_BUTTERFLY_LEFT:
4545 case EL_BD_BUTTERFLY_DOWN:
4546 Tile[x][y] = EL_BD_BUTTERFLY;
4547 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4550 case EL_BD_FIREFLY_RIGHT:
4551 case EL_BD_FIREFLY_UP:
4552 case EL_BD_FIREFLY_LEFT:
4553 case EL_BD_FIREFLY_DOWN:
4554 Tile[x][y] = EL_BD_FIREFLY;
4555 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4558 case EL_PACMAN_RIGHT:
4560 case EL_PACMAN_LEFT:
4561 case EL_PACMAN_DOWN:
4562 Tile[x][y] = EL_PACMAN;
4563 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4566 case EL_YAMYAM_LEFT:
4567 case EL_YAMYAM_RIGHT:
4569 case EL_YAMYAM_DOWN:
4570 Tile[x][y] = EL_YAMYAM;
4571 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4574 case EL_SP_SNIKSNAK:
4575 MovDir[x][y] = MV_UP;
4578 case EL_SP_ELECTRON:
4579 MovDir[x][y] = MV_LEFT;
4586 Tile[x][y] = EL_MOLE;
4587 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4590 case EL_SPRING_LEFT:
4591 case EL_SPRING_RIGHT:
4592 Tile[x][y] = EL_SPRING;
4593 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4597 if (IS_CUSTOM_ELEMENT(element))
4599 struct ElementInfo *ei = &element_info[element];
4600 int move_direction_initial = ei->move_direction_initial;
4601 int move_pattern = ei->move_pattern;
4603 if (move_direction_initial == MV_START_PREVIOUS)
4605 if (MovDir[x][y] != MV_NONE)
4608 move_direction_initial = MV_START_AUTOMATIC;
4611 if (move_direction_initial == MV_START_RANDOM)
4612 MovDir[x][y] = 1 << RND(4);
4613 else if (move_direction_initial & MV_ANY_DIRECTION)
4614 MovDir[x][y] = move_direction_initial;
4615 else if (move_pattern == MV_ALL_DIRECTIONS ||
4616 move_pattern == MV_TURNING_LEFT ||
4617 move_pattern == MV_TURNING_RIGHT ||
4618 move_pattern == MV_TURNING_LEFT_RIGHT ||
4619 move_pattern == MV_TURNING_RIGHT_LEFT ||
4620 move_pattern == MV_TURNING_RANDOM)
4621 MovDir[x][y] = 1 << RND(4);
4622 else if (move_pattern == MV_HORIZONTAL)
4623 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4624 else if (move_pattern == MV_VERTICAL)
4625 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4626 else if (move_pattern & MV_ANY_DIRECTION)
4627 MovDir[x][y] = element_info[element].move_pattern;
4628 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4629 move_pattern == MV_ALONG_RIGHT_SIDE)
4631 // use random direction as default start direction
4632 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4633 MovDir[x][y] = 1 << RND(4);
4635 for (i = 0; i < NUM_DIRECTIONS; i++)
4637 int x1 = x + xy[i][0];
4638 int y1 = y + xy[i][1];
4640 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4642 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4643 MovDir[x][y] = direction[0][i];
4645 MovDir[x][y] = direction[1][i];
4654 MovDir[x][y] = 1 << RND(4);
4656 if (element != EL_BUG &&
4657 element != EL_SPACESHIP &&
4658 element != EL_BD_BUTTERFLY &&
4659 element != EL_BD_FIREFLY)
4662 for (i = 0; i < NUM_DIRECTIONS; i++)
4664 int x1 = x + xy[i][0];
4665 int y1 = y + xy[i][1];
4667 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4669 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4671 MovDir[x][y] = direction[0][i];
4674 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4675 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4677 MovDir[x][y] = direction[1][i];
4686 GfxDir[x][y] = MovDir[x][y];
4689 void InitAmoebaNr(int x, int y)
4692 int group_nr = AmoebaNeighbourNr(x, y);
4696 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4698 if (AmoebaCnt[i] == 0)
4706 AmoebaNr[x][y] = group_nr;
4707 AmoebaCnt[group_nr]++;
4708 AmoebaCnt2[group_nr]++;
4711 static void LevelSolved_SetFinalGameValues(void)
4713 game.time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4714 game.score_time_final = (level.use_step_counter ? TimePlayed :
4715 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4717 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4718 game_em.lev->score :
4719 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4723 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4724 MM_HEALTH(game_mm.laser_overload_value) :
4727 game.LevelSolved_CountingTime = game.time_final;
4728 game.LevelSolved_CountingScore = game.score_final;
4729 game.LevelSolved_CountingHealth = game.health_final;
4732 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4734 game.LevelSolved_CountingTime = time;
4735 game.LevelSolved_CountingScore = score;
4736 game.LevelSolved_CountingHealth = health;
4738 game_panel_controls[GAME_PANEL_TIME].value = time;
4739 game_panel_controls[GAME_PANEL_SCORE].value = score;
4740 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4742 DisplayGameControlValues();
4745 static void LevelSolved(void)
4747 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4748 game.players_still_needed > 0)
4751 game.LevelSolved = TRUE;
4752 game.GameOver = TRUE;
4754 // needed here to display correct panel values while player walks into exit
4755 LevelSolved_SetFinalGameValues();
4760 static int time_count_steps;
4761 static int time, time_final;
4762 static float score, score_final; // needed for time score < 10 for 10 seconds
4763 static int health, health_final;
4764 static int game_over_delay_1 = 0;
4765 static int game_over_delay_2 = 0;
4766 static int game_over_delay_3 = 0;
4767 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4768 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4770 if (!game.LevelSolved_GameWon)
4774 // do not start end game actions before the player stops moving (to exit)
4775 if (local_player->active && local_player->MovPos)
4778 // calculate final game values after player finished walking into exit
4779 LevelSolved_SetFinalGameValues();
4781 game.LevelSolved_GameWon = TRUE;
4782 game.LevelSolved_SaveTape = tape.recording;
4783 game.LevelSolved_SaveScore = !tape.playing;
4787 LevelStats_incSolved(level_nr);
4789 SaveLevelSetup_SeriesInfo();
4792 if (tape.auto_play) // tape might already be stopped here
4793 tape.auto_play_level_solved = TRUE;
4797 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4798 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4799 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4801 time = time_final = game.time_final;
4802 score = score_final = game.score_final;
4803 health = health_final = game.health_final;
4805 // update game panel values before (delayed) counting of score (if any)
4806 LevelSolved_DisplayFinalGameValues(time, score, health);
4808 // if level has time score defined, calculate new final game values
4811 int time_final_max = 999;
4812 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4813 int time_frames = 0;
4814 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4815 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4820 time_frames = time_frames_left;
4822 else if (game.no_time_limit && TimePlayed < time_final_max)
4824 time_final = time_final_max;
4825 time_frames = time_frames_final_max - time_frames_played;
4828 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4830 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4832 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4835 score_final += health * time_score;
4838 game.score_final = score_final;
4839 game.health_final = health_final;
4842 // if not counting score after game, immediately update game panel values
4843 if (level_editor_test_game || !setup.count_score_after_game)
4846 score = score_final;
4848 LevelSolved_DisplayFinalGameValues(time, score, health);
4851 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4853 // check if last player has left the level
4854 if (game.exit_x >= 0 &&
4857 int x = game.exit_x;
4858 int y = game.exit_y;
4859 int element = Tile[x][y];
4861 // close exit door after last player
4862 if ((game.all_players_gone &&
4863 (element == EL_EXIT_OPEN ||
4864 element == EL_SP_EXIT_OPEN ||
4865 element == EL_STEEL_EXIT_OPEN)) ||
4866 element == EL_EM_EXIT_OPEN ||
4867 element == EL_EM_STEEL_EXIT_OPEN)
4871 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4872 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4873 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4874 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4875 EL_EM_STEEL_EXIT_CLOSING);
4877 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4880 // player disappears
4881 DrawLevelField(x, y);
4884 for (i = 0; i < MAX_PLAYERS; i++)
4886 struct PlayerInfo *player = &stored_player[i];
4888 if (player->present)
4890 RemovePlayer(player);
4892 // player disappears
4893 DrawLevelField(player->jx, player->jy);
4898 PlaySound(SND_GAME_WINNING);
4901 if (setup.count_score_after_game)
4903 if (time != time_final)
4905 if (game_over_delay_1 > 0)
4907 game_over_delay_1--;
4912 int time_to_go = ABS(time_final - time);
4913 int time_count_dir = (time < time_final ? +1 : -1);
4915 if (time_to_go < time_count_steps)
4916 time_count_steps = 1;
4918 time += time_count_steps * time_count_dir;
4919 score += time_count_steps * time_score;
4921 // set final score to correct rounding differences after counting score
4922 if (time == time_final)
4923 score = score_final;
4925 LevelSolved_DisplayFinalGameValues(time, score, health);
4927 if (time == time_final)
4928 StopSound(SND_GAME_LEVELTIME_BONUS);
4929 else if (setup.sound_loops)
4930 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4932 PlaySound(SND_GAME_LEVELTIME_BONUS);
4937 if (health != health_final)
4939 if (game_over_delay_2 > 0)
4941 game_over_delay_2--;
4946 int health_count_dir = (health < health_final ? +1 : -1);
4948 health += health_count_dir;
4949 score += time_score;
4951 LevelSolved_DisplayFinalGameValues(time, score, health);
4953 if (health == health_final)
4954 StopSound(SND_GAME_LEVELTIME_BONUS);
4955 else if (setup.sound_loops)
4956 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4958 PlaySound(SND_GAME_LEVELTIME_BONUS);
4964 game.panel.active = FALSE;
4966 if (game_over_delay_3 > 0)
4968 game_over_delay_3--;
4978 // used instead of "level_nr" (needed for network games)
4979 int last_level_nr = levelset.level_nr;
4980 boolean tape_saved = FALSE;
4982 game.LevelSolved_GameEnd = TRUE;
4984 if (game.LevelSolved_SaveTape)
4986 // make sure that request dialog to save tape does not open door again
4987 if (!global.use_envelope_request)
4988 CloseDoor(DOOR_CLOSE_1);
4991 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
4993 // set unique basename for score tape (also saved in high score table)
4994 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
4997 // if no tape is to be saved, close both doors simultaneously
4998 CloseDoor(DOOR_CLOSE_ALL);
5000 if (level_editor_test_game)
5002 SetGameStatus(GAME_MODE_MAIN);
5009 if (!game.LevelSolved_SaveScore)
5011 SetGameStatus(GAME_MODE_MAIN);
5018 if (level_nr == leveldir_current->handicap_level)
5020 leveldir_current->handicap_level++;
5022 SaveLevelSetup_SeriesInfo();
5025 // save score and score tape before potentially erasing tape below
5026 NewHighScore(last_level_nr, tape_saved);
5028 if (setup.increment_levels &&
5029 level_nr < leveldir_current->last_level &&
5032 level_nr++; // advance to next level
5033 TapeErase(); // start with empty tape
5035 if (setup.auto_play_next_level)
5037 LoadLevel(level_nr);
5039 SaveLevelSetup_SeriesInfo();
5043 if (scores.last_added >= 0 && setup.show_scores_after_game)
5045 SetGameStatus(GAME_MODE_SCORES);
5047 DrawHallOfFame(last_level_nr);
5049 else if (setup.auto_play_next_level && setup.increment_levels &&
5050 last_level_nr < leveldir_current->last_level &&
5053 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5057 SetGameStatus(GAME_MODE_MAIN);
5063 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5064 boolean one_score_entry_per_name)
5068 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5071 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5073 struct ScoreEntry *entry = &list->entry[i];
5074 boolean score_is_better = (new_entry->score > entry->score);
5075 boolean score_is_equal = (new_entry->score == entry->score);
5076 boolean time_is_better = (new_entry->time < entry->time);
5077 boolean time_is_equal = (new_entry->time == entry->time);
5078 boolean better_by_score = (score_is_better ||
5079 (score_is_equal && time_is_better));
5080 boolean better_by_time = (time_is_better ||
5081 (time_is_equal && score_is_better));
5082 boolean is_better = (level.rate_time_over_score ? better_by_time :
5084 boolean entry_is_empty = (entry->score == 0 &&
5087 // prevent adding server score entries if also existing in local score file
5088 // (special case: historic score entries have an empty tape basename entry)
5089 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5090 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5093 if (is_better || entry_is_empty)
5095 // player has made it to the hall of fame
5097 if (i < MAX_SCORE_ENTRIES - 1)
5099 int m = MAX_SCORE_ENTRIES - 1;
5102 if (one_score_entry_per_name)
5104 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5105 if (strEqual(list->entry[l].name, new_entry->name))
5108 if (m == i) // player's new highscore overwrites his old one
5112 for (l = m; l > i; l--)
5113 list->entry[l] = list->entry[l - 1];
5118 *entry = *new_entry;
5122 else if (one_score_entry_per_name &&
5123 strEqual(entry->name, new_entry->name))
5125 // player already in high score list with better score or time
5134 void NewHighScore(int level_nr, boolean tape_saved)
5136 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5137 boolean one_per_name = FALSE;
5139 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5140 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5142 new_entry.score = game.score_final;
5143 new_entry.time = game.score_time_final;
5145 LoadScore(level_nr);
5147 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5149 if (scores.last_added < 0)
5152 SaveScore(level_nr);
5154 // store last added local score entry (before merging server scores)
5155 scores.last_added_local = scores.last_added;
5157 if (!game.LevelSolved_SaveTape)
5160 SaveScoreTape(level_nr);
5162 if (setup.ask_for_using_api_server)
5164 setup.use_api_server =
5165 Request("Upload your score and tape to the high score server?", REQ_ASK);
5167 if (!setup.use_api_server)
5168 Request("Not using high score server! Use setup menu to enable again!",
5171 runtime.use_api_server = setup.use_api_server;
5173 // after asking for using API server once, do not ask again
5174 setup.ask_for_using_api_server = FALSE;
5176 SaveSetup_ServerSetup();
5179 SaveServerScore(level_nr, tape_saved);
5182 void MergeServerScore(void)
5184 struct ScoreEntry last_added_entry;
5185 boolean one_per_name = FALSE;
5188 if (scores.last_added >= 0)
5189 last_added_entry = scores.entry[scores.last_added];
5191 for (i = 0; i < server_scores.num_entries; i++)
5193 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5195 if (pos >= 0 && pos <= scores.last_added)
5196 scores.last_added++;
5199 if (scores.last_added >= MAX_SCORE_ENTRIES)
5201 scores.last_added = MAX_SCORE_ENTRIES - 1;
5202 scores.force_last_added = TRUE;
5204 scores.entry[scores.last_added] = last_added_entry;
5208 static int getElementMoveStepsizeExt(int x, int y, int direction)
5210 int element = Tile[x][y];
5211 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5212 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5213 int horiz_move = (dx != 0);
5214 int sign = (horiz_move ? dx : dy);
5215 int step = sign * element_info[element].move_stepsize;
5217 // special values for move stepsize for spring and things on conveyor belt
5220 if (CAN_FALL(element) &&
5221 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5222 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5223 else if (element == EL_SPRING)
5224 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5230 static int getElementMoveStepsize(int x, int y)
5232 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5235 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5237 if (player->GfxAction != action || player->GfxDir != dir)
5239 player->GfxAction = action;
5240 player->GfxDir = dir;
5242 player->StepFrame = 0;
5246 static void ResetGfxFrame(int x, int y)
5248 // profiling showed that "autotest" spends 10~20% of its time in this function
5249 if (DrawingDeactivatedField())
5252 int element = Tile[x][y];
5253 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5255 if (graphic_info[graphic].anim_global_sync)
5256 GfxFrame[x][y] = FrameCounter;
5257 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5258 GfxFrame[x][y] = CustomValue[x][y];
5259 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5260 GfxFrame[x][y] = element_info[element].collect_score;
5261 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5262 GfxFrame[x][y] = ChangeDelay[x][y];
5265 static void ResetGfxAnimation(int x, int y)
5267 GfxAction[x][y] = ACTION_DEFAULT;
5268 GfxDir[x][y] = MovDir[x][y];
5271 ResetGfxFrame(x, y);
5274 static void ResetRandomAnimationValue(int x, int y)
5276 GfxRandom[x][y] = INIT_GFX_RANDOM();
5279 static void InitMovingField(int x, int y, int direction)
5281 int element = Tile[x][y];
5282 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5283 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5286 boolean is_moving_before, is_moving_after;
5288 // check if element was/is moving or being moved before/after mode change
5289 is_moving_before = (WasJustMoving[x][y] != 0);
5290 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5292 // reset animation only for moving elements which change direction of moving
5293 // or which just started or stopped moving
5294 // (else CEs with property "can move" / "not moving" are reset each frame)
5295 if (is_moving_before != is_moving_after ||
5296 direction != MovDir[x][y])
5297 ResetGfxAnimation(x, y);
5299 MovDir[x][y] = direction;
5300 GfxDir[x][y] = direction;
5302 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5303 direction == MV_DOWN && CAN_FALL(element) ?
5304 ACTION_FALLING : ACTION_MOVING);
5306 // this is needed for CEs with property "can move" / "not moving"
5308 if (is_moving_after)
5310 if (Tile[newx][newy] == EL_EMPTY)
5311 Tile[newx][newy] = EL_BLOCKED;
5313 MovDir[newx][newy] = MovDir[x][y];
5315 CustomValue[newx][newy] = CustomValue[x][y];
5317 GfxFrame[newx][newy] = GfxFrame[x][y];
5318 GfxRandom[newx][newy] = GfxRandom[x][y];
5319 GfxAction[newx][newy] = GfxAction[x][y];
5320 GfxDir[newx][newy] = GfxDir[x][y];
5324 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5326 int direction = MovDir[x][y];
5327 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5328 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5334 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5336 int oldx = x, oldy = y;
5337 int direction = MovDir[x][y];
5339 if (direction == MV_LEFT)
5341 else if (direction == MV_RIGHT)
5343 else if (direction == MV_UP)
5345 else if (direction == MV_DOWN)
5348 *comes_from_x = oldx;
5349 *comes_from_y = oldy;
5352 static int MovingOrBlocked2Element(int x, int y)
5354 int element = Tile[x][y];
5356 if (element == EL_BLOCKED)
5360 Blocked2Moving(x, y, &oldx, &oldy);
5361 return Tile[oldx][oldy];
5367 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5369 // like MovingOrBlocked2Element(), but if element is moving
5370 // and (x,y) is the field the moving element is just leaving,
5371 // return EL_BLOCKED instead of the element value
5372 int element = Tile[x][y];
5374 if (IS_MOVING(x, y))
5376 if (element == EL_BLOCKED)
5380 Blocked2Moving(x, y, &oldx, &oldy);
5381 return Tile[oldx][oldy];
5390 static void RemoveField(int x, int y)
5392 Tile[x][y] = EL_EMPTY;
5398 CustomValue[x][y] = 0;
5401 ChangeDelay[x][y] = 0;
5402 ChangePage[x][y] = -1;
5403 Pushed[x][y] = FALSE;
5405 GfxElement[x][y] = EL_UNDEFINED;
5406 GfxAction[x][y] = ACTION_DEFAULT;
5407 GfxDir[x][y] = MV_NONE;
5410 static void RemoveMovingField(int x, int y)
5412 int oldx = x, oldy = y, newx = x, newy = y;
5413 int element = Tile[x][y];
5414 int next_element = EL_UNDEFINED;
5416 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5419 if (IS_MOVING(x, y))
5421 Moving2Blocked(x, y, &newx, &newy);
5423 if (Tile[newx][newy] != EL_BLOCKED)
5425 // element is moving, but target field is not free (blocked), but
5426 // already occupied by something different (example: acid pool);
5427 // in this case, only remove the moving field, but not the target
5429 RemoveField(oldx, oldy);
5431 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5433 TEST_DrawLevelField(oldx, oldy);
5438 else if (element == EL_BLOCKED)
5440 Blocked2Moving(x, y, &oldx, &oldy);
5441 if (!IS_MOVING(oldx, oldy))
5445 if (element == EL_BLOCKED &&
5446 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5447 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5448 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5449 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5450 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5451 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5452 next_element = get_next_element(Tile[oldx][oldy]);
5454 RemoveField(oldx, oldy);
5455 RemoveField(newx, newy);
5457 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5459 if (next_element != EL_UNDEFINED)
5460 Tile[oldx][oldy] = next_element;
5462 TEST_DrawLevelField(oldx, oldy);
5463 TEST_DrawLevelField(newx, newy);
5466 void DrawDynamite(int x, int y)
5468 int sx = SCREENX(x), sy = SCREENY(y);
5469 int graphic = el2img(Tile[x][y]);
5472 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5475 if (IS_WALKABLE_INSIDE(Back[x][y]))
5479 DrawLevelElement(x, y, Back[x][y]);
5480 else if (Store[x][y])
5481 DrawLevelElement(x, y, Store[x][y]);
5482 else if (game.use_masked_elements)
5483 DrawLevelElement(x, y, EL_EMPTY);
5485 frame = getGraphicAnimationFrameXY(graphic, x, y);
5487 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5488 DrawGraphicThruMask(sx, sy, graphic, frame);
5490 DrawGraphic(sx, sy, graphic, frame);
5493 static void CheckDynamite(int x, int y)
5495 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5499 if (MovDelay[x][y] != 0)
5502 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5508 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5513 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5515 boolean num_checked_players = 0;
5518 for (i = 0; i < MAX_PLAYERS; i++)
5520 if (stored_player[i].active)
5522 int sx = stored_player[i].jx;
5523 int sy = stored_player[i].jy;
5525 if (num_checked_players == 0)
5532 *sx1 = MIN(*sx1, sx);
5533 *sy1 = MIN(*sy1, sy);
5534 *sx2 = MAX(*sx2, sx);
5535 *sy2 = MAX(*sy2, sy);
5538 num_checked_players++;
5543 static boolean checkIfAllPlayersFitToScreen_RND(void)
5545 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5547 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5549 return (sx2 - sx1 < SCR_FIELDX &&
5550 sy2 - sy1 < SCR_FIELDY);
5553 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5555 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5557 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5559 *sx = (sx1 + sx2) / 2;
5560 *sy = (sy1 + sy2) / 2;
5563 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5564 boolean center_screen, boolean quick_relocation)
5566 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5567 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5568 boolean no_delay = (tape.warp_forward);
5569 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5570 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5571 int new_scroll_x, new_scroll_y;
5573 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5575 // case 1: quick relocation inside visible screen (without scrolling)
5582 if (!level.shifted_relocation || center_screen)
5584 // relocation _with_ centering of screen
5586 new_scroll_x = SCROLL_POSITION_X(x);
5587 new_scroll_y = SCROLL_POSITION_Y(y);
5591 // relocation _without_ centering of screen
5593 int center_scroll_x = SCROLL_POSITION_X(old_x);
5594 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5595 int offset_x = x + (scroll_x - center_scroll_x);
5596 int offset_y = y + (scroll_y - center_scroll_y);
5598 // for new screen position, apply previous offset to center position
5599 new_scroll_x = SCROLL_POSITION_X(offset_x);
5600 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5603 if (quick_relocation)
5605 // case 2: quick relocation (redraw without visible scrolling)
5607 scroll_x = new_scroll_x;
5608 scroll_y = new_scroll_y;
5615 // case 3: visible relocation (with scrolling to new position)
5617 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5619 SetVideoFrameDelay(wait_delay_value);
5621 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5623 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5624 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5626 if (dx == 0 && dy == 0) // no scrolling needed at all
5632 // set values for horizontal/vertical screen scrolling (half tile size)
5633 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5634 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5635 int pos_x = dx * TILEX / 2;
5636 int pos_y = dy * TILEY / 2;
5637 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5638 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5640 ScrollLevel(dx, dy);
5643 // scroll in two steps of half tile size to make things smoother
5644 BlitScreenToBitmapExt_RND(window, fx, fy);
5646 // scroll second step to align at full tile size
5647 BlitScreenToBitmap(window);
5653 SetVideoFrameDelay(frame_delay_value_old);
5656 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5658 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5659 int player_nr = GET_PLAYER_NR(el_player);
5660 struct PlayerInfo *player = &stored_player[player_nr];
5661 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5662 boolean no_delay = (tape.warp_forward);
5663 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5664 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5665 int old_jx = player->jx;
5666 int old_jy = player->jy;
5667 int old_element = Tile[old_jx][old_jy];
5668 int element = Tile[jx][jy];
5669 boolean player_relocated = (old_jx != jx || old_jy != jy);
5671 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5672 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5673 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5674 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5675 int leave_side_horiz = move_dir_horiz;
5676 int leave_side_vert = move_dir_vert;
5677 int enter_side = enter_side_horiz | enter_side_vert;
5678 int leave_side = leave_side_horiz | leave_side_vert;
5680 if (player->buried) // do not reanimate dead player
5683 if (!player_relocated) // no need to relocate the player
5686 if (IS_PLAYER(jx, jy)) // player already placed at new position
5688 RemoveField(jx, jy); // temporarily remove newly placed player
5689 DrawLevelField(jx, jy);
5692 if (player->present)
5694 while (player->MovPos)
5696 ScrollPlayer(player, SCROLL_GO_ON);
5697 ScrollScreen(NULL, SCROLL_GO_ON);
5699 AdvanceFrameAndPlayerCounters(player->index_nr);
5703 BackToFront_WithFrameDelay(wait_delay_value);
5706 DrawPlayer(player); // needed here only to cleanup last field
5707 DrawLevelField(player->jx, player->jy); // remove player graphic
5709 player->is_moving = FALSE;
5712 if (IS_CUSTOM_ELEMENT(old_element))
5713 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5715 player->index_bit, leave_side);
5717 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5719 player->index_bit, leave_side);
5721 Tile[jx][jy] = el_player;
5722 InitPlayerField(jx, jy, el_player, TRUE);
5724 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5725 possible that the relocation target field did not contain a player element,
5726 but a walkable element, to which the new player was relocated -- in this
5727 case, restore that (already initialized!) element on the player field */
5728 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5730 Tile[jx][jy] = element; // restore previously existing element
5733 // only visually relocate centered player
5734 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5735 FALSE, level.instant_relocation);
5737 TestIfPlayerTouchesBadThing(jx, jy);
5738 TestIfPlayerTouchesCustomElement(jx, jy);
5740 if (IS_CUSTOM_ELEMENT(element))
5741 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5742 player->index_bit, enter_side);
5744 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5745 player->index_bit, enter_side);
5747 if (player->is_switching)
5749 /* ensure that relocation while still switching an element does not cause
5750 a new element to be treated as also switched directly after relocation
5751 (this is important for teleporter switches that teleport the player to
5752 a place where another teleporter switch is in the same direction, which
5753 would then incorrectly be treated as immediately switched before the
5754 direction key that caused the switch was released) */
5756 player->switch_x += jx - old_jx;
5757 player->switch_y += jy - old_jy;
5761 static void Explode(int ex, int ey, int phase, int mode)
5767 // !!! eliminate this variable !!!
5768 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5770 if (game.explosions_delayed)
5772 ExplodeField[ex][ey] = mode;
5776 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5778 int center_element = Tile[ex][ey];
5779 int artwork_element, explosion_element; // set these values later
5781 // remove things displayed in background while burning dynamite
5782 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5785 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5787 // put moving element to center field (and let it explode there)
5788 center_element = MovingOrBlocked2Element(ex, ey);
5789 RemoveMovingField(ex, ey);
5790 Tile[ex][ey] = center_element;
5793 // now "center_element" is finally determined -- set related values now
5794 artwork_element = center_element; // for custom player artwork
5795 explosion_element = center_element; // for custom player artwork
5797 if (IS_PLAYER(ex, ey))
5799 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5801 artwork_element = stored_player[player_nr].artwork_element;
5803 if (level.use_explosion_element[player_nr])
5805 explosion_element = level.explosion_element[player_nr];
5806 artwork_element = explosion_element;
5810 if (mode == EX_TYPE_NORMAL ||
5811 mode == EX_TYPE_CENTER ||
5812 mode == EX_TYPE_CROSS)
5813 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5815 last_phase = element_info[explosion_element].explosion_delay + 1;
5817 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5819 int xx = x - ex + 1;
5820 int yy = y - ey + 1;
5823 if (!IN_LEV_FIELD(x, y) ||
5824 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5825 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5828 element = Tile[x][y];
5830 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5832 element = MovingOrBlocked2Element(x, y);
5834 if (!IS_EXPLOSION_PROOF(element))
5835 RemoveMovingField(x, y);
5838 // indestructible elements can only explode in center (but not flames)
5839 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5840 mode == EX_TYPE_BORDER)) ||
5841 element == EL_FLAMES)
5844 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5845 behaviour, for example when touching a yamyam that explodes to rocks
5846 with active deadly shield, a rock is created under the player !!! */
5847 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5849 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5850 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5851 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5853 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5856 if (IS_ACTIVE_BOMB(element))
5858 // re-activate things under the bomb like gate or penguin
5859 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5866 // save walkable background elements while explosion on same tile
5867 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5868 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5869 Back[x][y] = element;
5871 // ignite explodable elements reached by other explosion
5872 if (element == EL_EXPLOSION)
5873 element = Store2[x][y];
5875 if (AmoebaNr[x][y] &&
5876 (element == EL_AMOEBA_FULL ||
5877 element == EL_BD_AMOEBA ||
5878 element == EL_AMOEBA_GROWING))
5880 AmoebaCnt[AmoebaNr[x][y]]--;
5881 AmoebaCnt2[AmoebaNr[x][y]]--;
5886 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5888 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5890 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5892 if (PLAYERINFO(ex, ey)->use_murphy)
5893 Store[x][y] = EL_EMPTY;
5896 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5897 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5898 else if (IS_PLAYER_ELEMENT(center_element))
5899 Store[x][y] = EL_EMPTY;
5900 else if (center_element == EL_YAMYAM)
5901 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5902 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5903 Store[x][y] = element_info[center_element].content.e[xx][yy];
5905 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5906 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5907 // otherwise) -- FIX THIS !!!
5908 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5909 Store[x][y] = element_info[element].content.e[1][1];
5911 else if (!CAN_EXPLODE(element))
5912 Store[x][y] = element_info[element].content.e[1][1];
5915 Store[x][y] = EL_EMPTY;
5917 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5918 center_element == EL_AMOEBA_TO_DIAMOND)
5919 Store2[x][y] = element;
5921 Tile[x][y] = EL_EXPLOSION;
5922 GfxElement[x][y] = artwork_element;
5924 ExplodePhase[x][y] = 1;
5925 ExplodeDelay[x][y] = last_phase;
5930 if (center_element == EL_YAMYAM)
5931 game.yamyam_content_nr =
5932 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5944 GfxFrame[x][y] = 0; // restart explosion animation
5946 last_phase = ExplodeDelay[x][y];
5948 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5950 // this can happen if the player leaves an explosion just in time
5951 if (GfxElement[x][y] == EL_UNDEFINED)
5952 GfxElement[x][y] = EL_EMPTY;
5954 border_element = Store2[x][y];
5955 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5956 border_element = StorePlayer[x][y];
5958 if (phase == element_info[border_element].ignition_delay ||
5959 phase == last_phase)
5961 boolean border_explosion = FALSE;
5963 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5964 !PLAYER_EXPLOSION_PROTECTED(x, y))
5966 KillPlayerUnlessExplosionProtected(x, y);
5967 border_explosion = TRUE;
5969 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5971 Tile[x][y] = Store2[x][y];
5974 border_explosion = TRUE;
5976 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5978 AmoebaToDiamond(x, y);
5980 border_explosion = TRUE;
5983 // if an element just explodes due to another explosion (chain-reaction),
5984 // do not immediately end the new explosion when it was the last frame of
5985 // the explosion (as it would be done in the following "if"-statement!)
5986 if (border_explosion && phase == last_phase)
5990 if (phase == last_phase)
5994 element = Tile[x][y] = Store[x][y];
5995 Store[x][y] = Store2[x][y] = 0;
5996 GfxElement[x][y] = EL_UNDEFINED;
5998 // player can escape from explosions and might therefore be still alive
5999 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6000 element <= EL_PLAYER_IS_EXPLODING_4)
6002 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6003 int explosion_element = EL_PLAYER_1 + player_nr;
6004 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6005 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6007 if (level.use_explosion_element[player_nr])
6008 explosion_element = level.explosion_element[player_nr];
6010 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6011 element_info[explosion_element].content.e[xx][yy]);
6014 // restore probably existing indestructible background element
6015 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6016 element = Tile[x][y] = Back[x][y];
6019 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6020 GfxDir[x][y] = MV_NONE;
6021 ChangeDelay[x][y] = 0;
6022 ChangePage[x][y] = -1;
6024 CustomValue[x][y] = 0;
6026 InitField_WithBug2(x, y, FALSE);
6028 TEST_DrawLevelField(x, y);
6030 TestIfElementTouchesCustomElement(x, y);
6032 if (GFX_CRUMBLED(element))
6033 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6035 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6036 StorePlayer[x][y] = 0;
6038 if (IS_PLAYER_ELEMENT(element))
6039 RelocatePlayer(x, y, element);
6041 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6043 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6044 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6047 TEST_DrawLevelFieldCrumbled(x, y);
6049 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6051 DrawLevelElement(x, y, Back[x][y]);
6052 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6054 else if (IS_WALKABLE_UNDER(Back[x][y]))
6056 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
6057 DrawLevelElementThruMask(x, y, Back[x][y]);
6059 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6060 DrawScreenGraphic(SCREENX(x), SCREENY(y), graphic, frame);
6064 static void DynaExplode(int ex, int ey)
6067 int dynabomb_element = Tile[ex][ey];
6068 int dynabomb_size = 1;
6069 boolean dynabomb_xl = FALSE;
6070 struct PlayerInfo *player;
6071 static int xy[4][2] =
6079 if (IS_ACTIVE_BOMB(dynabomb_element))
6081 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6082 dynabomb_size = player->dynabomb_size;
6083 dynabomb_xl = player->dynabomb_xl;
6084 player->dynabombs_left++;
6087 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6089 for (i = 0; i < NUM_DIRECTIONS; i++)
6091 for (j = 1; j <= dynabomb_size; j++)
6093 int x = ex + j * xy[i][0];
6094 int y = ey + j * xy[i][1];
6097 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6100 element = Tile[x][y];
6102 // do not restart explosions of fields with active bombs
6103 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6106 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6108 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6109 !IS_DIGGABLE(element) && !dynabomb_xl)
6115 void Bang(int x, int y)
6117 int element = MovingOrBlocked2Element(x, y);
6118 int explosion_type = EX_TYPE_NORMAL;
6120 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6122 struct PlayerInfo *player = PLAYERINFO(x, y);
6124 element = Tile[x][y] = player->initial_element;
6126 if (level.use_explosion_element[player->index_nr])
6128 int explosion_element = level.explosion_element[player->index_nr];
6130 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6131 explosion_type = EX_TYPE_CROSS;
6132 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6133 explosion_type = EX_TYPE_CENTER;
6141 case EL_BD_BUTTERFLY:
6144 case EL_DARK_YAMYAM:
6148 RaiseScoreElement(element);
6151 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6152 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6153 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6154 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6155 case EL_DYNABOMB_INCREASE_NUMBER:
6156 case EL_DYNABOMB_INCREASE_SIZE:
6157 case EL_DYNABOMB_INCREASE_POWER:
6158 explosion_type = EX_TYPE_DYNA;
6161 case EL_DC_LANDMINE:
6162 explosion_type = EX_TYPE_CENTER;
6167 case EL_LAMP_ACTIVE:
6168 case EL_AMOEBA_TO_DIAMOND:
6169 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6170 explosion_type = EX_TYPE_CENTER;
6174 if (element_info[element].explosion_type == EXPLODES_CROSS)
6175 explosion_type = EX_TYPE_CROSS;
6176 else if (element_info[element].explosion_type == EXPLODES_1X1)
6177 explosion_type = EX_TYPE_CENTER;
6181 if (explosion_type == EX_TYPE_DYNA)
6184 Explode(x, y, EX_PHASE_START, explosion_type);
6186 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6189 static void SplashAcid(int x, int y)
6191 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6192 (!IN_LEV_FIELD(x - 1, y - 2) ||
6193 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6194 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6196 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6197 (!IN_LEV_FIELD(x + 1, y - 2) ||
6198 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6199 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6201 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6204 static void InitBeltMovement(void)
6206 static int belt_base_element[4] =
6208 EL_CONVEYOR_BELT_1_LEFT,
6209 EL_CONVEYOR_BELT_2_LEFT,
6210 EL_CONVEYOR_BELT_3_LEFT,
6211 EL_CONVEYOR_BELT_4_LEFT
6213 static int belt_base_active_element[4] =
6215 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6216 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6217 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6218 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6223 // set frame order for belt animation graphic according to belt direction
6224 for (i = 0; i < NUM_BELTS; i++)
6228 for (j = 0; j < NUM_BELT_PARTS; j++)
6230 int element = belt_base_active_element[belt_nr] + j;
6231 int graphic_1 = el2img(element);
6232 int graphic_2 = el2panelimg(element);
6234 if (game.belt_dir[i] == MV_LEFT)
6236 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6237 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6241 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6242 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6247 SCAN_PLAYFIELD(x, y)
6249 int element = Tile[x][y];
6251 for (i = 0; i < NUM_BELTS; i++)
6253 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6255 int e_belt_nr = getBeltNrFromBeltElement(element);
6258 if (e_belt_nr == belt_nr)
6260 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6262 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6269 static void ToggleBeltSwitch(int x, int y)
6271 static int belt_base_element[4] =
6273 EL_CONVEYOR_BELT_1_LEFT,
6274 EL_CONVEYOR_BELT_2_LEFT,
6275 EL_CONVEYOR_BELT_3_LEFT,
6276 EL_CONVEYOR_BELT_4_LEFT
6278 static int belt_base_active_element[4] =
6280 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6281 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6282 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6283 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6285 static int belt_base_switch_element[4] =
6287 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6288 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6289 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6290 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6292 static int belt_move_dir[4] =
6300 int element = Tile[x][y];
6301 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6302 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6303 int belt_dir = belt_move_dir[belt_dir_nr];
6306 if (!IS_BELT_SWITCH(element))
6309 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6310 game.belt_dir[belt_nr] = belt_dir;
6312 if (belt_dir_nr == 3)
6315 // set frame order for belt animation graphic according to belt direction
6316 for (i = 0; i < NUM_BELT_PARTS; i++)
6318 int element = belt_base_active_element[belt_nr] + i;
6319 int graphic_1 = el2img(element);
6320 int graphic_2 = el2panelimg(element);
6322 if (belt_dir == MV_LEFT)
6324 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6325 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6329 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6330 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6334 SCAN_PLAYFIELD(xx, yy)
6336 int element = Tile[xx][yy];
6338 if (IS_BELT_SWITCH(element))
6340 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6342 if (e_belt_nr == belt_nr)
6344 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6345 TEST_DrawLevelField(xx, yy);
6348 else if (IS_BELT(element) && belt_dir != MV_NONE)
6350 int e_belt_nr = getBeltNrFromBeltElement(element);
6352 if (e_belt_nr == belt_nr)
6354 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6356 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6357 TEST_DrawLevelField(xx, yy);
6360 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6362 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6364 if (e_belt_nr == belt_nr)
6366 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6368 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6369 TEST_DrawLevelField(xx, yy);
6375 static void ToggleSwitchgateSwitch(int x, int y)
6379 game.switchgate_pos = !game.switchgate_pos;
6381 SCAN_PLAYFIELD(xx, yy)
6383 int element = Tile[xx][yy];
6385 if (element == EL_SWITCHGATE_SWITCH_UP)
6387 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6388 TEST_DrawLevelField(xx, yy);
6390 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6392 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6393 TEST_DrawLevelField(xx, yy);
6395 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6397 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6398 TEST_DrawLevelField(xx, yy);
6400 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6402 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6403 TEST_DrawLevelField(xx, yy);
6405 else if (element == EL_SWITCHGATE_OPEN ||
6406 element == EL_SWITCHGATE_OPENING)
6408 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6410 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6412 else if (element == EL_SWITCHGATE_CLOSED ||
6413 element == EL_SWITCHGATE_CLOSING)
6415 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6417 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6422 static int getInvisibleActiveFromInvisibleElement(int element)
6424 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6425 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6426 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6430 static int getInvisibleFromInvisibleActiveElement(int element)
6432 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6433 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6434 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6438 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6442 SCAN_PLAYFIELD(x, y)
6444 int element = Tile[x][y];
6446 if (element == EL_LIGHT_SWITCH &&
6447 game.light_time_left > 0)
6449 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6450 TEST_DrawLevelField(x, y);
6452 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6453 game.light_time_left == 0)
6455 Tile[x][y] = EL_LIGHT_SWITCH;
6456 TEST_DrawLevelField(x, y);
6458 else if (element == EL_EMC_DRIPPER &&
6459 game.light_time_left > 0)
6461 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6462 TEST_DrawLevelField(x, y);
6464 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6465 game.light_time_left == 0)
6467 Tile[x][y] = EL_EMC_DRIPPER;
6468 TEST_DrawLevelField(x, y);
6470 else if (element == EL_INVISIBLE_STEELWALL ||
6471 element == EL_INVISIBLE_WALL ||
6472 element == EL_INVISIBLE_SAND)
6474 if (game.light_time_left > 0)
6475 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6477 TEST_DrawLevelField(x, y);
6479 // uncrumble neighbour fields, if needed
6480 if (element == EL_INVISIBLE_SAND)
6481 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6483 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6484 element == EL_INVISIBLE_WALL_ACTIVE ||
6485 element == EL_INVISIBLE_SAND_ACTIVE)
6487 if (game.light_time_left == 0)
6488 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6490 TEST_DrawLevelField(x, y);
6492 // re-crumble neighbour fields, if needed
6493 if (element == EL_INVISIBLE_SAND)
6494 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6499 static void RedrawAllInvisibleElementsForLenses(void)
6503 SCAN_PLAYFIELD(x, y)
6505 int element = Tile[x][y];
6507 if (element == EL_EMC_DRIPPER &&
6508 game.lenses_time_left > 0)
6510 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6511 TEST_DrawLevelField(x, y);
6513 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6514 game.lenses_time_left == 0)
6516 Tile[x][y] = EL_EMC_DRIPPER;
6517 TEST_DrawLevelField(x, y);
6519 else if (element == EL_INVISIBLE_STEELWALL ||
6520 element == EL_INVISIBLE_WALL ||
6521 element == EL_INVISIBLE_SAND)
6523 if (game.lenses_time_left > 0)
6524 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6526 TEST_DrawLevelField(x, y);
6528 // uncrumble neighbour fields, if needed
6529 if (element == EL_INVISIBLE_SAND)
6530 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6532 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6533 element == EL_INVISIBLE_WALL_ACTIVE ||
6534 element == EL_INVISIBLE_SAND_ACTIVE)
6536 if (game.lenses_time_left == 0)
6537 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6539 TEST_DrawLevelField(x, y);
6541 // re-crumble neighbour fields, if needed
6542 if (element == EL_INVISIBLE_SAND)
6543 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6548 static void RedrawAllInvisibleElementsForMagnifier(void)
6552 SCAN_PLAYFIELD(x, y)
6554 int element = Tile[x][y];
6556 if (element == EL_EMC_FAKE_GRASS &&
6557 game.magnify_time_left > 0)
6559 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6560 TEST_DrawLevelField(x, y);
6562 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6563 game.magnify_time_left == 0)
6565 Tile[x][y] = EL_EMC_FAKE_GRASS;
6566 TEST_DrawLevelField(x, y);
6568 else if (IS_GATE_GRAY(element) &&
6569 game.magnify_time_left > 0)
6571 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6572 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6573 IS_EM_GATE_GRAY(element) ?
6574 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6575 IS_EMC_GATE_GRAY(element) ?
6576 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6577 IS_DC_GATE_GRAY(element) ?
6578 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6580 TEST_DrawLevelField(x, y);
6582 else if (IS_GATE_GRAY_ACTIVE(element) &&
6583 game.magnify_time_left == 0)
6585 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6586 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6587 IS_EM_GATE_GRAY_ACTIVE(element) ?
6588 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6589 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6590 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6591 IS_DC_GATE_GRAY_ACTIVE(element) ?
6592 EL_DC_GATE_WHITE_GRAY :
6594 TEST_DrawLevelField(x, y);
6599 static void ToggleLightSwitch(int x, int y)
6601 int element = Tile[x][y];
6603 game.light_time_left =
6604 (element == EL_LIGHT_SWITCH ?
6605 level.time_light * FRAMES_PER_SECOND : 0);
6607 RedrawAllLightSwitchesAndInvisibleElements();
6610 static void ActivateTimegateSwitch(int x, int y)
6614 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6616 SCAN_PLAYFIELD(xx, yy)
6618 int element = Tile[xx][yy];
6620 if (element == EL_TIMEGATE_CLOSED ||
6621 element == EL_TIMEGATE_CLOSING)
6623 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6624 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6628 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6630 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6631 TEST_DrawLevelField(xx, yy);
6637 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6638 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6641 static void Impact(int x, int y)
6643 boolean last_line = (y == lev_fieldy - 1);
6644 boolean object_hit = FALSE;
6645 boolean impact = (last_line || object_hit);
6646 int element = Tile[x][y];
6647 int smashed = EL_STEELWALL;
6649 if (!last_line) // check if element below was hit
6651 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6654 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6655 MovDir[x][y + 1] != MV_DOWN ||
6656 MovPos[x][y + 1] <= TILEY / 2));
6658 // do not smash moving elements that left the smashed field in time
6659 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6660 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6663 #if USE_QUICKSAND_IMPACT_BUGFIX
6664 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6666 RemoveMovingField(x, y + 1);
6667 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6668 Tile[x][y + 2] = EL_ROCK;
6669 TEST_DrawLevelField(x, y + 2);
6674 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6676 RemoveMovingField(x, y + 1);
6677 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6678 Tile[x][y + 2] = EL_ROCK;
6679 TEST_DrawLevelField(x, y + 2);
6686 smashed = MovingOrBlocked2Element(x, y + 1);
6688 impact = (last_line || object_hit);
6691 if (!last_line && smashed == EL_ACID) // element falls into acid
6693 SplashAcid(x, y + 1);
6697 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6698 // only reset graphic animation if graphic really changes after impact
6700 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6702 ResetGfxAnimation(x, y);
6703 TEST_DrawLevelField(x, y);
6706 if (impact && CAN_EXPLODE_IMPACT(element))
6711 else if (impact && element == EL_PEARL &&
6712 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6714 ResetGfxAnimation(x, y);
6716 Tile[x][y] = EL_PEARL_BREAKING;
6717 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6720 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6722 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6727 if (impact && element == EL_AMOEBA_DROP)
6729 if (object_hit && IS_PLAYER(x, y + 1))
6730 KillPlayerUnlessEnemyProtected(x, y + 1);
6731 else if (object_hit && smashed == EL_PENGUIN)
6735 Tile[x][y] = EL_AMOEBA_GROWING;
6736 Store[x][y] = EL_AMOEBA_WET;
6738 ResetRandomAnimationValue(x, y);
6743 if (object_hit) // check which object was hit
6745 if ((CAN_PASS_MAGIC_WALL(element) &&
6746 (smashed == EL_MAGIC_WALL ||
6747 smashed == EL_BD_MAGIC_WALL)) ||
6748 (CAN_PASS_DC_MAGIC_WALL(element) &&
6749 smashed == EL_DC_MAGIC_WALL))
6752 int activated_magic_wall =
6753 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6754 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6755 EL_DC_MAGIC_WALL_ACTIVE);
6757 // activate magic wall / mill
6758 SCAN_PLAYFIELD(xx, yy)
6760 if (Tile[xx][yy] == smashed)
6761 Tile[xx][yy] = activated_magic_wall;
6764 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6765 game.magic_wall_active = TRUE;
6767 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6768 SND_MAGIC_WALL_ACTIVATING :
6769 smashed == EL_BD_MAGIC_WALL ?
6770 SND_BD_MAGIC_WALL_ACTIVATING :
6771 SND_DC_MAGIC_WALL_ACTIVATING));
6774 if (IS_PLAYER(x, y + 1))
6776 if (CAN_SMASH_PLAYER(element))
6778 KillPlayerUnlessEnemyProtected(x, y + 1);
6782 else if (smashed == EL_PENGUIN)
6784 if (CAN_SMASH_PLAYER(element))
6790 else if (element == EL_BD_DIAMOND)
6792 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6798 else if (((element == EL_SP_INFOTRON ||
6799 element == EL_SP_ZONK) &&
6800 (smashed == EL_SP_SNIKSNAK ||
6801 smashed == EL_SP_ELECTRON ||
6802 smashed == EL_SP_DISK_ORANGE)) ||
6803 (element == EL_SP_INFOTRON &&
6804 smashed == EL_SP_DISK_YELLOW))
6809 else if (CAN_SMASH_EVERYTHING(element))
6811 if (IS_CLASSIC_ENEMY(smashed) ||
6812 CAN_EXPLODE_SMASHED(smashed))
6817 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6819 if (smashed == EL_LAMP ||
6820 smashed == EL_LAMP_ACTIVE)
6825 else if (smashed == EL_NUT)
6827 Tile[x][y + 1] = EL_NUT_BREAKING;
6828 PlayLevelSound(x, y, SND_NUT_BREAKING);
6829 RaiseScoreElement(EL_NUT);
6832 else if (smashed == EL_PEARL)
6834 ResetGfxAnimation(x, y);
6836 Tile[x][y + 1] = EL_PEARL_BREAKING;
6837 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6840 else if (smashed == EL_DIAMOND)
6842 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6843 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6846 else if (IS_BELT_SWITCH(smashed))
6848 ToggleBeltSwitch(x, y + 1);
6850 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6851 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6852 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6853 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6855 ToggleSwitchgateSwitch(x, y + 1);
6857 else if (smashed == EL_LIGHT_SWITCH ||
6858 smashed == EL_LIGHT_SWITCH_ACTIVE)
6860 ToggleLightSwitch(x, y + 1);
6864 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6866 CheckElementChangeBySide(x, y + 1, smashed, element,
6867 CE_SWITCHED, CH_SIDE_TOP);
6868 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6874 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6879 // play sound of magic wall / mill
6881 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6882 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6883 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6885 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6886 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6887 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6888 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6889 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6890 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6895 // play sound of object that hits the ground
6896 if (last_line || object_hit)
6897 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6900 static void TurnRoundExt(int x, int y)
6912 { 0, 0 }, { 0, 0 }, { 0, 0 },
6917 int left, right, back;
6921 { MV_DOWN, MV_UP, MV_RIGHT },
6922 { MV_UP, MV_DOWN, MV_LEFT },
6924 { MV_LEFT, MV_RIGHT, MV_DOWN },
6928 { MV_RIGHT, MV_LEFT, MV_UP }
6931 int element = Tile[x][y];
6932 int move_pattern = element_info[element].move_pattern;
6934 int old_move_dir = MovDir[x][y];
6935 int left_dir = turn[old_move_dir].left;
6936 int right_dir = turn[old_move_dir].right;
6937 int back_dir = turn[old_move_dir].back;
6939 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6940 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6941 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6942 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6944 int left_x = x + left_dx, left_y = y + left_dy;
6945 int right_x = x + right_dx, right_y = y + right_dy;
6946 int move_x = x + move_dx, move_y = y + move_dy;
6950 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6952 TestIfBadThingTouchesOtherBadThing(x, y);
6954 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6955 MovDir[x][y] = right_dir;
6956 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6957 MovDir[x][y] = left_dir;
6959 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6961 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6964 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6966 TestIfBadThingTouchesOtherBadThing(x, y);
6968 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6969 MovDir[x][y] = left_dir;
6970 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6971 MovDir[x][y] = right_dir;
6973 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6975 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6978 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6980 TestIfBadThingTouchesOtherBadThing(x, y);
6982 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6983 MovDir[x][y] = left_dir;
6984 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6985 MovDir[x][y] = right_dir;
6987 if (MovDir[x][y] != old_move_dir)
6990 else if (element == EL_YAMYAM)
6992 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6993 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6995 if (can_turn_left && can_turn_right)
6996 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6997 else if (can_turn_left)
6998 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6999 else if (can_turn_right)
7000 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7002 MovDir[x][y] = back_dir;
7004 MovDelay[x][y] = 16 + 16 * RND(3);
7006 else if (element == EL_DARK_YAMYAM)
7008 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7010 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7013 if (can_turn_left && can_turn_right)
7014 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7015 else if (can_turn_left)
7016 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7017 else if (can_turn_right)
7018 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7020 MovDir[x][y] = back_dir;
7022 MovDelay[x][y] = 16 + 16 * RND(3);
7024 else if (element == EL_PACMAN)
7026 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7027 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7029 if (can_turn_left && can_turn_right)
7030 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7031 else if (can_turn_left)
7032 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7033 else if (can_turn_right)
7034 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7036 MovDir[x][y] = back_dir;
7038 MovDelay[x][y] = 6 + RND(40);
7040 else if (element == EL_PIG)
7042 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7043 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7044 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7045 boolean should_turn_left, should_turn_right, should_move_on;
7047 int rnd = RND(rnd_value);
7049 should_turn_left = (can_turn_left &&
7051 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7052 y + back_dy + left_dy)));
7053 should_turn_right = (can_turn_right &&
7055 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7056 y + back_dy + right_dy)));
7057 should_move_on = (can_move_on &&
7060 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7061 y + move_dy + left_dy) ||
7062 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7063 y + move_dy + right_dy)));
7065 if (should_turn_left || should_turn_right || should_move_on)
7067 if (should_turn_left && should_turn_right && should_move_on)
7068 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7069 rnd < 2 * rnd_value / 3 ? right_dir :
7071 else if (should_turn_left && should_turn_right)
7072 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7073 else if (should_turn_left && should_move_on)
7074 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7075 else if (should_turn_right && should_move_on)
7076 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7077 else if (should_turn_left)
7078 MovDir[x][y] = left_dir;
7079 else if (should_turn_right)
7080 MovDir[x][y] = right_dir;
7081 else if (should_move_on)
7082 MovDir[x][y] = old_move_dir;
7084 else if (can_move_on && rnd > rnd_value / 8)
7085 MovDir[x][y] = old_move_dir;
7086 else if (can_turn_left && can_turn_right)
7087 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7088 else if (can_turn_left && rnd > rnd_value / 8)
7089 MovDir[x][y] = left_dir;
7090 else if (can_turn_right && rnd > rnd_value/8)
7091 MovDir[x][y] = right_dir;
7093 MovDir[x][y] = back_dir;
7095 xx = x + move_xy[MovDir[x][y]].dx;
7096 yy = y + move_xy[MovDir[x][y]].dy;
7098 if (!IN_LEV_FIELD(xx, yy) ||
7099 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7100 MovDir[x][y] = old_move_dir;
7104 else if (element == EL_DRAGON)
7106 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7107 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7108 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7110 int rnd = RND(rnd_value);
7112 if (can_move_on && rnd > rnd_value / 8)
7113 MovDir[x][y] = old_move_dir;
7114 else if (can_turn_left && can_turn_right)
7115 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7116 else if (can_turn_left && rnd > rnd_value / 8)
7117 MovDir[x][y] = left_dir;
7118 else if (can_turn_right && rnd > rnd_value / 8)
7119 MovDir[x][y] = right_dir;
7121 MovDir[x][y] = back_dir;
7123 xx = x + move_xy[MovDir[x][y]].dx;
7124 yy = y + move_xy[MovDir[x][y]].dy;
7126 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7127 MovDir[x][y] = old_move_dir;
7131 else if (element == EL_MOLE)
7133 boolean can_move_on =
7134 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7135 IS_AMOEBOID(Tile[move_x][move_y]) ||
7136 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7139 boolean can_turn_left =
7140 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7141 IS_AMOEBOID(Tile[left_x][left_y])));
7143 boolean can_turn_right =
7144 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7145 IS_AMOEBOID(Tile[right_x][right_y])));
7147 if (can_turn_left && can_turn_right)
7148 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7149 else if (can_turn_left)
7150 MovDir[x][y] = left_dir;
7152 MovDir[x][y] = right_dir;
7155 if (MovDir[x][y] != old_move_dir)
7158 else if (element == EL_BALLOON)
7160 MovDir[x][y] = game.wind_direction;
7163 else if (element == EL_SPRING)
7165 if (MovDir[x][y] & MV_HORIZONTAL)
7167 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7168 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7170 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7171 ResetGfxAnimation(move_x, move_y);
7172 TEST_DrawLevelField(move_x, move_y);
7174 MovDir[x][y] = back_dir;
7176 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7177 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7178 MovDir[x][y] = MV_NONE;
7183 else if (element == EL_ROBOT ||
7184 element == EL_SATELLITE ||
7185 element == EL_PENGUIN ||
7186 element == EL_EMC_ANDROID)
7188 int attr_x = -1, attr_y = -1;
7190 if (game.all_players_gone)
7192 attr_x = game.exit_x;
7193 attr_y = game.exit_y;
7199 for (i = 0; i < MAX_PLAYERS; i++)
7201 struct PlayerInfo *player = &stored_player[i];
7202 int jx = player->jx, jy = player->jy;
7204 if (!player->active)
7208 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7216 if (element == EL_ROBOT &&
7217 game.robot_wheel_x >= 0 &&
7218 game.robot_wheel_y >= 0 &&
7219 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7220 game.engine_version < VERSION_IDENT(3,1,0,0)))
7222 attr_x = game.robot_wheel_x;
7223 attr_y = game.robot_wheel_y;
7226 if (element == EL_PENGUIN)
7229 static int xy[4][2] =
7237 for (i = 0; i < NUM_DIRECTIONS; i++)
7239 int ex = x + xy[i][0];
7240 int ey = y + xy[i][1];
7242 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7243 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7244 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7245 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7254 MovDir[x][y] = MV_NONE;
7256 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7257 else if (attr_x > x)
7258 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7260 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7261 else if (attr_y > y)
7262 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7264 if (element == EL_ROBOT)
7268 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7269 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7270 Moving2Blocked(x, y, &newx, &newy);
7272 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7273 MovDelay[x][y] = 8 + 8 * !RND(3);
7275 MovDelay[x][y] = 16;
7277 else if (element == EL_PENGUIN)
7283 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7285 boolean first_horiz = RND(2);
7286 int new_move_dir = MovDir[x][y];
7289 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7290 Moving2Blocked(x, y, &newx, &newy);
7292 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7296 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7297 Moving2Blocked(x, y, &newx, &newy);
7299 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7302 MovDir[x][y] = old_move_dir;
7306 else if (element == EL_SATELLITE)
7312 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7314 boolean first_horiz = RND(2);
7315 int new_move_dir = MovDir[x][y];
7318 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7319 Moving2Blocked(x, y, &newx, &newy);
7321 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7325 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7326 Moving2Blocked(x, y, &newx, &newy);
7328 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7331 MovDir[x][y] = old_move_dir;
7335 else if (element == EL_EMC_ANDROID)
7337 static int check_pos[16] =
7339 -1, // 0 => (invalid)
7342 -1, // 3 => (invalid)
7344 0, // 5 => MV_LEFT | MV_UP
7345 2, // 6 => MV_RIGHT | MV_UP
7346 -1, // 7 => (invalid)
7348 6, // 9 => MV_LEFT | MV_DOWN
7349 4, // 10 => MV_RIGHT | MV_DOWN
7350 -1, // 11 => (invalid)
7351 -1, // 12 => (invalid)
7352 -1, // 13 => (invalid)
7353 -1, // 14 => (invalid)
7354 -1, // 15 => (invalid)
7362 { -1, -1, MV_LEFT | MV_UP },
7364 { +1, -1, MV_RIGHT | MV_UP },
7365 { +1, 0, MV_RIGHT },
7366 { +1, +1, MV_RIGHT | MV_DOWN },
7368 { -1, +1, MV_LEFT | MV_DOWN },
7371 int start_pos, check_order;
7372 boolean can_clone = FALSE;
7375 // check if there is any free field around current position
7376 for (i = 0; i < 8; i++)
7378 int newx = x + check_xy[i].dx;
7379 int newy = y + check_xy[i].dy;
7381 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7389 if (can_clone) // randomly find an element to clone
7393 start_pos = check_pos[RND(8)];
7394 check_order = (RND(2) ? -1 : +1);
7396 for (i = 0; i < 8; i++)
7398 int pos_raw = start_pos + i * check_order;
7399 int pos = (pos_raw + 8) % 8;
7400 int newx = x + check_xy[pos].dx;
7401 int newy = y + check_xy[pos].dy;
7403 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7405 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7406 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7408 Store[x][y] = Tile[newx][newy];
7417 if (can_clone) // randomly find a direction to move
7421 start_pos = check_pos[RND(8)];
7422 check_order = (RND(2) ? -1 : +1);
7424 for (i = 0; i < 8; i++)
7426 int pos_raw = start_pos + i * check_order;
7427 int pos = (pos_raw + 8) % 8;
7428 int newx = x + check_xy[pos].dx;
7429 int newy = y + check_xy[pos].dy;
7430 int new_move_dir = check_xy[pos].dir;
7432 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7434 MovDir[x][y] = new_move_dir;
7435 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7444 if (can_clone) // cloning and moving successful
7447 // cannot clone -- try to move towards player
7449 start_pos = check_pos[MovDir[x][y] & 0x0f];
7450 check_order = (RND(2) ? -1 : +1);
7452 for (i = 0; i < 3; i++)
7454 // first check start_pos, then previous/next or (next/previous) pos
7455 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7456 int pos = (pos_raw + 8) % 8;
7457 int newx = x + check_xy[pos].dx;
7458 int newy = y + check_xy[pos].dy;
7459 int new_move_dir = check_xy[pos].dir;
7461 if (IS_PLAYER(newx, newy))
7464 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7466 MovDir[x][y] = new_move_dir;
7467 MovDelay[x][y] = level.android_move_time * 8 + 1;
7474 else if (move_pattern == MV_TURNING_LEFT ||
7475 move_pattern == MV_TURNING_RIGHT ||
7476 move_pattern == MV_TURNING_LEFT_RIGHT ||
7477 move_pattern == MV_TURNING_RIGHT_LEFT ||
7478 move_pattern == MV_TURNING_RANDOM ||
7479 move_pattern == MV_ALL_DIRECTIONS)
7481 boolean can_turn_left =
7482 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7483 boolean can_turn_right =
7484 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7486 if (element_info[element].move_stepsize == 0) // "not moving"
7489 if (move_pattern == MV_TURNING_LEFT)
7490 MovDir[x][y] = left_dir;
7491 else if (move_pattern == MV_TURNING_RIGHT)
7492 MovDir[x][y] = right_dir;
7493 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7494 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7495 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7496 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7497 else if (move_pattern == MV_TURNING_RANDOM)
7498 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7499 can_turn_right && !can_turn_left ? right_dir :
7500 RND(2) ? left_dir : right_dir);
7501 else if (can_turn_left && can_turn_right)
7502 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7503 else if (can_turn_left)
7504 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7505 else if (can_turn_right)
7506 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7508 MovDir[x][y] = back_dir;
7510 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7512 else if (move_pattern == MV_HORIZONTAL ||
7513 move_pattern == MV_VERTICAL)
7515 if (move_pattern & old_move_dir)
7516 MovDir[x][y] = back_dir;
7517 else if (move_pattern == MV_HORIZONTAL)
7518 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7519 else if (move_pattern == MV_VERTICAL)
7520 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7522 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7524 else if (move_pattern & MV_ANY_DIRECTION)
7526 MovDir[x][y] = move_pattern;
7527 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7529 else if (move_pattern & MV_WIND_DIRECTION)
7531 MovDir[x][y] = game.wind_direction;
7532 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7534 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7536 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7537 MovDir[x][y] = left_dir;
7538 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7539 MovDir[x][y] = right_dir;
7541 if (MovDir[x][y] != old_move_dir)
7542 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7544 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7546 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7547 MovDir[x][y] = right_dir;
7548 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7549 MovDir[x][y] = left_dir;
7551 if (MovDir[x][y] != old_move_dir)
7552 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7554 else if (move_pattern == MV_TOWARDS_PLAYER ||
7555 move_pattern == MV_AWAY_FROM_PLAYER)
7557 int attr_x = -1, attr_y = -1;
7559 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7561 if (game.all_players_gone)
7563 attr_x = game.exit_x;
7564 attr_y = game.exit_y;
7570 for (i = 0; i < MAX_PLAYERS; i++)
7572 struct PlayerInfo *player = &stored_player[i];
7573 int jx = player->jx, jy = player->jy;
7575 if (!player->active)
7579 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7587 MovDir[x][y] = MV_NONE;
7589 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7590 else if (attr_x > x)
7591 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7593 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7594 else if (attr_y > y)
7595 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7597 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7599 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7601 boolean first_horiz = RND(2);
7602 int new_move_dir = MovDir[x][y];
7604 if (element_info[element].move_stepsize == 0) // "not moving"
7606 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7607 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7613 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7614 Moving2Blocked(x, y, &newx, &newy);
7616 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7620 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7621 Moving2Blocked(x, y, &newx, &newy);
7623 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7626 MovDir[x][y] = old_move_dir;
7629 else if (move_pattern == MV_WHEN_PUSHED ||
7630 move_pattern == MV_WHEN_DROPPED)
7632 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7633 MovDir[x][y] = MV_NONE;
7637 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7639 static int test_xy[7][2] =
7649 static int test_dir[7] =
7659 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7660 int move_preference = -1000000; // start with very low preference
7661 int new_move_dir = MV_NONE;
7662 int start_test = RND(4);
7665 for (i = 0; i < NUM_DIRECTIONS; i++)
7667 int move_dir = test_dir[start_test + i];
7668 int move_dir_preference;
7670 xx = x + test_xy[start_test + i][0];
7671 yy = y + test_xy[start_test + i][1];
7673 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7674 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7676 new_move_dir = move_dir;
7681 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7684 move_dir_preference = -1 * RunnerVisit[xx][yy];
7685 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7686 move_dir_preference = PlayerVisit[xx][yy];
7688 if (move_dir_preference > move_preference)
7690 // prefer field that has not been visited for the longest time
7691 move_preference = move_dir_preference;
7692 new_move_dir = move_dir;
7694 else if (move_dir_preference == move_preference &&
7695 move_dir == old_move_dir)
7697 // prefer last direction when all directions are preferred equally
7698 move_preference = move_dir_preference;
7699 new_move_dir = move_dir;
7703 MovDir[x][y] = new_move_dir;
7704 if (old_move_dir != new_move_dir)
7705 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7709 static void TurnRound(int x, int y)
7711 int direction = MovDir[x][y];
7715 GfxDir[x][y] = MovDir[x][y];
7717 if (direction != MovDir[x][y])
7721 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7723 ResetGfxFrame(x, y);
7726 static boolean JustBeingPushed(int x, int y)
7730 for (i = 0; i < MAX_PLAYERS; i++)
7732 struct PlayerInfo *player = &stored_player[i];
7734 if (player->active && player->is_pushing && player->MovPos)
7736 int next_jx = player->jx + (player->jx - player->last_jx);
7737 int next_jy = player->jy + (player->jy - player->last_jy);
7739 if (x == next_jx && y == next_jy)
7747 static void StartMoving(int x, int y)
7749 boolean started_moving = FALSE; // some elements can fall _and_ move
7750 int element = Tile[x][y];
7755 if (MovDelay[x][y] == 0)
7756 GfxAction[x][y] = ACTION_DEFAULT;
7758 if (CAN_FALL(element) && y < lev_fieldy - 1)
7760 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7761 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7762 if (JustBeingPushed(x, y))
7765 if (element == EL_QUICKSAND_FULL)
7767 if (IS_FREE(x, y + 1))
7769 InitMovingField(x, y, MV_DOWN);
7770 started_moving = TRUE;
7772 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7773 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7774 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7775 Store[x][y] = EL_ROCK;
7777 Store[x][y] = EL_ROCK;
7780 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7782 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7784 if (!MovDelay[x][y])
7786 MovDelay[x][y] = TILEY + 1;
7788 ResetGfxAnimation(x, y);
7789 ResetGfxAnimation(x, y + 1);
7794 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7795 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7802 Tile[x][y] = EL_QUICKSAND_EMPTY;
7803 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7804 Store[x][y + 1] = Store[x][y];
7807 PlayLevelSoundAction(x, y, ACTION_FILLING);
7809 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7811 if (!MovDelay[x][y])
7813 MovDelay[x][y] = TILEY + 1;
7815 ResetGfxAnimation(x, y);
7816 ResetGfxAnimation(x, y + 1);
7821 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7822 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7829 Tile[x][y] = EL_QUICKSAND_EMPTY;
7830 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7831 Store[x][y + 1] = Store[x][y];
7834 PlayLevelSoundAction(x, y, ACTION_FILLING);
7837 else if (element == EL_QUICKSAND_FAST_FULL)
7839 if (IS_FREE(x, y + 1))
7841 InitMovingField(x, y, MV_DOWN);
7842 started_moving = TRUE;
7844 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7845 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7846 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7847 Store[x][y] = EL_ROCK;
7849 Store[x][y] = EL_ROCK;
7852 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7854 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7856 if (!MovDelay[x][y])
7858 MovDelay[x][y] = TILEY + 1;
7860 ResetGfxAnimation(x, y);
7861 ResetGfxAnimation(x, y + 1);
7866 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7867 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7874 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7875 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7876 Store[x][y + 1] = Store[x][y];
7879 PlayLevelSoundAction(x, y, ACTION_FILLING);
7881 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7883 if (!MovDelay[x][y])
7885 MovDelay[x][y] = TILEY + 1;
7887 ResetGfxAnimation(x, y);
7888 ResetGfxAnimation(x, y + 1);
7893 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7894 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7901 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7902 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7903 Store[x][y + 1] = Store[x][y];
7906 PlayLevelSoundAction(x, y, ACTION_FILLING);
7909 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7910 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7912 InitMovingField(x, y, MV_DOWN);
7913 started_moving = TRUE;
7915 Tile[x][y] = EL_QUICKSAND_FILLING;
7916 Store[x][y] = element;
7918 PlayLevelSoundAction(x, y, ACTION_FILLING);
7920 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7921 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7923 InitMovingField(x, y, MV_DOWN);
7924 started_moving = TRUE;
7926 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7927 Store[x][y] = element;
7929 PlayLevelSoundAction(x, y, ACTION_FILLING);
7931 else if (element == EL_MAGIC_WALL_FULL)
7933 if (IS_FREE(x, y + 1))
7935 InitMovingField(x, y, MV_DOWN);
7936 started_moving = TRUE;
7938 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7939 Store[x][y] = EL_CHANGED(Store[x][y]);
7941 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7943 if (!MovDelay[x][y])
7944 MovDelay[x][y] = TILEY / 4 + 1;
7953 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7954 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7955 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7959 else if (element == EL_BD_MAGIC_WALL_FULL)
7961 if (IS_FREE(x, y + 1))
7963 InitMovingField(x, y, MV_DOWN);
7964 started_moving = TRUE;
7966 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7967 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7969 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7971 if (!MovDelay[x][y])
7972 MovDelay[x][y] = TILEY / 4 + 1;
7981 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7982 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7983 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7987 else if (element == EL_DC_MAGIC_WALL_FULL)
7989 if (IS_FREE(x, y + 1))
7991 InitMovingField(x, y, MV_DOWN);
7992 started_moving = TRUE;
7994 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7995 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7997 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7999 if (!MovDelay[x][y])
8000 MovDelay[x][y] = TILEY / 4 + 1;
8009 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8010 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8011 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8015 else if ((CAN_PASS_MAGIC_WALL(element) &&
8016 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8017 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8018 (CAN_PASS_DC_MAGIC_WALL(element) &&
8019 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8022 InitMovingField(x, y, MV_DOWN);
8023 started_moving = TRUE;
8026 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8027 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8028 EL_DC_MAGIC_WALL_FILLING);
8029 Store[x][y] = element;
8031 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8033 SplashAcid(x, y + 1);
8035 InitMovingField(x, y, MV_DOWN);
8036 started_moving = TRUE;
8038 Store[x][y] = EL_ACID;
8041 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8042 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8043 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8044 CAN_FALL(element) && WasJustFalling[x][y] &&
8045 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8047 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8048 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8049 (Tile[x][y + 1] == EL_BLOCKED)))
8051 /* this is needed for a special case not covered by calling "Impact()"
8052 from "ContinueMoving()": if an element moves to a tile directly below
8053 another element which was just falling on that tile (which was empty
8054 in the previous frame), the falling element above would just stop
8055 instead of smashing the element below (in previous version, the above
8056 element was just checked for "moving" instead of "falling", resulting
8057 in incorrect smashes caused by horizontal movement of the above
8058 element; also, the case of the player being the element to smash was
8059 simply not covered here... :-/ ) */
8061 CheckCollision[x][y] = 0;
8062 CheckImpact[x][y] = 0;
8066 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8068 if (MovDir[x][y] == MV_NONE)
8070 InitMovingField(x, y, MV_DOWN);
8071 started_moving = TRUE;
8074 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8076 if (WasJustFalling[x][y]) // prevent animation from being restarted
8077 MovDir[x][y] = MV_DOWN;
8079 InitMovingField(x, y, MV_DOWN);
8080 started_moving = TRUE;
8082 else if (element == EL_AMOEBA_DROP)
8084 Tile[x][y] = EL_AMOEBA_GROWING;
8085 Store[x][y] = EL_AMOEBA_WET;
8087 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8088 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8089 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8090 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8092 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8093 (IS_FREE(x - 1, y + 1) ||
8094 Tile[x - 1][y + 1] == EL_ACID));
8095 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8096 (IS_FREE(x + 1, y + 1) ||
8097 Tile[x + 1][y + 1] == EL_ACID));
8098 boolean can_fall_any = (can_fall_left || can_fall_right);
8099 boolean can_fall_both = (can_fall_left && can_fall_right);
8100 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8102 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8104 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8105 can_fall_right = FALSE;
8106 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8107 can_fall_left = FALSE;
8108 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8109 can_fall_right = FALSE;
8110 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8111 can_fall_left = FALSE;
8113 can_fall_any = (can_fall_left || can_fall_right);
8114 can_fall_both = FALSE;
8119 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8120 can_fall_right = FALSE; // slip down on left side
8122 can_fall_left = !(can_fall_right = RND(2));
8124 can_fall_both = FALSE;
8129 // if not determined otherwise, prefer left side for slipping down
8130 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8131 started_moving = TRUE;
8134 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8136 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8137 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8138 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8139 int belt_dir = game.belt_dir[belt_nr];
8141 if ((belt_dir == MV_LEFT && left_is_free) ||
8142 (belt_dir == MV_RIGHT && right_is_free))
8144 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8146 InitMovingField(x, y, belt_dir);
8147 started_moving = TRUE;
8149 Pushed[x][y] = TRUE;
8150 Pushed[nextx][y] = TRUE;
8152 GfxAction[x][y] = ACTION_DEFAULT;
8156 MovDir[x][y] = 0; // if element was moving, stop it
8161 // not "else if" because of elements that can fall and move (EL_SPRING)
8162 if (CAN_MOVE(element) && !started_moving)
8164 int move_pattern = element_info[element].move_pattern;
8167 Moving2Blocked(x, y, &newx, &newy);
8169 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8172 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8173 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8175 WasJustMoving[x][y] = 0;
8176 CheckCollision[x][y] = 0;
8178 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8180 if (Tile[x][y] != element) // element has changed
8184 if (!MovDelay[x][y]) // start new movement phase
8186 // all objects that can change their move direction after each step
8187 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8189 if (element != EL_YAMYAM &&
8190 element != EL_DARK_YAMYAM &&
8191 element != EL_PACMAN &&
8192 !(move_pattern & MV_ANY_DIRECTION) &&
8193 move_pattern != MV_TURNING_LEFT &&
8194 move_pattern != MV_TURNING_RIGHT &&
8195 move_pattern != MV_TURNING_LEFT_RIGHT &&
8196 move_pattern != MV_TURNING_RIGHT_LEFT &&
8197 move_pattern != MV_TURNING_RANDOM)
8201 if (MovDelay[x][y] && (element == EL_BUG ||
8202 element == EL_SPACESHIP ||
8203 element == EL_SP_SNIKSNAK ||
8204 element == EL_SP_ELECTRON ||
8205 element == EL_MOLE))
8206 TEST_DrawLevelField(x, y);
8210 if (MovDelay[x][y]) // wait some time before next movement
8214 if (element == EL_ROBOT ||
8215 element == EL_YAMYAM ||
8216 element == EL_DARK_YAMYAM)
8218 DrawLevelElementAnimationIfNeeded(x, y, element);
8219 PlayLevelSoundAction(x, y, ACTION_WAITING);
8221 else if (element == EL_SP_ELECTRON)
8222 DrawLevelElementAnimationIfNeeded(x, y, element);
8223 else if (element == EL_DRAGON)
8226 int dir = MovDir[x][y];
8227 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8228 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8229 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8230 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8231 dir == MV_UP ? IMG_FLAMES_1_UP :
8232 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8233 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8235 GfxAction[x][y] = ACTION_ATTACKING;
8237 if (IS_PLAYER(x, y))
8238 DrawPlayerField(x, y);
8240 TEST_DrawLevelField(x, y);
8242 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8244 for (i = 1; i <= 3; i++)
8246 int xx = x + i * dx;
8247 int yy = y + i * dy;
8248 int sx = SCREENX(xx);
8249 int sy = SCREENY(yy);
8250 int flame_graphic = graphic + (i - 1);
8252 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8257 int flamed = MovingOrBlocked2Element(xx, yy);
8259 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8262 RemoveMovingField(xx, yy);
8264 ChangeDelay[xx][yy] = 0;
8266 Tile[xx][yy] = EL_FLAMES;
8268 if (IN_SCR_FIELD(sx, sy))
8270 TEST_DrawLevelFieldCrumbled(xx, yy);
8271 DrawGraphic(sx, sy, flame_graphic, frame);
8276 if (Tile[xx][yy] == EL_FLAMES)
8277 Tile[xx][yy] = EL_EMPTY;
8278 TEST_DrawLevelField(xx, yy);
8283 if (MovDelay[x][y]) // element still has to wait some time
8285 PlayLevelSoundAction(x, y, ACTION_WAITING);
8291 // now make next step
8293 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8295 if (DONT_COLLIDE_WITH(element) &&
8296 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8297 !PLAYER_ENEMY_PROTECTED(newx, newy))
8299 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8304 else if (CAN_MOVE_INTO_ACID(element) &&
8305 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8306 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8307 (MovDir[x][y] == MV_DOWN ||
8308 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8310 SplashAcid(newx, newy);
8311 Store[x][y] = EL_ACID;
8313 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8315 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8316 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8317 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8318 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8321 TEST_DrawLevelField(x, y);
8323 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8324 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8325 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8327 game.friends_still_needed--;
8328 if (!game.friends_still_needed &&
8330 game.all_players_gone)
8335 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8337 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8338 TEST_DrawLevelField(newx, newy);
8340 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8342 else if (!IS_FREE(newx, newy))
8344 GfxAction[x][y] = ACTION_WAITING;
8346 if (IS_PLAYER(x, y))
8347 DrawPlayerField(x, y);
8349 TEST_DrawLevelField(x, y);
8354 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8356 if (IS_FOOD_PIG(Tile[newx][newy]))
8358 if (IS_MOVING(newx, newy))
8359 RemoveMovingField(newx, newy);
8362 Tile[newx][newy] = EL_EMPTY;
8363 TEST_DrawLevelField(newx, newy);
8366 PlayLevelSound(x, y, SND_PIG_DIGGING);
8368 else if (!IS_FREE(newx, newy))
8370 if (IS_PLAYER(x, y))
8371 DrawPlayerField(x, y);
8373 TEST_DrawLevelField(x, y);
8378 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8380 if (Store[x][y] != EL_EMPTY)
8382 boolean can_clone = FALSE;
8385 // check if element to clone is still there
8386 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8388 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8396 // cannot clone or target field not free anymore -- do not clone
8397 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8398 Store[x][y] = EL_EMPTY;
8401 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8403 if (IS_MV_DIAGONAL(MovDir[x][y]))
8405 int diagonal_move_dir = MovDir[x][y];
8406 int stored = Store[x][y];
8407 int change_delay = 8;
8410 // android is moving diagonally
8412 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8414 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8415 GfxElement[x][y] = EL_EMC_ANDROID;
8416 GfxAction[x][y] = ACTION_SHRINKING;
8417 GfxDir[x][y] = diagonal_move_dir;
8418 ChangeDelay[x][y] = change_delay;
8420 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8423 DrawLevelGraphicAnimation(x, y, graphic);
8424 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8426 if (Tile[newx][newy] == EL_ACID)
8428 SplashAcid(newx, newy);
8433 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8435 Store[newx][newy] = EL_EMC_ANDROID;
8436 GfxElement[newx][newy] = EL_EMC_ANDROID;
8437 GfxAction[newx][newy] = ACTION_GROWING;
8438 GfxDir[newx][newy] = diagonal_move_dir;
8439 ChangeDelay[newx][newy] = change_delay;
8441 graphic = el_act_dir2img(GfxElement[newx][newy],
8442 GfxAction[newx][newy], GfxDir[newx][newy]);
8444 DrawLevelGraphicAnimation(newx, newy, graphic);
8445 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8451 Tile[newx][newy] = EL_EMPTY;
8452 TEST_DrawLevelField(newx, newy);
8454 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8457 else if (!IS_FREE(newx, newy))
8462 else if (IS_CUSTOM_ELEMENT(element) &&
8463 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8465 if (!DigFieldByCE(newx, newy, element))
8468 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8470 RunnerVisit[x][y] = FrameCounter;
8471 PlayerVisit[x][y] /= 8; // expire player visit path
8474 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8476 if (!IS_FREE(newx, newy))
8478 if (IS_PLAYER(x, y))
8479 DrawPlayerField(x, y);
8481 TEST_DrawLevelField(x, y);
8487 boolean wanna_flame = !RND(10);
8488 int dx = newx - x, dy = newy - y;
8489 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8490 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8491 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8492 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8493 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8494 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8497 IS_CLASSIC_ENEMY(element1) ||
8498 IS_CLASSIC_ENEMY(element2)) &&
8499 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8500 element1 != EL_FLAMES && element2 != EL_FLAMES)
8502 ResetGfxAnimation(x, y);
8503 GfxAction[x][y] = ACTION_ATTACKING;
8505 if (IS_PLAYER(x, y))
8506 DrawPlayerField(x, y);
8508 TEST_DrawLevelField(x, y);
8510 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8512 MovDelay[x][y] = 50;
8514 Tile[newx][newy] = EL_FLAMES;
8515 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8516 Tile[newx1][newy1] = EL_FLAMES;
8517 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8518 Tile[newx2][newy2] = EL_FLAMES;
8524 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8525 Tile[newx][newy] == EL_DIAMOND)
8527 if (IS_MOVING(newx, newy))
8528 RemoveMovingField(newx, newy);
8531 Tile[newx][newy] = EL_EMPTY;
8532 TEST_DrawLevelField(newx, newy);
8535 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8537 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8538 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8540 if (AmoebaNr[newx][newy])
8542 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8543 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8544 Tile[newx][newy] == EL_BD_AMOEBA)
8545 AmoebaCnt[AmoebaNr[newx][newy]]--;
8548 if (IS_MOVING(newx, newy))
8550 RemoveMovingField(newx, newy);
8554 Tile[newx][newy] = EL_EMPTY;
8555 TEST_DrawLevelField(newx, newy);
8558 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8560 else if ((element == EL_PACMAN || element == EL_MOLE)
8561 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8563 if (AmoebaNr[newx][newy])
8565 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8566 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8567 Tile[newx][newy] == EL_BD_AMOEBA)
8568 AmoebaCnt[AmoebaNr[newx][newy]]--;
8571 if (element == EL_MOLE)
8573 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8574 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8576 ResetGfxAnimation(x, y);
8577 GfxAction[x][y] = ACTION_DIGGING;
8578 TEST_DrawLevelField(x, y);
8580 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8582 return; // wait for shrinking amoeba
8584 else // element == EL_PACMAN
8586 Tile[newx][newy] = EL_EMPTY;
8587 TEST_DrawLevelField(newx, newy);
8588 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8591 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8592 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8593 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8595 // wait for shrinking amoeba to completely disappear
8598 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8600 // object was running against a wall
8604 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8605 DrawLevelElementAnimation(x, y, element);
8607 if (DONT_TOUCH(element))
8608 TestIfBadThingTouchesPlayer(x, y);
8613 InitMovingField(x, y, MovDir[x][y]);
8615 PlayLevelSoundAction(x, y, ACTION_MOVING);
8619 ContinueMoving(x, y);
8622 void ContinueMoving(int x, int y)
8624 int element = Tile[x][y];
8625 struct ElementInfo *ei = &element_info[element];
8626 int direction = MovDir[x][y];
8627 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8628 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8629 int newx = x + dx, newy = y + dy;
8630 int stored = Store[x][y];
8631 int stored_new = Store[newx][newy];
8632 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8633 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8634 boolean last_line = (newy == lev_fieldy - 1);
8635 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8637 if (pushed_by_player) // special case: moving object pushed by player
8639 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8641 else if (use_step_delay) // special case: moving object has step delay
8643 if (!MovDelay[x][y])
8644 MovPos[x][y] += getElementMoveStepsize(x, y);
8649 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8653 TEST_DrawLevelField(x, y);
8655 return; // element is still waiting
8658 else // normal case: generically moving object
8660 MovPos[x][y] += getElementMoveStepsize(x, y);
8663 if (ABS(MovPos[x][y]) < TILEX)
8665 TEST_DrawLevelField(x, y);
8667 return; // element is still moving
8670 // element reached destination field
8672 Tile[x][y] = EL_EMPTY;
8673 Tile[newx][newy] = element;
8674 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8676 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8678 element = Tile[newx][newy] = EL_ACID;
8680 else if (element == EL_MOLE)
8682 Tile[x][y] = EL_SAND;
8684 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8686 else if (element == EL_QUICKSAND_FILLING)
8688 element = Tile[newx][newy] = get_next_element(element);
8689 Store[newx][newy] = Store[x][y];
8691 else if (element == EL_QUICKSAND_EMPTYING)
8693 Tile[x][y] = get_next_element(element);
8694 element = Tile[newx][newy] = Store[x][y];
8696 else if (element == EL_QUICKSAND_FAST_FILLING)
8698 element = Tile[newx][newy] = get_next_element(element);
8699 Store[newx][newy] = Store[x][y];
8701 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8703 Tile[x][y] = get_next_element(element);
8704 element = Tile[newx][newy] = Store[x][y];
8706 else if (element == EL_MAGIC_WALL_FILLING)
8708 element = Tile[newx][newy] = get_next_element(element);
8709 if (!game.magic_wall_active)
8710 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8711 Store[newx][newy] = Store[x][y];
8713 else if (element == EL_MAGIC_WALL_EMPTYING)
8715 Tile[x][y] = get_next_element(element);
8716 if (!game.magic_wall_active)
8717 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8718 element = Tile[newx][newy] = Store[x][y];
8720 InitField(newx, newy, FALSE);
8722 else if (element == EL_BD_MAGIC_WALL_FILLING)
8724 element = Tile[newx][newy] = get_next_element(element);
8725 if (!game.magic_wall_active)
8726 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8727 Store[newx][newy] = Store[x][y];
8729 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8731 Tile[x][y] = get_next_element(element);
8732 if (!game.magic_wall_active)
8733 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8734 element = Tile[newx][newy] = Store[x][y];
8736 InitField(newx, newy, FALSE);
8738 else if (element == EL_DC_MAGIC_WALL_FILLING)
8740 element = Tile[newx][newy] = get_next_element(element);
8741 if (!game.magic_wall_active)
8742 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8743 Store[newx][newy] = Store[x][y];
8745 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8747 Tile[x][y] = get_next_element(element);
8748 if (!game.magic_wall_active)
8749 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8750 element = Tile[newx][newy] = Store[x][y];
8752 InitField(newx, newy, FALSE);
8754 else if (element == EL_AMOEBA_DROPPING)
8756 Tile[x][y] = get_next_element(element);
8757 element = Tile[newx][newy] = Store[x][y];
8759 else if (element == EL_SOKOBAN_OBJECT)
8762 Tile[x][y] = Back[x][y];
8764 if (Back[newx][newy])
8765 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8767 Back[x][y] = Back[newx][newy] = 0;
8770 Store[x][y] = EL_EMPTY;
8775 MovDelay[newx][newy] = 0;
8777 if (CAN_CHANGE_OR_HAS_ACTION(element))
8779 // copy element change control values to new field
8780 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8781 ChangePage[newx][newy] = ChangePage[x][y];
8782 ChangeCount[newx][newy] = ChangeCount[x][y];
8783 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8786 CustomValue[newx][newy] = CustomValue[x][y];
8788 ChangeDelay[x][y] = 0;
8789 ChangePage[x][y] = -1;
8790 ChangeCount[x][y] = 0;
8791 ChangeEvent[x][y] = -1;
8793 CustomValue[x][y] = 0;
8795 // copy animation control values to new field
8796 GfxFrame[newx][newy] = GfxFrame[x][y];
8797 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8798 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8799 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8801 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8803 // some elements can leave other elements behind after moving
8804 if (ei->move_leave_element != EL_EMPTY &&
8805 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8806 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8808 int move_leave_element = ei->move_leave_element;
8810 // this makes it possible to leave the removed element again
8811 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8812 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8814 Tile[x][y] = move_leave_element;
8816 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8817 MovDir[x][y] = direction;
8819 InitField(x, y, FALSE);
8821 if (GFX_CRUMBLED(Tile[x][y]))
8822 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8824 if (IS_PLAYER_ELEMENT(move_leave_element))
8825 RelocatePlayer(x, y, move_leave_element);
8828 // do this after checking for left-behind element
8829 ResetGfxAnimation(x, y); // reset animation values for old field
8831 if (!CAN_MOVE(element) ||
8832 (CAN_FALL(element) && direction == MV_DOWN &&
8833 (element == EL_SPRING ||
8834 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8835 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8836 GfxDir[x][y] = MovDir[newx][newy] = 0;
8838 TEST_DrawLevelField(x, y);
8839 TEST_DrawLevelField(newx, newy);
8841 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8843 // prevent pushed element from moving on in pushed direction
8844 if (pushed_by_player && CAN_MOVE(element) &&
8845 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8846 !(element_info[element].move_pattern & direction))
8847 TurnRound(newx, newy);
8849 // prevent elements on conveyor belt from moving on in last direction
8850 if (pushed_by_conveyor && CAN_FALL(element) &&
8851 direction & MV_HORIZONTAL)
8852 MovDir[newx][newy] = 0;
8854 if (!pushed_by_player)
8856 int nextx = newx + dx, nexty = newy + dy;
8857 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8859 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8861 if (CAN_FALL(element) && direction == MV_DOWN)
8862 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8864 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8865 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8867 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8868 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8871 if (DONT_TOUCH(element)) // object may be nasty to player or others
8873 TestIfBadThingTouchesPlayer(newx, newy);
8874 TestIfBadThingTouchesFriend(newx, newy);
8876 if (!IS_CUSTOM_ELEMENT(element))
8877 TestIfBadThingTouchesOtherBadThing(newx, newy);
8879 else if (element == EL_PENGUIN)
8880 TestIfFriendTouchesBadThing(newx, newy);
8882 if (DONT_GET_HIT_BY(element))
8884 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8887 // give the player one last chance (one more frame) to move away
8888 if (CAN_FALL(element) && direction == MV_DOWN &&
8889 (last_line || (!IS_FREE(x, newy + 1) &&
8890 (!IS_PLAYER(x, newy + 1) ||
8891 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8894 if (pushed_by_player && !game.use_change_when_pushing_bug)
8896 int push_side = MV_DIR_OPPOSITE(direction);
8897 struct PlayerInfo *player = PLAYERINFO(x, y);
8899 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8900 player->index_bit, push_side);
8901 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8902 player->index_bit, push_side);
8905 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8906 MovDelay[newx][newy] = 1;
8908 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8910 TestIfElementTouchesCustomElement(x, y); // empty or new element
8911 TestIfElementHitsCustomElement(newx, newy, direction);
8912 TestIfPlayerTouchesCustomElement(newx, newy);
8913 TestIfElementTouchesCustomElement(newx, newy);
8915 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8916 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8917 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8918 MV_DIR_OPPOSITE(direction));
8921 int AmoebaNeighbourNr(int ax, int ay)
8924 int element = Tile[ax][ay];
8926 static int xy[4][2] =
8934 for (i = 0; i < NUM_DIRECTIONS; i++)
8936 int x = ax + xy[i][0];
8937 int y = ay + xy[i][1];
8939 if (!IN_LEV_FIELD(x, y))
8942 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8943 group_nr = AmoebaNr[x][y];
8949 static void AmoebaMerge(int ax, int ay)
8951 int i, x, y, xx, yy;
8952 int new_group_nr = AmoebaNr[ax][ay];
8953 static int xy[4][2] =
8961 if (new_group_nr == 0)
8964 for (i = 0; i < NUM_DIRECTIONS; i++)
8969 if (!IN_LEV_FIELD(x, y))
8972 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8973 Tile[x][y] == EL_BD_AMOEBA ||
8974 Tile[x][y] == EL_AMOEBA_DEAD) &&
8975 AmoebaNr[x][y] != new_group_nr)
8977 int old_group_nr = AmoebaNr[x][y];
8979 if (old_group_nr == 0)
8982 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8983 AmoebaCnt[old_group_nr] = 0;
8984 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8985 AmoebaCnt2[old_group_nr] = 0;
8987 SCAN_PLAYFIELD(xx, yy)
8989 if (AmoebaNr[xx][yy] == old_group_nr)
8990 AmoebaNr[xx][yy] = new_group_nr;
8996 void AmoebaToDiamond(int ax, int ay)
9000 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9002 int group_nr = AmoebaNr[ax][ay];
9007 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9008 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9014 SCAN_PLAYFIELD(x, y)
9016 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9019 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9023 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9024 SND_AMOEBA_TURNING_TO_GEM :
9025 SND_AMOEBA_TURNING_TO_ROCK));
9030 static int xy[4][2] =
9038 for (i = 0; i < NUM_DIRECTIONS; i++)
9043 if (!IN_LEV_FIELD(x, y))
9046 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9048 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9049 SND_AMOEBA_TURNING_TO_GEM :
9050 SND_AMOEBA_TURNING_TO_ROCK));
9057 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9060 int group_nr = AmoebaNr[ax][ay];
9061 boolean done = FALSE;
9066 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9067 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9073 SCAN_PLAYFIELD(x, y)
9075 if (AmoebaNr[x][y] == group_nr &&
9076 (Tile[x][y] == EL_AMOEBA_DEAD ||
9077 Tile[x][y] == EL_BD_AMOEBA ||
9078 Tile[x][y] == EL_AMOEBA_GROWING))
9081 Tile[x][y] = new_element;
9082 InitField(x, y, FALSE);
9083 TEST_DrawLevelField(x, y);
9089 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9090 SND_BD_AMOEBA_TURNING_TO_ROCK :
9091 SND_BD_AMOEBA_TURNING_TO_GEM));
9094 static void AmoebaGrowing(int x, int y)
9096 static unsigned int sound_delay = 0;
9097 static unsigned int sound_delay_value = 0;
9099 if (!MovDelay[x][y]) // start new growing cycle
9103 if (DelayReached(&sound_delay, sound_delay_value))
9105 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9106 sound_delay_value = 30;
9110 if (MovDelay[x][y]) // wait some time before growing bigger
9113 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9115 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9116 6 - MovDelay[x][y]);
9118 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
9121 if (!MovDelay[x][y])
9123 Tile[x][y] = Store[x][y];
9125 TEST_DrawLevelField(x, y);
9130 static void AmoebaShrinking(int x, int y)
9132 static unsigned int sound_delay = 0;
9133 static unsigned int sound_delay_value = 0;
9135 if (!MovDelay[x][y]) // start new shrinking cycle
9139 if (DelayReached(&sound_delay, sound_delay_value))
9140 sound_delay_value = 30;
9143 if (MovDelay[x][y]) // wait some time before shrinking
9146 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9148 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9149 6 - MovDelay[x][y]);
9151 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
9154 if (!MovDelay[x][y])
9156 Tile[x][y] = EL_EMPTY;
9157 TEST_DrawLevelField(x, y);
9159 // don't let mole enter this field in this cycle;
9160 // (give priority to objects falling to this field from above)
9166 static void AmoebaReproduce(int ax, int ay)
9169 int element = Tile[ax][ay];
9170 int graphic = el2img(element);
9171 int newax = ax, neway = ay;
9172 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9173 static int xy[4][2] =
9181 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9183 Tile[ax][ay] = EL_AMOEBA_DEAD;
9184 TEST_DrawLevelField(ax, ay);
9188 if (IS_ANIMATED(graphic))
9189 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9191 if (!MovDelay[ax][ay]) // start making new amoeba field
9192 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9194 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9197 if (MovDelay[ax][ay])
9201 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9204 int x = ax + xy[start][0];
9205 int y = ay + xy[start][1];
9207 if (!IN_LEV_FIELD(x, y))
9210 if (IS_FREE(x, y) ||
9211 CAN_GROW_INTO(Tile[x][y]) ||
9212 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9213 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9219 if (newax == ax && neway == ay)
9222 else // normal or "filled" (BD style) amoeba
9225 boolean waiting_for_player = FALSE;
9227 for (i = 0; i < NUM_DIRECTIONS; i++)
9229 int j = (start + i) % 4;
9230 int x = ax + xy[j][0];
9231 int y = ay + xy[j][1];
9233 if (!IN_LEV_FIELD(x, y))
9236 if (IS_FREE(x, y) ||
9237 CAN_GROW_INTO(Tile[x][y]) ||
9238 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9239 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9245 else if (IS_PLAYER(x, y))
9246 waiting_for_player = TRUE;
9249 if (newax == ax && neway == ay) // amoeba cannot grow
9251 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9253 Tile[ax][ay] = EL_AMOEBA_DEAD;
9254 TEST_DrawLevelField(ax, ay);
9255 AmoebaCnt[AmoebaNr[ax][ay]]--;
9257 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9259 if (element == EL_AMOEBA_FULL)
9260 AmoebaToDiamond(ax, ay);
9261 else if (element == EL_BD_AMOEBA)
9262 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9267 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9269 // amoeba gets larger by growing in some direction
9271 int new_group_nr = AmoebaNr[ax][ay];
9274 if (new_group_nr == 0)
9276 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9278 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9284 AmoebaNr[newax][neway] = new_group_nr;
9285 AmoebaCnt[new_group_nr]++;
9286 AmoebaCnt2[new_group_nr]++;
9288 // if amoeba touches other amoeba(s) after growing, unify them
9289 AmoebaMerge(newax, neway);
9291 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9293 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9299 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9300 (neway == lev_fieldy - 1 && newax != ax))
9302 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9303 Store[newax][neway] = element;
9305 else if (neway == ay || element == EL_EMC_DRIPPER)
9307 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9309 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9313 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9314 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9315 Store[ax][ay] = EL_AMOEBA_DROP;
9316 ContinueMoving(ax, ay);
9320 TEST_DrawLevelField(newax, neway);
9323 static void Life(int ax, int ay)
9327 int element = Tile[ax][ay];
9328 int graphic = el2img(element);
9329 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9331 boolean changed = FALSE;
9333 if (IS_ANIMATED(graphic))
9334 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9339 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9340 MovDelay[ax][ay] = life_time;
9342 if (MovDelay[ax][ay]) // wait some time before next cycle
9345 if (MovDelay[ax][ay])
9349 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9351 int xx = ax+x1, yy = ay+y1;
9352 int old_element = Tile[xx][yy];
9353 int num_neighbours = 0;
9355 if (!IN_LEV_FIELD(xx, yy))
9358 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9360 int x = xx+x2, y = yy+y2;
9362 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9365 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9366 boolean is_neighbour = FALSE;
9368 if (level.use_life_bugs)
9370 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9371 (IS_FREE(x, y) && Stop[x][y]));
9374 (Last[x][y] == element || is_player_cell);
9380 boolean is_free = FALSE;
9382 if (level.use_life_bugs)
9383 is_free = (IS_FREE(xx, yy));
9385 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9387 if (xx == ax && yy == ay) // field in the middle
9389 if (num_neighbours < life_parameter[0] ||
9390 num_neighbours > life_parameter[1])
9392 Tile[xx][yy] = EL_EMPTY;
9393 if (Tile[xx][yy] != old_element)
9394 TEST_DrawLevelField(xx, yy);
9395 Stop[xx][yy] = TRUE;
9399 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9400 { // free border field
9401 if (num_neighbours >= life_parameter[2] &&
9402 num_neighbours <= life_parameter[3])
9404 Tile[xx][yy] = element;
9405 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9406 if (Tile[xx][yy] != old_element)
9407 TEST_DrawLevelField(xx, yy);
9408 Stop[xx][yy] = TRUE;
9415 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9416 SND_GAME_OF_LIFE_GROWING);
9419 static void InitRobotWheel(int x, int y)
9421 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9424 static void RunRobotWheel(int x, int y)
9426 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9429 static void StopRobotWheel(int x, int y)
9431 if (game.robot_wheel_x == x &&
9432 game.robot_wheel_y == y)
9434 game.robot_wheel_x = -1;
9435 game.robot_wheel_y = -1;
9436 game.robot_wheel_active = FALSE;
9440 static void InitTimegateWheel(int x, int y)
9442 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9445 static void RunTimegateWheel(int x, int y)
9447 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9450 static void InitMagicBallDelay(int x, int y)
9452 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9455 static void ActivateMagicBall(int bx, int by)
9459 if (level.ball_random)
9461 int pos_border = RND(8); // select one of the eight border elements
9462 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9463 int xx = pos_content % 3;
9464 int yy = pos_content / 3;
9469 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9470 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9474 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9476 int xx = x - bx + 1;
9477 int yy = y - by + 1;
9479 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9480 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9484 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9487 static void CheckExit(int x, int y)
9489 if (game.gems_still_needed > 0 ||
9490 game.sokoban_fields_still_needed > 0 ||
9491 game.sokoban_objects_still_needed > 0 ||
9492 game.lights_still_needed > 0)
9494 int element = Tile[x][y];
9495 int graphic = el2img(element);
9497 if (IS_ANIMATED(graphic))
9498 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9503 // do not re-open exit door closed after last player
9504 if (game.all_players_gone)
9507 Tile[x][y] = EL_EXIT_OPENING;
9509 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9512 static void CheckExitEM(int x, int y)
9514 if (game.gems_still_needed > 0 ||
9515 game.sokoban_fields_still_needed > 0 ||
9516 game.sokoban_objects_still_needed > 0 ||
9517 game.lights_still_needed > 0)
9519 int element = Tile[x][y];
9520 int graphic = el2img(element);
9522 if (IS_ANIMATED(graphic))
9523 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9528 // do not re-open exit door closed after last player
9529 if (game.all_players_gone)
9532 Tile[x][y] = EL_EM_EXIT_OPENING;
9534 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9537 static void CheckExitSteel(int x, int y)
9539 if (game.gems_still_needed > 0 ||
9540 game.sokoban_fields_still_needed > 0 ||
9541 game.sokoban_objects_still_needed > 0 ||
9542 game.lights_still_needed > 0)
9544 int element = Tile[x][y];
9545 int graphic = el2img(element);
9547 if (IS_ANIMATED(graphic))
9548 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9553 // do not re-open exit door closed after last player
9554 if (game.all_players_gone)
9557 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9559 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9562 static void CheckExitSteelEM(int x, int y)
9564 if (game.gems_still_needed > 0 ||
9565 game.sokoban_fields_still_needed > 0 ||
9566 game.sokoban_objects_still_needed > 0 ||
9567 game.lights_still_needed > 0)
9569 int element = Tile[x][y];
9570 int graphic = el2img(element);
9572 if (IS_ANIMATED(graphic))
9573 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9578 // do not re-open exit door closed after last player
9579 if (game.all_players_gone)
9582 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9584 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9587 static void CheckExitSP(int x, int y)
9589 if (game.gems_still_needed > 0)
9591 int element = Tile[x][y];
9592 int graphic = el2img(element);
9594 if (IS_ANIMATED(graphic))
9595 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9600 // do not re-open exit door closed after last player
9601 if (game.all_players_gone)
9604 Tile[x][y] = EL_SP_EXIT_OPENING;
9606 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9609 static void CloseAllOpenTimegates(void)
9613 SCAN_PLAYFIELD(x, y)
9615 int element = Tile[x][y];
9617 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9619 Tile[x][y] = EL_TIMEGATE_CLOSING;
9621 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9626 static void DrawTwinkleOnField(int x, int y)
9628 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9631 if (Tile[x][y] == EL_BD_DIAMOND)
9634 if (MovDelay[x][y] == 0) // next animation frame
9635 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9637 if (MovDelay[x][y] != 0) // wait some time before next frame
9641 DrawLevelElementAnimation(x, y, Tile[x][y]);
9643 if (MovDelay[x][y] != 0)
9645 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9646 10 - MovDelay[x][y]);
9648 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9653 static void MauerWaechst(int x, int y)
9657 if (!MovDelay[x][y]) // next animation frame
9658 MovDelay[x][y] = 3 * delay;
9660 if (MovDelay[x][y]) // wait some time before next frame
9664 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9666 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9667 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9669 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9672 if (!MovDelay[x][y])
9674 if (MovDir[x][y] == MV_LEFT)
9676 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9677 TEST_DrawLevelField(x - 1, y);
9679 else if (MovDir[x][y] == MV_RIGHT)
9681 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9682 TEST_DrawLevelField(x + 1, y);
9684 else if (MovDir[x][y] == MV_UP)
9686 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9687 TEST_DrawLevelField(x, y - 1);
9691 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9692 TEST_DrawLevelField(x, y + 1);
9695 Tile[x][y] = Store[x][y];
9697 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9698 TEST_DrawLevelField(x, y);
9703 static void MauerAbleger(int ax, int ay)
9705 int element = Tile[ax][ay];
9706 int graphic = el2img(element);
9707 boolean oben_frei = FALSE, unten_frei = FALSE;
9708 boolean links_frei = FALSE, rechts_frei = FALSE;
9709 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9710 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9711 boolean new_wall = FALSE;
9713 if (IS_ANIMATED(graphic))
9714 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9716 if (!MovDelay[ax][ay]) // start building new wall
9717 MovDelay[ax][ay] = 6;
9719 if (MovDelay[ax][ay]) // wait some time before building new wall
9722 if (MovDelay[ax][ay])
9726 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9728 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9730 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9732 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9735 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9736 element == EL_EXPANDABLE_WALL_ANY)
9740 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9741 Store[ax][ay-1] = element;
9742 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9743 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9744 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9745 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9750 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9751 Store[ax][ay+1] = element;
9752 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9753 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9754 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9755 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9760 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9761 element == EL_EXPANDABLE_WALL_ANY ||
9762 element == EL_EXPANDABLE_WALL ||
9763 element == EL_BD_EXPANDABLE_WALL)
9767 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9768 Store[ax-1][ay] = element;
9769 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9770 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9771 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9772 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9778 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9779 Store[ax+1][ay] = element;
9780 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9781 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9782 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9783 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9788 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9789 TEST_DrawLevelField(ax, ay);
9791 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9793 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9794 unten_massiv = TRUE;
9795 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9796 links_massiv = TRUE;
9797 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9798 rechts_massiv = TRUE;
9800 if (((oben_massiv && unten_massiv) ||
9801 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9802 element == EL_EXPANDABLE_WALL) &&
9803 ((links_massiv && rechts_massiv) ||
9804 element == EL_EXPANDABLE_WALL_VERTICAL))
9805 Tile[ax][ay] = EL_WALL;
9808 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9811 static void MauerAblegerStahl(int ax, int ay)
9813 int element = Tile[ax][ay];
9814 int graphic = el2img(element);
9815 boolean oben_frei = FALSE, unten_frei = FALSE;
9816 boolean links_frei = FALSE, rechts_frei = FALSE;
9817 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9818 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9819 boolean new_wall = FALSE;
9821 if (IS_ANIMATED(graphic))
9822 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9824 if (!MovDelay[ax][ay]) // start building new wall
9825 MovDelay[ax][ay] = 6;
9827 if (MovDelay[ax][ay]) // wait some time before building new wall
9830 if (MovDelay[ax][ay])
9834 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9836 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9838 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9840 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9843 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9844 element == EL_EXPANDABLE_STEELWALL_ANY)
9848 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9849 Store[ax][ay-1] = element;
9850 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9851 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9852 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9853 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9858 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9859 Store[ax][ay+1] = element;
9860 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9861 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9862 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9863 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9868 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9869 element == EL_EXPANDABLE_STEELWALL_ANY)
9873 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9874 Store[ax-1][ay] = element;
9875 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9876 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9877 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9878 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9884 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9885 Store[ax+1][ay] = element;
9886 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9887 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9888 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9889 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9894 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9896 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9897 unten_massiv = TRUE;
9898 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9899 links_massiv = TRUE;
9900 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9901 rechts_massiv = TRUE;
9903 if (((oben_massiv && unten_massiv) ||
9904 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9905 ((links_massiv && rechts_massiv) ||
9906 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9907 Tile[ax][ay] = EL_STEELWALL;
9910 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9913 static void CheckForDragon(int x, int y)
9916 boolean dragon_found = FALSE;
9917 static int xy[4][2] =
9925 for (i = 0; i < NUM_DIRECTIONS; i++)
9927 for (j = 0; j < 4; j++)
9929 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9931 if (IN_LEV_FIELD(xx, yy) &&
9932 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9934 if (Tile[xx][yy] == EL_DRAGON)
9935 dragon_found = TRUE;
9944 for (i = 0; i < NUM_DIRECTIONS; i++)
9946 for (j = 0; j < 3; j++)
9948 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9950 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9952 Tile[xx][yy] = EL_EMPTY;
9953 TEST_DrawLevelField(xx, yy);
9962 static void InitBuggyBase(int x, int y)
9964 int element = Tile[x][y];
9965 int activating_delay = FRAMES_PER_SECOND / 4;
9968 (element == EL_SP_BUGGY_BASE ?
9969 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9970 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9972 element == EL_SP_BUGGY_BASE_ACTIVE ?
9973 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9976 static void WarnBuggyBase(int x, int y)
9979 static int xy[4][2] =
9987 for (i = 0; i < NUM_DIRECTIONS; i++)
9989 int xx = x + xy[i][0];
9990 int yy = y + xy[i][1];
9992 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9994 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10001 static void InitTrap(int x, int y)
10003 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10006 static void ActivateTrap(int x, int y)
10008 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10011 static void ChangeActiveTrap(int x, int y)
10013 int graphic = IMG_TRAP_ACTIVE;
10015 // if new animation frame was drawn, correct crumbled sand border
10016 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10017 TEST_DrawLevelFieldCrumbled(x, y);
10020 static int getSpecialActionElement(int element, int number, int base_element)
10022 return (element != EL_EMPTY ? element :
10023 number != -1 ? base_element + number - 1 :
10027 static int getModifiedActionNumber(int value_old, int operator, int operand,
10028 int value_min, int value_max)
10030 int value_new = (operator == CA_MODE_SET ? operand :
10031 operator == CA_MODE_ADD ? value_old + operand :
10032 operator == CA_MODE_SUBTRACT ? value_old - operand :
10033 operator == CA_MODE_MULTIPLY ? value_old * operand :
10034 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10035 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10038 return (value_new < value_min ? value_min :
10039 value_new > value_max ? value_max :
10043 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10045 struct ElementInfo *ei = &element_info[element];
10046 struct ElementChangeInfo *change = &ei->change_page[page];
10047 int target_element = change->target_element;
10048 int action_type = change->action_type;
10049 int action_mode = change->action_mode;
10050 int action_arg = change->action_arg;
10051 int action_element = change->action_element;
10054 if (!change->has_action)
10057 // ---------- determine action paramater values -----------------------------
10059 int level_time_value =
10060 (level.time > 0 ? TimeLeft :
10063 int action_arg_element_raw =
10064 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10065 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10066 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10067 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10068 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10069 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10070 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10072 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10074 int action_arg_direction =
10075 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10076 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10077 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10078 change->actual_trigger_side :
10079 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10080 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10083 int action_arg_number_min =
10084 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10087 int action_arg_number_max =
10088 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10089 action_type == CA_SET_LEVEL_GEMS ? 999 :
10090 action_type == CA_SET_LEVEL_TIME ? 9999 :
10091 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10092 action_type == CA_SET_CE_VALUE ? 9999 :
10093 action_type == CA_SET_CE_SCORE ? 9999 :
10096 int action_arg_number_reset =
10097 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10098 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10099 action_type == CA_SET_LEVEL_TIME ? level.time :
10100 action_type == CA_SET_LEVEL_SCORE ? 0 :
10101 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10102 action_type == CA_SET_CE_SCORE ? 0 :
10105 int action_arg_number =
10106 (action_arg <= CA_ARG_MAX ? action_arg :
10107 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10108 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10109 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10110 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10111 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10112 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10113 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10114 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10115 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10116 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10117 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10118 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10119 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10120 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10121 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10122 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10123 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10124 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10125 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10126 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10127 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10130 int action_arg_number_old =
10131 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10132 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10133 action_type == CA_SET_LEVEL_SCORE ? game.score :
10134 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10135 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10138 int action_arg_number_new =
10139 getModifiedActionNumber(action_arg_number_old,
10140 action_mode, action_arg_number,
10141 action_arg_number_min, action_arg_number_max);
10143 int trigger_player_bits =
10144 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10145 change->actual_trigger_player_bits : change->trigger_player);
10147 int action_arg_player_bits =
10148 (action_arg >= CA_ARG_PLAYER_1 &&
10149 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10150 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10151 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10154 // ---------- execute action -----------------------------------------------
10156 switch (action_type)
10163 // ---------- level actions ----------------------------------------------
10165 case CA_RESTART_LEVEL:
10167 game.restart_level = TRUE;
10172 case CA_SHOW_ENVELOPE:
10174 int element = getSpecialActionElement(action_arg_element,
10175 action_arg_number, EL_ENVELOPE_1);
10177 if (IS_ENVELOPE(element))
10178 local_player->show_envelope = element;
10183 case CA_SET_LEVEL_TIME:
10185 if (level.time > 0) // only modify limited time value
10187 TimeLeft = action_arg_number_new;
10189 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10191 DisplayGameControlValues();
10193 if (!TimeLeft && setup.time_limit)
10194 for (i = 0; i < MAX_PLAYERS; i++)
10195 KillPlayer(&stored_player[i]);
10201 case CA_SET_LEVEL_SCORE:
10203 game.score = action_arg_number_new;
10205 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10207 DisplayGameControlValues();
10212 case CA_SET_LEVEL_GEMS:
10214 game.gems_still_needed = action_arg_number_new;
10216 game.snapshot.collected_item = TRUE;
10218 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10220 DisplayGameControlValues();
10225 case CA_SET_LEVEL_WIND:
10227 game.wind_direction = action_arg_direction;
10232 case CA_SET_LEVEL_RANDOM_SEED:
10234 // ensure that setting a new random seed while playing is predictable
10235 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10240 // ---------- player actions ---------------------------------------------
10242 case CA_MOVE_PLAYER:
10243 case CA_MOVE_PLAYER_NEW:
10245 // automatically move to the next field in specified direction
10246 for (i = 0; i < MAX_PLAYERS; i++)
10247 if (trigger_player_bits & (1 << i))
10248 if (action_type == CA_MOVE_PLAYER ||
10249 stored_player[i].MovPos == 0)
10250 stored_player[i].programmed_action = action_arg_direction;
10255 case CA_EXIT_PLAYER:
10257 for (i = 0; i < MAX_PLAYERS; i++)
10258 if (action_arg_player_bits & (1 << i))
10259 ExitPlayer(&stored_player[i]);
10261 if (game.players_still_needed == 0)
10267 case CA_KILL_PLAYER:
10269 for (i = 0; i < MAX_PLAYERS; i++)
10270 if (action_arg_player_bits & (1 << i))
10271 KillPlayer(&stored_player[i]);
10276 case CA_SET_PLAYER_KEYS:
10278 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10279 int element = getSpecialActionElement(action_arg_element,
10280 action_arg_number, EL_KEY_1);
10282 if (IS_KEY(element))
10284 for (i = 0; i < MAX_PLAYERS; i++)
10286 if (trigger_player_bits & (1 << i))
10288 stored_player[i].key[KEY_NR(element)] = key_state;
10290 DrawGameDoorValues();
10298 case CA_SET_PLAYER_SPEED:
10300 for (i = 0; i < MAX_PLAYERS; i++)
10302 if (trigger_player_bits & (1 << i))
10304 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10306 if (action_arg == CA_ARG_SPEED_FASTER &&
10307 stored_player[i].cannot_move)
10309 action_arg_number = STEPSIZE_VERY_SLOW;
10311 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10312 action_arg == CA_ARG_SPEED_FASTER)
10314 action_arg_number = 2;
10315 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10318 else if (action_arg == CA_ARG_NUMBER_RESET)
10320 action_arg_number = level.initial_player_stepsize[i];
10324 getModifiedActionNumber(move_stepsize,
10327 action_arg_number_min,
10328 action_arg_number_max);
10330 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10337 case CA_SET_PLAYER_SHIELD:
10339 for (i = 0; i < MAX_PLAYERS; i++)
10341 if (trigger_player_bits & (1 << i))
10343 if (action_arg == CA_ARG_SHIELD_OFF)
10345 stored_player[i].shield_normal_time_left = 0;
10346 stored_player[i].shield_deadly_time_left = 0;
10348 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10350 stored_player[i].shield_normal_time_left = 999999;
10352 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10354 stored_player[i].shield_normal_time_left = 999999;
10355 stored_player[i].shield_deadly_time_left = 999999;
10363 case CA_SET_PLAYER_GRAVITY:
10365 for (i = 0; i < MAX_PLAYERS; i++)
10367 if (trigger_player_bits & (1 << i))
10369 stored_player[i].gravity =
10370 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10371 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10372 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10373 stored_player[i].gravity);
10380 case CA_SET_PLAYER_ARTWORK:
10382 for (i = 0; i < MAX_PLAYERS; i++)
10384 if (trigger_player_bits & (1 << i))
10386 int artwork_element = action_arg_element;
10388 if (action_arg == CA_ARG_ELEMENT_RESET)
10390 (level.use_artwork_element[i] ? level.artwork_element[i] :
10391 stored_player[i].element_nr);
10393 if (stored_player[i].artwork_element != artwork_element)
10394 stored_player[i].Frame = 0;
10396 stored_player[i].artwork_element = artwork_element;
10398 SetPlayerWaiting(&stored_player[i], FALSE);
10400 // set number of special actions for bored and sleeping animation
10401 stored_player[i].num_special_action_bored =
10402 get_num_special_action(artwork_element,
10403 ACTION_BORING_1, ACTION_BORING_LAST);
10404 stored_player[i].num_special_action_sleeping =
10405 get_num_special_action(artwork_element,
10406 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10413 case CA_SET_PLAYER_INVENTORY:
10415 for (i = 0; i < MAX_PLAYERS; i++)
10417 struct PlayerInfo *player = &stored_player[i];
10420 if (trigger_player_bits & (1 << i))
10422 int inventory_element = action_arg_element;
10424 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10425 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10426 action_arg == CA_ARG_ELEMENT_ACTION)
10428 int element = inventory_element;
10429 int collect_count = element_info[element].collect_count_initial;
10431 if (!IS_CUSTOM_ELEMENT(element))
10434 if (collect_count == 0)
10435 player->inventory_infinite_element = element;
10437 for (k = 0; k < collect_count; k++)
10438 if (player->inventory_size < MAX_INVENTORY_SIZE)
10439 player->inventory_element[player->inventory_size++] =
10442 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10443 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10444 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10446 if (player->inventory_infinite_element != EL_UNDEFINED &&
10447 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10448 action_arg_element_raw))
10449 player->inventory_infinite_element = EL_UNDEFINED;
10451 for (k = 0, j = 0; j < player->inventory_size; j++)
10453 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10454 action_arg_element_raw))
10455 player->inventory_element[k++] = player->inventory_element[j];
10458 player->inventory_size = k;
10460 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10462 if (player->inventory_size > 0)
10464 for (j = 0; j < player->inventory_size - 1; j++)
10465 player->inventory_element[j] = player->inventory_element[j + 1];
10467 player->inventory_size--;
10470 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10472 if (player->inventory_size > 0)
10473 player->inventory_size--;
10475 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10477 player->inventory_infinite_element = EL_UNDEFINED;
10478 player->inventory_size = 0;
10480 else if (action_arg == CA_ARG_INVENTORY_RESET)
10482 player->inventory_infinite_element = EL_UNDEFINED;
10483 player->inventory_size = 0;
10485 if (level.use_initial_inventory[i])
10487 for (j = 0; j < level.initial_inventory_size[i]; j++)
10489 int element = level.initial_inventory_content[i][j];
10490 int collect_count = element_info[element].collect_count_initial;
10492 if (!IS_CUSTOM_ELEMENT(element))
10495 if (collect_count == 0)
10496 player->inventory_infinite_element = element;
10498 for (k = 0; k < collect_count; k++)
10499 if (player->inventory_size < MAX_INVENTORY_SIZE)
10500 player->inventory_element[player->inventory_size++] =
10511 // ---------- CE actions -------------------------------------------------
10513 case CA_SET_CE_VALUE:
10515 int last_ce_value = CustomValue[x][y];
10517 CustomValue[x][y] = action_arg_number_new;
10519 if (CustomValue[x][y] != last_ce_value)
10521 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10522 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10524 if (CustomValue[x][y] == 0)
10526 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10527 ChangeCount[x][y] = 0; // allow at least one more change
10529 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10530 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10537 case CA_SET_CE_SCORE:
10539 int last_ce_score = ei->collect_score;
10541 ei->collect_score = action_arg_number_new;
10543 if (ei->collect_score != last_ce_score)
10545 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10546 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10548 if (ei->collect_score == 0)
10552 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10553 ChangeCount[x][y] = 0; // allow at least one more change
10555 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10556 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10559 This is a very special case that seems to be a mixture between
10560 CheckElementChange() and CheckTriggeredElementChange(): while
10561 the first one only affects single elements that are triggered
10562 directly, the second one affects multiple elements in the playfield
10563 that are triggered indirectly by another element. This is a third
10564 case: Changing the CE score always affects multiple identical CEs,
10565 so every affected CE must be checked, not only the single CE for
10566 which the CE score was changed in the first place (as every instance
10567 of that CE shares the same CE score, and therefore also can change)!
10569 SCAN_PLAYFIELD(xx, yy)
10571 if (Tile[xx][yy] == element)
10572 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10573 CE_SCORE_GETS_ZERO);
10581 case CA_SET_CE_ARTWORK:
10583 int artwork_element = action_arg_element;
10584 boolean reset_frame = FALSE;
10587 if (action_arg == CA_ARG_ELEMENT_RESET)
10588 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10591 if (ei->gfx_element != artwork_element)
10592 reset_frame = TRUE;
10594 ei->gfx_element = artwork_element;
10596 SCAN_PLAYFIELD(xx, yy)
10598 if (Tile[xx][yy] == element)
10602 ResetGfxAnimation(xx, yy);
10603 ResetRandomAnimationValue(xx, yy);
10606 TEST_DrawLevelField(xx, yy);
10613 // ---------- engine actions ---------------------------------------------
10615 case CA_SET_ENGINE_SCAN_MODE:
10617 InitPlayfieldScanMode(action_arg);
10627 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10629 int old_element = Tile[x][y];
10630 int new_element = GetElementFromGroupElement(element);
10631 int previous_move_direction = MovDir[x][y];
10632 int last_ce_value = CustomValue[x][y];
10633 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10634 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10635 boolean add_player_onto_element = (new_element_is_player &&
10636 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10637 IS_WALKABLE(old_element));
10639 if (!add_player_onto_element)
10641 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10642 RemoveMovingField(x, y);
10646 Tile[x][y] = new_element;
10648 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10649 MovDir[x][y] = previous_move_direction;
10651 if (element_info[new_element].use_last_ce_value)
10652 CustomValue[x][y] = last_ce_value;
10654 InitField_WithBug1(x, y, FALSE);
10656 new_element = Tile[x][y]; // element may have changed
10658 ResetGfxAnimation(x, y);
10659 ResetRandomAnimationValue(x, y);
10661 TEST_DrawLevelField(x, y);
10663 if (GFX_CRUMBLED(new_element))
10664 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10667 // check if element under the player changes from accessible to unaccessible
10668 // (needed for special case of dropping element which then changes)
10669 // (must be checked after creating new element for walkable group elements)
10670 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10671 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10678 // "ChangeCount" not set yet to allow "entered by player" change one time
10679 if (new_element_is_player)
10680 RelocatePlayer(x, y, new_element);
10683 ChangeCount[x][y]++; // count number of changes in the same frame
10685 TestIfBadThingTouchesPlayer(x, y);
10686 TestIfPlayerTouchesCustomElement(x, y);
10687 TestIfElementTouchesCustomElement(x, y);
10690 static void CreateField(int x, int y, int element)
10692 CreateFieldExt(x, y, element, FALSE);
10695 static void CreateElementFromChange(int x, int y, int element)
10697 element = GET_VALID_RUNTIME_ELEMENT(element);
10699 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10701 int old_element = Tile[x][y];
10703 // prevent changed element from moving in same engine frame
10704 // unless both old and new element can either fall or move
10705 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10706 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10710 CreateFieldExt(x, y, element, TRUE);
10713 static boolean ChangeElement(int x, int y, int element, int page)
10715 struct ElementInfo *ei = &element_info[element];
10716 struct ElementChangeInfo *change = &ei->change_page[page];
10717 int ce_value = CustomValue[x][y];
10718 int ce_score = ei->collect_score;
10719 int target_element;
10720 int old_element = Tile[x][y];
10722 // always use default change event to prevent running into a loop
10723 if (ChangeEvent[x][y] == -1)
10724 ChangeEvent[x][y] = CE_DELAY;
10726 if (ChangeEvent[x][y] == CE_DELAY)
10728 // reset actual trigger element, trigger player and action element
10729 change->actual_trigger_element = EL_EMPTY;
10730 change->actual_trigger_player = EL_EMPTY;
10731 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10732 change->actual_trigger_side = CH_SIDE_NONE;
10733 change->actual_trigger_ce_value = 0;
10734 change->actual_trigger_ce_score = 0;
10737 // do not change elements more than a specified maximum number of changes
10738 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10741 ChangeCount[x][y]++; // count number of changes in the same frame
10743 if (change->explode)
10750 if (change->use_target_content)
10752 boolean complete_replace = TRUE;
10753 boolean can_replace[3][3];
10756 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10759 boolean is_walkable;
10760 boolean is_diggable;
10761 boolean is_collectible;
10762 boolean is_removable;
10763 boolean is_destructible;
10764 int ex = x + xx - 1;
10765 int ey = y + yy - 1;
10766 int content_element = change->target_content.e[xx][yy];
10769 can_replace[xx][yy] = TRUE;
10771 if (ex == x && ey == y) // do not check changing element itself
10774 if (content_element == EL_EMPTY_SPACE)
10776 can_replace[xx][yy] = FALSE; // do not replace border with space
10781 if (!IN_LEV_FIELD(ex, ey))
10783 can_replace[xx][yy] = FALSE;
10784 complete_replace = FALSE;
10791 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10792 e = MovingOrBlocked2Element(ex, ey);
10794 is_empty = (IS_FREE(ex, ey) ||
10795 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10797 is_walkable = (is_empty || IS_WALKABLE(e));
10798 is_diggable = (is_empty || IS_DIGGABLE(e));
10799 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10800 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10801 is_removable = (is_diggable || is_collectible);
10803 can_replace[xx][yy] =
10804 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10805 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10806 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10807 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10808 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10809 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10810 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10812 if (!can_replace[xx][yy])
10813 complete_replace = FALSE;
10816 if (!change->only_if_complete || complete_replace)
10818 boolean something_has_changed = FALSE;
10820 if (change->only_if_complete && change->use_random_replace &&
10821 RND(100) < change->random_percentage)
10824 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10826 int ex = x + xx - 1;
10827 int ey = y + yy - 1;
10828 int content_element;
10830 if (can_replace[xx][yy] && (!change->use_random_replace ||
10831 RND(100) < change->random_percentage))
10833 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10834 RemoveMovingField(ex, ey);
10836 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10838 content_element = change->target_content.e[xx][yy];
10839 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10840 ce_value, ce_score);
10842 CreateElementFromChange(ex, ey, target_element);
10844 something_has_changed = TRUE;
10846 // for symmetry reasons, freeze newly created border elements
10847 if (ex != x || ey != y)
10848 Stop[ex][ey] = TRUE; // no more moving in this frame
10852 if (something_has_changed)
10854 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10855 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10861 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10862 ce_value, ce_score);
10864 if (element == EL_DIAGONAL_GROWING ||
10865 element == EL_DIAGONAL_SHRINKING)
10867 target_element = Store[x][y];
10869 Store[x][y] = EL_EMPTY;
10872 // special case: element changes to player (and may be kept if walkable)
10873 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10874 CreateElementFromChange(x, y, EL_EMPTY);
10876 CreateElementFromChange(x, y, target_element);
10878 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10879 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10882 // this uses direct change before indirect change
10883 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10888 static void HandleElementChange(int x, int y, int page)
10890 int element = MovingOrBlocked2Element(x, y);
10891 struct ElementInfo *ei = &element_info[element];
10892 struct ElementChangeInfo *change = &ei->change_page[page];
10893 boolean handle_action_before_change = FALSE;
10896 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10897 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10899 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10900 x, y, element, element_info[element].token_name);
10901 Debug("game:playing:HandleElementChange", "This should never happen!");
10905 // this can happen with classic bombs on walkable, changing elements
10906 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10911 if (ChangeDelay[x][y] == 0) // initialize element change
10913 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10915 if (change->can_change)
10917 // !!! not clear why graphic animation should be reset at all here !!!
10918 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10919 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10922 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10924 When using an animation frame delay of 1 (this only happens with
10925 "sp_zonk.moving.left/right" in the classic graphics), the default
10926 (non-moving) animation shows wrong animation frames (while the
10927 moving animation, like "sp_zonk.moving.left/right", is correct,
10928 so this graphical bug never shows up with the classic graphics).
10929 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10930 be drawn instead of the correct frames 0,1,2,3. This is caused by
10931 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10932 an element change: First when the change delay ("ChangeDelay[][]")
10933 counter has reached zero after decrementing, then a second time in
10934 the next frame (after "GfxFrame[][]" was already incremented) when
10935 "ChangeDelay[][]" is reset to the initial delay value again.
10937 This causes frame 0 to be drawn twice, while the last frame won't
10938 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10940 As some animations may already be cleverly designed around this bug
10941 (at least the "Snake Bite" snake tail animation does this), it cannot
10942 simply be fixed here without breaking such existing animations.
10943 Unfortunately, it cannot easily be detected if a graphics set was
10944 designed "before" or "after" the bug was fixed. As a workaround,
10945 a new graphics set option "game.graphics_engine_version" was added
10946 to be able to specify the game's major release version for which the
10947 graphics set was designed, which can then be used to decide if the
10948 bugfix should be used (version 4 and above) or not (version 3 or
10949 below, or if no version was specified at all, as with old sets).
10951 (The wrong/fixed animation frames can be tested with the test level set
10952 "test_gfxframe" and level "000", which contains a specially prepared
10953 custom element at level position (x/y) == (11/9) which uses the zonk
10954 animation mentioned above. Using "game.graphics_engine_version: 4"
10955 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10956 This can also be seen from the debug output for this test element.)
10959 // when a custom element is about to change (for example by change delay),
10960 // do not reset graphic animation when the custom element is moving
10961 if (game.graphics_engine_version < 4 &&
10964 ResetGfxAnimation(x, y);
10965 ResetRandomAnimationValue(x, y);
10968 if (change->pre_change_function)
10969 change->pre_change_function(x, y);
10973 ChangeDelay[x][y]--;
10975 if (ChangeDelay[x][y] != 0) // continue element change
10977 if (change->can_change)
10979 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10981 if (IS_ANIMATED(graphic))
10982 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10984 if (change->change_function)
10985 change->change_function(x, y);
10988 else // finish element change
10990 if (ChangePage[x][y] != -1) // remember page from delayed change
10992 page = ChangePage[x][y];
10993 ChangePage[x][y] = -1;
10995 change = &ei->change_page[page];
10998 if (IS_MOVING(x, y)) // never change a running system ;-)
11000 ChangeDelay[x][y] = 1; // try change after next move step
11001 ChangePage[x][y] = page; // remember page to use for change
11006 // special case: set new level random seed before changing element
11007 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11008 handle_action_before_change = TRUE;
11010 if (change->has_action && handle_action_before_change)
11011 ExecuteCustomElementAction(x, y, element, page);
11013 if (change->can_change)
11015 if (ChangeElement(x, y, element, page))
11017 if (change->post_change_function)
11018 change->post_change_function(x, y);
11022 if (change->has_action && !handle_action_before_change)
11023 ExecuteCustomElementAction(x, y, element, page);
11027 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11028 int trigger_element,
11030 int trigger_player,
11034 boolean change_done_any = FALSE;
11035 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11038 if (!(trigger_events[trigger_element][trigger_event]))
11041 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11043 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11045 int element = EL_CUSTOM_START + i;
11046 boolean change_done = FALSE;
11049 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11050 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11053 for (p = 0; p < element_info[element].num_change_pages; p++)
11055 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11057 if (change->can_change_or_has_action &&
11058 change->has_event[trigger_event] &&
11059 change->trigger_side & trigger_side &&
11060 change->trigger_player & trigger_player &&
11061 change->trigger_page & trigger_page_bits &&
11062 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11064 change->actual_trigger_element = trigger_element;
11065 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11066 change->actual_trigger_player_bits = trigger_player;
11067 change->actual_trigger_side = trigger_side;
11068 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11069 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11071 if ((change->can_change && !change_done) || change->has_action)
11075 SCAN_PLAYFIELD(x, y)
11077 if (Tile[x][y] == element)
11079 if (change->can_change && !change_done)
11081 // if element already changed in this frame, not only prevent
11082 // another element change (checked in ChangeElement()), but
11083 // also prevent additional element actions for this element
11085 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11086 !level.use_action_after_change_bug)
11089 ChangeDelay[x][y] = 1;
11090 ChangeEvent[x][y] = trigger_event;
11092 HandleElementChange(x, y, p);
11094 else if (change->has_action)
11096 // if element already changed in this frame, not only prevent
11097 // another element change (checked in ChangeElement()), but
11098 // also prevent additional element actions for this element
11100 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11101 !level.use_action_after_change_bug)
11104 ExecuteCustomElementAction(x, y, element, p);
11105 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11110 if (change->can_change)
11112 change_done = TRUE;
11113 change_done_any = TRUE;
11120 RECURSION_LOOP_DETECTION_END();
11122 return change_done_any;
11125 static boolean CheckElementChangeExt(int x, int y,
11127 int trigger_element,
11129 int trigger_player,
11132 boolean change_done = FALSE;
11135 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11136 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11139 if (Tile[x][y] == EL_BLOCKED)
11141 Blocked2Moving(x, y, &x, &y);
11142 element = Tile[x][y];
11145 // check if element has already changed or is about to change after moving
11146 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11147 Tile[x][y] != element) ||
11149 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11150 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11151 ChangePage[x][y] != -1)))
11154 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11156 for (p = 0; p < element_info[element].num_change_pages; p++)
11158 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11160 /* check trigger element for all events where the element that is checked
11161 for changing interacts with a directly adjacent element -- this is
11162 different to element changes that affect other elements to change on the
11163 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11164 boolean check_trigger_element =
11165 (trigger_event == CE_NEXT_TO_X ||
11166 trigger_event == CE_TOUCHING_X ||
11167 trigger_event == CE_HITTING_X ||
11168 trigger_event == CE_HIT_BY_X ||
11169 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11171 if (change->can_change_or_has_action &&
11172 change->has_event[trigger_event] &&
11173 change->trigger_side & trigger_side &&
11174 change->trigger_player & trigger_player &&
11175 (!check_trigger_element ||
11176 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11178 change->actual_trigger_element = trigger_element;
11179 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11180 change->actual_trigger_player_bits = trigger_player;
11181 change->actual_trigger_side = trigger_side;
11182 change->actual_trigger_ce_value = CustomValue[x][y];
11183 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11185 // special case: trigger element not at (x,y) position for some events
11186 if (check_trigger_element)
11198 { 0, 0 }, { 0, 0 }, { 0, 0 },
11202 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11203 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11205 change->actual_trigger_ce_value = CustomValue[xx][yy];
11206 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11209 if (change->can_change && !change_done)
11211 ChangeDelay[x][y] = 1;
11212 ChangeEvent[x][y] = trigger_event;
11214 HandleElementChange(x, y, p);
11216 change_done = TRUE;
11218 else if (change->has_action)
11220 ExecuteCustomElementAction(x, y, element, p);
11221 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11226 RECURSION_LOOP_DETECTION_END();
11228 return change_done;
11231 static void PlayPlayerSound(struct PlayerInfo *player)
11233 int jx = player->jx, jy = player->jy;
11234 int sound_element = player->artwork_element;
11235 int last_action = player->last_action_waiting;
11236 int action = player->action_waiting;
11238 if (player->is_waiting)
11240 if (action != last_action)
11241 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11243 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11247 if (action != last_action)
11248 StopSound(element_info[sound_element].sound[last_action]);
11250 if (last_action == ACTION_SLEEPING)
11251 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11255 static void PlayAllPlayersSound(void)
11259 for (i = 0; i < MAX_PLAYERS; i++)
11260 if (stored_player[i].active)
11261 PlayPlayerSound(&stored_player[i]);
11264 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11266 boolean last_waiting = player->is_waiting;
11267 int move_dir = player->MovDir;
11269 player->dir_waiting = move_dir;
11270 player->last_action_waiting = player->action_waiting;
11274 if (!last_waiting) // not waiting -> waiting
11276 player->is_waiting = TRUE;
11278 player->frame_counter_bored =
11280 game.player_boring_delay_fixed +
11281 GetSimpleRandom(game.player_boring_delay_random);
11282 player->frame_counter_sleeping =
11284 game.player_sleeping_delay_fixed +
11285 GetSimpleRandom(game.player_sleeping_delay_random);
11287 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11290 if (game.player_sleeping_delay_fixed +
11291 game.player_sleeping_delay_random > 0 &&
11292 player->anim_delay_counter == 0 &&
11293 player->post_delay_counter == 0 &&
11294 FrameCounter >= player->frame_counter_sleeping)
11295 player->is_sleeping = TRUE;
11296 else if (game.player_boring_delay_fixed +
11297 game.player_boring_delay_random > 0 &&
11298 FrameCounter >= player->frame_counter_bored)
11299 player->is_bored = TRUE;
11301 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11302 player->is_bored ? ACTION_BORING :
11305 if (player->is_sleeping && player->use_murphy)
11307 // special case for sleeping Murphy when leaning against non-free tile
11309 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11310 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11311 !IS_MOVING(player->jx - 1, player->jy)))
11312 move_dir = MV_LEFT;
11313 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11314 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11315 !IS_MOVING(player->jx + 1, player->jy)))
11316 move_dir = MV_RIGHT;
11318 player->is_sleeping = FALSE;
11320 player->dir_waiting = move_dir;
11323 if (player->is_sleeping)
11325 if (player->num_special_action_sleeping > 0)
11327 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11329 int last_special_action = player->special_action_sleeping;
11330 int num_special_action = player->num_special_action_sleeping;
11331 int special_action =
11332 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11333 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11334 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11335 last_special_action + 1 : ACTION_SLEEPING);
11336 int special_graphic =
11337 el_act_dir2img(player->artwork_element, special_action, move_dir);
11339 player->anim_delay_counter =
11340 graphic_info[special_graphic].anim_delay_fixed +
11341 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11342 player->post_delay_counter =
11343 graphic_info[special_graphic].post_delay_fixed +
11344 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11346 player->special_action_sleeping = special_action;
11349 if (player->anim_delay_counter > 0)
11351 player->action_waiting = player->special_action_sleeping;
11352 player->anim_delay_counter--;
11354 else if (player->post_delay_counter > 0)
11356 player->post_delay_counter--;
11360 else if (player->is_bored)
11362 if (player->num_special_action_bored > 0)
11364 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11366 int special_action =
11367 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11368 int special_graphic =
11369 el_act_dir2img(player->artwork_element, special_action, move_dir);
11371 player->anim_delay_counter =
11372 graphic_info[special_graphic].anim_delay_fixed +
11373 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11374 player->post_delay_counter =
11375 graphic_info[special_graphic].post_delay_fixed +
11376 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11378 player->special_action_bored = special_action;
11381 if (player->anim_delay_counter > 0)
11383 player->action_waiting = player->special_action_bored;
11384 player->anim_delay_counter--;
11386 else if (player->post_delay_counter > 0)
11388 player->post_delay_counter--;
11393 else if (last_waiting) // waiting -> not waiting
11395 player->is_waiting = FALSE;
11396 player->is_bored = FALSE;
11397 player->is_sleeping = FALSE;
11399 player->frame_counter_bored = -1;
11400 player->frame_counter_sleeping = -1;
11402 player->anim_delay_counter = 0;
11403 player->post_delay_counter = 0;
11405 player->dir_waiting = player->MovDir;
11406 player->action_waiting = ACTION_DEFAULT;
11408 player->special_action_bored = ACTION_DEFAULT;
11409 player->special_action_sleeping = ACTION_DEFAULT;
11413 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11415 if ((!player->is_moving && player->was_moving) ||
11416 (player->MovPos == 0 && player->was_moving) ||
11417 (player->is_snapping && !player->was_snapping) ||
11418 (player->is_dropping && !player->was_dropping))
11420 if (!CheckSaveEngineSnapshotToList())
11423 player->was_moving = FALSE;
11424 player->was_snapping = TRUE;
11425 player->was_dropping = TRUE;
11429 if (player->is_moving)
11430 player->was_moving = TRUE;
11432 if (!player->is_snapping)
11433 player->was_snapping = FALSE;
11435 if (!player->is_dropping)
11436 player->was_dropping = FALSE;
11439 static struct MouseActionInfo mouse_action_last = { 0 };
11440 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11441 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11444 CheckSaveEngineSnapshotToList();
11446 mouse_action_last = mouse_action;
11449 static void CheckSingleStepMode(struct PlayerInfo *player)
11451 if (tape.single_step && tape.recording && !tape.pausing)
11453 // as it is called "single step mode", just return to pause mode when the
11454 // player stopped moving after one tile (or never starts moving at all)
11455 // (reverse logic needed here in case single step mode used in team mode)
11456 if (player->is_moving ||
11457 player->is_pushing ||
11458 player->is_dropping_pressed ||
11459 player->effective_mouse_action.button)
11460 game.enter_single_step_mode = FALSE;
11463 CheckSaveEngineSnapshot(player);
11466 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11468 int left = player_action & JOY_LEFT;
11469 int right = player_action & JOY_RIGHT;
11470 int up = player_action & JOY_UP;
11471 int down = player_action & JOY_DOWN;
11472 int button1 = player_action & JOY_BUTTON_1;
11473 int button2 = player_action & JOY_BUTTON_2;
11474 int dx = (left ? -1 : right ? 1 : 0);
11475 int dy = (up ? -1 : down ? 1 : 0);
11477 if (!player->active || tape.pausing)
11483 SnapField(player, dx, dy);
11487 DropElement(player);
11489 MovePlayer(player, dx, dy);
11492 CheckSingleStepMode(player);
11494 SetPlayerWaiting(player, FALSE);
11496 return player_action;
11500 // no actions for this player (no input at player's configured device)
11502 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11503 SnapField(player, 0, 0);
11504 CheckGravityMovementWhenNotMoving(player);
11506 if (player->MovPos == 0)
11507 SetPlayerWaiting(player, TRUE);
11509 if (player->MovPos == 0) // needed for tape.playing
11510 player->is_moving = FALSE;
11512 player->is_dropping = FALSE;
11513 player->is_dropping_pressed = FALSE;
11514 player->drop_pressed_delay = 0;
11516 CheckSingleStepMode(player);
11522 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11525 if (!tape.use_mouse_actions)
11528 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11529 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11530 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11533 static void SetTapeActionFromMouseAction(byte *tape_action,
11534 struct MouseActionInfo *mouse_action)
11536 if (!tape.use_mouse_actions)
11539 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11540 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11541 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11544 static void CheckLevelSolved(void)
11546 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11548 if (game_em.level_solved &&
11549 !game_em.game_over) // game won
11553 game_em.game_over = TRUE;
11555 game.all_players_gone = TRUE;
11558 if (game_em.game_over) // game lost
11559 game.all_players_gone = TRUE;
11561 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11563 if (game_sp.level_solved &&
11564 !game_sp.game_over) // game won
11568 game_sp.game_over = TRUE;
11570 game.all_players_gone = TRUE;
11573 if (game_sp.game_over) // game lost
11574 game.all_players_gone = TRUE;
11576 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11578 if (game_mm.level_solved &&
11579 !game_mm.game_over) // game won
11583 game_mm.game_over = TRUE;
11585 game.all_players_gone = TRUE;
11588 if (game_mm.game_over) // game lost
11589 game.all_players_gone = TRUE;
11593 static void CheckLevelTime(void)
11597 if (TimeFrames >= FRAMES_PER_SECOND)
11602 for (i = 0; i < MAX_PLAYERS; i++)
11604 struct PlayerInfo *player = &stored_player[i];
11606 if (SHIELD_ON(player))
11608 player->shield_normal_time_left--;
11610 if (player->shield_deadly_time_left > 0)
11611 player->shield_deadly_time_left--;
11615 if (!game.LevelSolved && !level.use_step_counter)
11623 if (TimeLeft <= 10 && setup.time_limit)
11624 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11626 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11627 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11629 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11631 if (!TimeLeft && setup.time_limit)
11633 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11634 game_em.lev->killed_out_of_time = TRUE;
11636 for (i = 0; i < MAX_PLAYERS; i++)
11637 KillPlayer(&stored_player[i]);
11640 else if (game.no_time_limit && !game.all_players_gone)
11642 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11645 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11648 if (tape.recording || tape.playing)
11649 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11652 if (tape.recording || tape.playing)
11653 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11655 UpdateAndDisplayGameControlValues();
11658 void AdvanceFrameAndPlayerCounters(int player_nr)
11662 // advance frame counters (global frame counter and time frame counter)
11666 // advance player counters (counters for move delay, move animation etc.)
11667 for (i = 0; i < MAX_PLAYERS; i++)
11669 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11670 int move_delay_value = stored_player[i].move_delay_value;
11671 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11673 if (!advance_player_counters) // not all players may be affected
11676 if (move_frames == 0) // less than one move per game frame
11678 int stepsize = TILEX / move_delay_value;
11679 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11680 int count = (stored_player[i].is_moving ?
11681 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11683 if (count % delay == 0)
11687 stored_player[i].Frame += move_frames;
11689 if (stored_player[i].MovPos != 0)
11690 stored_player[i].StepFrame += move_frames;
11692 if (stored_player[i].move_delay > 0)
11693 stored_player[i].move_delay--;
11695 // due to bugs in previous versions, counter must count up, not down
11696 if (stored_player[i].push_delay != -1)
11697 stored_player[i].push_delay++;
11699 if (stored_player[i].drop_delay > 0)
11700 stored_player[i].drop_delay--;
11702 if (stored_player[i].is_dropping_pressed)
11703 stored_player[i].drop_pressed_delay++;
11707 void StartGameActions(boolean init_network_game, boolean record_tape,
11710 unsigned int new_random_seed = InitRND(random_seed);
11713 TapeStartRecording(new_random_seed);
11715 if (init_network_game)
11717 SendToServer_LevelFile();
11718 SendToServer_StartPlaying();
11726 static void GameActionsExt(void)
11729 static unsigned int game_frame_delay = 0;
11731 unsigned int game_frame_delay_value;
11732 byte *recorded_player_action;
11733 byte summarized_player_action = 0;
11734 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11737 // detect endless loops, caused by custom element programming
11738 if (recursion_loop_detected && recursion_loop_depth == 0)
11740 char *message = getStringCat3("Internal Error! Element ",
11741 EL_NAME(recursion_loop_element),
11742 " caused endless loop! Quit the game?");
11744 Warn("element '%s' caused endless loop in game engine",
11745 EL_NAME(recursion_loop_element));
11747 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11749 recursion_loop_detected = FALSE; // if game should be continued
11756 if (game.restart_level)
11757 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11759 CheckLevelSolved();
11761 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11764 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11767 if (game_status != GAME_MODE_PLAYING) // status might have changed
11770 game_frame_delay_value =
11771 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11773 if (tape.playing && tape.warp_forward && !tape.pausing)
11774 game_frame_delay_value = 0;
11776 SetVideoFrameDelay(game_frame_delay_value);
11778 // (de)activate virtual buttons depending on current game status
11779 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11781 if (game.all_players_gone) // if no players there to be controlled anymore
11782 SetOverlayActive(FALSE);
11783 else if (!tape.playing) // if game continues after tape stopped playing
11784 SetOverlayActive(TRUE);
11789 // ---------- main game synchronization point ----------
11791 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11793 Debug("game:playing:skip", "skip == %d", skip);
11796 // ---------- main game synchronization point ----------
11798 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11802 if (network_playing && !network_player_action_received)
11804 // try to get network player actions in time
11806 // last chance to get network player actions without main loop delay
11807 HandleNetworking();
11809 // game was quit by network peer
11810 if (game_status != GAME_MODE_PLAYING)
11813 // check if network player actions still missing and game still running
11814 if (!network_player_action_received && !checkGameEnded())
11815 return; // failed to get network player actions in time
11817 // do not yet reset "network_player_action_received" (for tape.pausing)
11823 // at this point we know that we really continue executing the game
11825 network_player_action_received = FALSE;
11827 // when playing tape, read previously recorded player input from tape data
11828 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11830 local_player->effective_mouse_action = local_player->mouse_action;
11832 if (recorded_player_action != NULL)
11833 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11834 recorded_player_action);
11836 // TapePlayAction() may return NULL when toggling to "pause before death"
11840 if (tape.set_centered_player)
11842 game.centered_player_nr_next = tape.centered_player_nr_next;
11843 game.set_centered_player = TRUE;
11846 for (i = 0; i < MAX_PLAYERS; i++)
11848 summarized_player_action |= stored_player[i].action;
11850 if (!network_playing && (game.team_mode || tape.playing))
11851 stored_player[i].effective_action = stored_player[i].action;
11854 if (network_playing && !checkGameEnded())
11855 SendToServer_MovePlayer(summarized_player_action);
11857 // summarize all actions at local players mapped input device position
11858 // (this allows using different input devices in single player mode)
11859 if (!network.enabled && !game.team_mode)
11860 stored_player[map_player_action[local_player->index_nr]].effective_action =
11861 summarized_player_action;
11863 // summarize all actions at centered player in local team mode
11864 if (tape.recording &&
11865 setup.team_mode && !network.enabled &&
11866 setup.input_on_focus &&
11867 game.centered_player_nr != -1)
11869 for (i = 0; i < MAX_PLAYERS; i++)
11870 stored_player[map_player_action[i]].effective_action =
11871 (i == game.centered_player_nr ? summarized_player_action : 0);
11874 if (recorded_player_action != NULL)
11875 for (i = 0; i < MAX_PLAYERS; i++)
11876 stored_player[i].effective_action = recorded_player_action[i];
11878 for (i = 0; i < MAX_PLAYERS; i++)
11880 tape_action[i] = stored_player[i].effective_action;
11882 /* (this may happen in the RND game engine if a player was not present on
11883 the playfield on level start, but appeared later from a custom element */
11884 if (setup.team_mode &&
11887 !tape.player_participates[i])
11888 tape.player_participates[i] = TRUE;
11891 SetTapeActionFromMouseAction(tape_action,
11892 &local_player->effective_mouse_action);
11894 // only record actions from input devices, but not programmed actions
11895 if (tape.recording)
11896 TapeRecordAction(tape_action);
11898 // remember if game was played (especially after tape stopped playing)
11899 if (!tape.playing && summarized_player_action)
11900 game.GamePlayed = TRUE;
11902 #if USE_NEW_PLAYER_ASSIGNMENTS
11903 // !!! also map player actions in single player mode !!!
11904 // if (game.team_mode)
11907 byte mapped_action[MAX_PLAYERS];
11909 #if DEBUG_PLAYER_ACTIONS
11910 for (i = 0; i < MAX_PLAYERS; i++)
11911 DebugContinued("", "%d, ", stored_player[i].effective_action);
11914 for (i = 0; i < MAX_PLAYERS; i++)
11915 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11917 for (i = 0; i < MAX_PLAYERS; i++)
11918 stored_player[i].effective_action = mapped_action[i];
11920 #if DEBUG_PLAYER_ACTIONS
11921 DebugContinued("", "=> ");
11922 for (i = 0; i < MAX_PLAYERS; i++)
11923 DebugContinued("", "%d, ", stored_player[i].effective_action);
11924 DebugContinued("game:playing:player", "\n");
11927 #if DEBUG_PLAYER_ACTIONS
11930 for (i = 0; i < MAX_PLAYERS; i++)
11931 DebugContinued("", "%d, ", stored_player[i].effective_action);
11932 DebugContinued("game:playing:player", "\n");
11937 for (i = 0; i < MAX_PLAYERS; i++)
11939 // allow engine snapshot in case of changed movement attempt
11940 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11941 (stored_player[i].effective_action & KEY_MOTION))
11942 game.snapshot.changed_action = TRUE;
11944 // allow engine snapshot in case of snapping/dropping attempt
11945 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11946 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11947 game.snapshot.changed_action = TRUE;
11949 game.snapshot.last_action[i] = stored_player[i].effective_action;
11952 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11954 GameActions_EM_Main();
11956 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11958 GameActions_SP_Main();
11960 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11962 GameActions_MM_Main();
11966 GameActions_RND_Main();
11969 BlitScreenToBitmap(backbuffer);
11971 CheckLevelSolved();
11974 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11976 if (global.show_frames_per_second)
11978 static unsigned int fps_counter = 0;
11979 static int fps_frames = 0;
11980 unsigned int fps_delay_ms = Counter() - fps_counter;
11984 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11986 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11989 fps_counter = Counter();
11991 // always draw FPS to screen after FPS value was updated
11992 redraw_mask |= REDRAW_FPS;
11995 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11996 if (GetDrawDeactivationMask() == REDRAW_NONE)
11997 redraw_mask |= REDRAW_FPS;
12001 static void GameActions_CheckSaveEngineSnapshot(void)
12003 if (!game.snapshot.save_snapshot)
12006 // clear flag for saving snapshot _before_ saving snapshot
12007 game.snapshot.save_snapshot = FALSE;
12009 SaveEngineSnapshotToList();
12012 void GameActions(void)
12016 GameActions_CheckSaveEngineSnapshot();
12019 void GameActions_EM_Main(void)
12021 byte effective_action[MAX_PLAYERS];
12022 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12025 for (i = 0; i < MAX_PLAYERS; i++)
12026 effective_action[i] = stored_player[i].effective_action;
12028 GameActions_EM(effective_action, warp_mode);
12031 void GameActions_SP_Main(void)
12033 byte effective_action[MAX_PLAYERS];
12034 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12037 for (i = 0; i < MAX_PLAYERS; i++)
12038 effective_action[i] = stored_player[i].effective_action;
12040 GameActions_SP(effective_action, warp_mode);
12042 for (i = 0; i < MAX_PLAYERS; i++)
12044 if (stored_player[i].force_dropping)
12045 stored_player[i].action |= KEY_BUTTON_DROP;
12047 stored_player[i].force_dropping = FALSE;
12051 void GameActions_MM_Main(void)
12053 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12055 GameActions_MM(local_player->effective_mouse_action, warp_mode);
12058 void GameActions_RND_Main(void)
12063 void GameActions_RND(void)
12065 static struct MouseActionInfo mouse_action_last = { 0 };
12066 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12067 int magic_wall_x = 0, magic_wall_y = 0;
12068 int i, x, y, element, graphic, last_gfx_frame;
12070 InitPlayfieldScanModeVars();
12072 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12074 SCAN_PLAYFIELD(x, y)
12076 ChangeCount[x][y] = 0;
12077 ChangeEvent[x][y] = -1;
12081 if (game.set_centered_player)
12083 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12085 // switching to "all players" only possible if all players fit to screen
12086 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12088 game.centered_player_nr_next = game.centered_player_nr;
12089 game.set_centered_player = FALSE;
12092 // do not switch focus to non-existing (or non-active) player
12093 if (game.centered_player_nr_next >= 0 &&
12094 !stored_player[game.centered_player_nr_next].active)
12096 game.centered_player_nr_next = game.centered_player_nr;
12097 game.set_centered_player = FALSE;
12101 if (game.set_centered_player &&
12102 ScreenMovPos == 0) // screen currently aligned at tile position
12106 if (game.centered_player_nr_next == -1)
12108 setScreenCenteredToAllPlayers(&sx, &sy);
12112 sx = stored_player[game.centered_player_nr_next].jx;
12113 sy = stored_player[game.centered_player_nr_next].jy;
12116 game.centered_player_nr = game.centered_player_nr_next;
12117 game.set_centered_player = FALSE;
12119 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
12120 DrawGameDoorValues();
12123 // check single step mode (set flag and clear again if any player is active)
12124 game.enter_single_step_mode =
12125 (tape.single_step && tape.recording && !tape.pausing);
12127 for (i = 0; i < MAX_PLAYERS; i++)
12129 int actual_player_action = stored_player[i].effective_action;
12132 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12133 - rnd_equinox_tetrachloride 048
12134 - rnd_equinox_tetrachloride_ii 096
12135 - rnd_emanuel_schmieg 002
12136 - doctor_sloan_ww 001, 020
12138 if (stored_player[i].MovPos == 0)
12139 CheckGravityMovement(&stored_player[i]);
12142 // overwrite programmed action with tape action
12143 if (stored_player[i].programmed_action)
12144 actual_player_action = stored_player[i].programmed_action;
12146 PlayerActions(&stored_player[i], actual_player_action);
12148 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12151 // single step pause mode may already have been toggled by "ScrollPlayer()"
12152 if (game.enter_single_step_mode && !tape.pausing)
12153 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12155 ScrollScreen(NULL, SCROLL_GO_ON);
12157 /* for backwards compatibility, the following code emulates a fixed bug that
12158 occured when pushing elements (causing elements that just made their last
12159 pushing step to already (if possible) make their first falling step in the
12160 same game frame, which is bad); this code is also needed to use the famous
12161 "spring push bug" which is used in older levels and might be wanted to be
12162 used also in newer levels, but in this case the buggy pushing code is only
12163 affecting the "spring" element and no other elements */
12165 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12167 for (i = 0; i < MAX_PLAYERS; i++)
12169 struct PlayerInfo *player = &stored_player[i];
12170 int x = player->jx;
12171 int y = player->jy;
12173 if (player->active && player->is_pushing && player->is_moving &&
12175 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12176 Tile[x][y] == EL_SPRING))
12178 ContinueMoving(x, y);
12180 // continue moving after pushing (this is actually a bug)
12181 if (!IS_MOVING(x, y))
12182 Stop[x][y] = FALSE;
12187 SCAN_PLAYFIELD(x, y)
12189 Last[x][y] = Tile[x][y];
12191 ChangeCount[x][y] = 0;
12192 ChangeEvent[x][y] = -1;
12194 // this must be handled before main playfield loop
12195 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12198 if (MovDelay[x][y] <= 0)
12202 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12205 if (MovDelay[x][y] <= 0)
12207 int element = Store[x][y];
12208 int move_direction = MovDir[x][y];
12209 int player_index_bit = Store2[x][y];
12215 TEST_DrawLevelField(x, y);
12217 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12219 if (IS_ENVELOPE(element))
12220 local_player->show_envelope = element;
12225 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12227 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12229 Debug("game:playing:GameActions_RND", "This should never happen!");
12231 ChangePage[x][y] = -1;
12235 Stop[x][y] = FALSE;
12236 if (WasJustMoving[x][y] > 0)
12237 WasJustMoving[x][y]--;
12238 if (WasJustFalling[x][y] > 0)
12239 WasJustFalling[x][y]--;
12240 if (CheckCollision[x][y] > 0)
12241 CheckCollision[x][y]--;
12242 if (CheckImpact[x][y] > 0)
12243 CheckImpact[x][y]--;
12247 /* reset finished pushing action (not done in ContinueMoving() to allow
12248 continuous pushing animation for elements with zero push delay) */
12249 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12251 ResetGfxAnimation(x, y);
12252 TEST_DrawLevelField(x, y);
12256 if (IS_BLOCKED(x, y))
12260 Blocked2Moving(x, y, &oldx, &oldy);
12261 if (!IS_MOVING(oldx, oldy))
12263 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12264 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12265 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12266 Debug("game:playing:GameActions_RND", "This should never happen!");
12272 if (mouse_action.button)
12274 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12275 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action.button);
12277 x = mouse_action.lx;
12278 y = mouse_action.ly;
12279 element = Tile[x][y];
12283 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12284 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12288 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12289 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12293 SCAN_PLAYFIELD(x, y)
12295 element = Tile[x][y];
12296 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12297 last_gfx_frame = GfxFrame[x][y];
12299 ResetGfxFrame(x, y);
12301 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12302 DrawLevelGraphicAnimation(x, y, graphic);
12304 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12305 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12306 ResetRandomAnimationValue(x, y);
12308 SetRandomAnimationValue(x, y);
12310 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12312 if (IS_INACTIVE(element))
12314 if (IS_ANIMATED(graphic))
12315 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12320 // this may take place after moving, so 'element' may have changed
12321 if (IS_CHANGING(x, y) &&
12322 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12324 int page = element_info[element].event_page_nr[CE_DELAY];
12326 HandleElementChange(x, y, page);
12328 element = Tile[x][y];
12329 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12332 CheckNextToConditions(x, y);
12334 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12338 element = Tile[x][y];
12339 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12341 if (IS_ANIMATED(graphic) &&
12342 !IS_MOVING(x, y) &&
12344 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12346 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12347 TEST_DrawTwinkleOnField(x, y);
12349 else if (element == EL_ACID)
12352 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12354 else if ((element == EL_EXIT_OPEN ||
12355 element == EL_EM_EXIT_OPEN ||
12356 element == EL_SP_EXIT_OPEN ||
12357 element == EL_STEEL_EXIT_OPEN ||
12358 element == EL_EM_STEEL_EXIT_OPEN ||
12359 element == EL_SP_TERMINAL ||
12360 element == EL_SP_TERMINAL_ACTIVE ||
12361 element == EL_EXTRA_TIME ||
12362 element == EL_SHIELD_NORMAL ||
12363 element == EL_SHIELD_DEADLY) &&
12364 IS_ANIMATED(graphic))
12365 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12366 else if (IS_MOVING(x, y))
12367 ContinueMoving(x, y);
12368 else if (IS_ACTIVE_BOMB(element))
12369 CheckDynamite(x, y);
12370 else if (element == EL_AMOEBA_GROWING)
12371 AmoebaGrowing(x, y);
12372 else if (element == EL_AMOEBA_SHRINKING)
12373 AmoebaShrinking(x, y);
12375 #if !USE_NEW_AMOEBA_CODE
12376 else if (IS_AMOEBALIVE(element))
12377 AmoebaReproduce(x, y);
12380 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12382 else if (element == EL_EXIT_CLOSED)
12384 else if (element == EL_EM_EXIT_CLOSED)
12386 else if (element == EL_STEEL_EXIT_CLOSED)
12387 CheckExitSteel(x, y);
12388 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12389 CheckExitSteelEM(x, y);
12390 else if (element == EL_SP_EXIT_CLOSED)
12392 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12393 element == EL_EXPANDABLE_STEELWALL_GROWING)
12394 MauerWaechst(x, y);
12395 else if (element == EL_EXPANDABLE_WALL ||
12396 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12397 element == EL_EXPANDABLE_WALL_VERTICAL ||
12398 element == EL_EXPANDABLE_WALL_ANY ||
12399 element == EL_BD_EXPANDABLE_WALL)
12400 MauerAbleger(x, y);
12401 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12402 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12403 element == EL_EXPANDABLE_STEELWALL_ANY)
12404 MauerAblegerStahl(x, y);
12405 else if (element == EL_FLAMES)
12406 CheckForDragon(x, y);
12407 else if (element == EL_EXPLOSION)
12408 ; // drawing of correct explosion animation is handled separately
12409 else if (element == EL_ELEMENT_SNAPPING ||
12410 element == EL_DIAGONAL_SHRINKING ||
12411 element == EL_DIAGONAL_GROWING)
12413 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12415 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12417 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12418 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12420 if (IS_BELT_ACTIVE(element))
12421 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12423 if (game.magic_wall_active)
12425 int jx = local_player->jx, jy = local_player->jy;
12427 // play the element sound at the position nearest to the player
12428 if ((element == EL_MAGIC_WALL_FULL ||
12429 element == EL_MAGIC_WALL_ACTIVE ||
12430 element == EL_MAGIC_WALL_EMPTYING ||
12431 element == EL_BD_MAGIC_WALL_FULL ||
12432 element == EL_BD_MAGIC_WALL_ACTIVE ||
12433 element == EL_BD_MAGIC_WALL_EMPTYING ||
12434 element == EL_DC_MAGIC_WALL_FULL ||
12435 element == EL_DC_MAGIC_WALL_ACTIVE ||
12436 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12437 ABS(x - jx) + ABS(y - jy) <
12438 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12446 #if USE_NEW_AMOEBA_CODE
12447 // new experimental amoeba growth stuff
12448 if (!(FrameCounter % 8))
12450 static unsigned int random = 1684108901;
12452 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12454 x = RND(lev_fieldx);
12455 y = RND(lev_fieldy);
12456 element = Tile[x][y];
12458 if (!IS_PLAYER(x,y) &&
12459 (element == EL_EMPTY ||
12460 CAN_GROW_INTO(element) ||
12461 element == EL_QUICKSAND_EMPTY ||
12462 element == EL_QUICKSAND_FAST_EMPTY ||
12463 element == EL_ACID_SPLASH_LEFT ||
12464 element == EL_ACID_SPLASH_RIGHT))
12466 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12467 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12468 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12469 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12470 Tile[x][y] = EL_AMOEBA_DROP;
12473 random = random * 129 + 1;
12478 game.explosions_delayed = FALSE;
12480 SCAN_PLAYFIELD(x, y)
12482 element = Tile[x][y];
12484 if (ExplodeField[x][y])
12485 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12486 else if (element == EL_EXPLOSION)
12487 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12489 ExplodeField[x][y] = EX_TYPE_NONE;
12492 game.explosions_delayed = TRUE;
12494 if (game.magic_wall_active)
12496 if (!(game.magic_wall_time_left % 4))
12498 int element = Tile[magic_wall_x][magic_wall_y];
12500 if (element == EL_BD_MAGIC_WALL_FULL ||
12501 element == EL_BD_MAGIC_WALL_ACTIVE ||
12502 element == EL_BD_MAGIC_WALL_EMPTYING)
12503 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12504 else if (element == EL_DC_MAGIC_WALL_FULL ||
12505 element == EL_DC_MAGIC_WALL_ACTIVE ||
12506 element == EL_DC_MAGIC_WALL_EMPTYING)
12507 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12509 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12512 if (game.magic_wall_time_left > 0)
12514 game.magic_wall_time_left--;
12516 if (!game.magic_wall_time_left)
12518 SCAN_PLAYFIELD(x, y)
12520 element = Tile[x][y];
12522 if (element == EL_MAGIC_WALL_ACTIVE ||
12523 element == EL_MAGIC_WALL_FULL)
12525 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12526 TEST_DrawLevelField(x, y);
12528 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12529 element == EL_BD_MAGIC_WALL_FULL)
12531 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12532 TEST_DrawLevelField(x, y);
12534 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12535 element == EL_DC_MAGIC_WALL_FULL)
12537 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12538 TEST_DrawLevelField(x, y);
12542 game.magic_wall_active = FALSE;
12547 if (game.light_time_left > 0)
12549 game.light_time_left--;
12551 if (game.light_time_left == 0)
12552 RedrawAllLightSwitchesAndInvisibleElements();
12555 if (game.timegate_time_left > 0)
12557 game.timegate_time_left--;
12559 if (game.timegate_time_left == 0)
12560 CloseAllOpenTimegates();
12563 if (game.lenses_time_left > 0)
12565 game.lenses_time_left--;
12567 if (game.lenses_time_left == 0)
12568 RedrawAllInvisibleElementsForLenses();
12571 if (game.magnify_time_left > 0)
12573 game.magnify_time_left--;
12575 if (game.magnify_time_left == 0)
12576 RedrawAllInvisibleElementsForMagnifier();
12579 for (i = 0; i < MAX_PLAYERS; i++)
12581 struct PlayerInfo *player = &stored_player[i];
12583 if (SHIELD_ON(player))
12585 if (player->shield_deadly_time_left)
12586 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12587 else if (player->shield_normal_time_left)
12588 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12592 #if USE_DELAYED_GFX_REDRAW
12593 SCAN_PLAYFIELD(x, y)
12595 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12597 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12598 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12600 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12601 DrawLevelField(x, y);
12603 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12604 DrawLevelFieldCrumbled(x, y);
12606 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12607 DrawLevelFieldCrumbledNeighbours(x, y);
12609 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12610 DrawTwinkleOnField(x, y);
12613 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12618 PlayAllPlayersSound();
12620 for (i = 0; i < MAX_PLAYERS; i++)
12622 struct PlayerInfo *player = &stored_player[i];
12624 if (player->show_envelope != 0 && (!player->active ||
12625 player->MovPos == 0))
12627 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12629 player->show_envelope = 0;
12633 // use random number generator in every frame to make it less predictable
12634 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12637 mouse_action_last = mouse_action;
12640 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12642 int min_x = x, min_y = y, max_x = x, max_y = y;
12643 int scr_fieldx = getScreenFieldSizeX();
12644 int scr_fieldy = getScreenFieldSizeY();
12647 for (i = 0; i < MAX_PLAYERS; i++)
12649 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12651 if (!stored_player[i].active || &stored_player[i] == player)
12654 min_x = MIN(min_x, jx);
12655 min_y = MIN(min_y, jy);
12656 max_x = MAX(max_x, jx);
12657 max_y = MAX(max_y, jy);
12660 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12663 static boolean AllPlayersInVisibleScreen(void)
12667 for (i = 0; i < MAX_PLAYERS; i++)
12669 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12671 if (!stored_player[i].active)
12674 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12681 void ScrollLevel(int dx, int dy)
12683 int scroll_offset = 2 * TILEX_VAR;
12686 BlitBitmap(drawto_field, drawto_field,
12687 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12688 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12689 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12690 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12691 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12692 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12696 x = (dx == 1 ? BX1 : BX2);
12697 for (y = BY1; y <= BY2; y++)
12698 DrawScreenField(x, y);
12703 y = (dy == 1 ? BY1 : BY2);
12704 for (x = BX1; x <= BX2; x++)
12705 DrawScreenField(x, y);
12708 redraw_mask |= REDRAW_FIELD;
12711 static boolean canFallDown(struct PlayerInfo *player)
12713 int jx = player->jx, jy = player->jy;
12715 return (IN_LEV_FIELD(jx, jy + 1) &&
12716 (IS_FREE(jx, jy + 1) ||
12717 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12718 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12719 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12722 static boolean canPassField(int x, int y, int move_dir)
12724 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12725 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12726 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12727 int nextx = x + dx;
12728 int nexty = y + dy;
12729 int element = Tile[x][y];
12731 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12732 !CAN_MOVE(element) &&
12733 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12734 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12735 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12738 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12740 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12741 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12742 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12746 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12747 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12748 (IS_DIGGABLE(Tile[newx][newy]) ||
12749 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12750 canPassField(newx, newy, move_dir)));
12753 static void CheckGravityMovement(struct PlayerInfo *player)
12755 if (player->gravity && !player->programmed_action)
12757 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12758 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12759 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12760 int jx = player->jx, jy = player->jy;
12761 boolean player_is_moving_to_valid_field =
12762 (!player_is_snapping &&
12763 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12764 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12765 boolean player_can_fall_down = canFallDown(player);
12767 if (player_can_fall_down &&
12768 !player_is_moving_to_valid_field)
12769 player->programmed_action = MV_DOWN;
12773 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12775 return CheckGravityMovement(player);
12777 if (player->gravity && !player->programmed_action)
12779 int jx = player->jx, jy = player->jy;
12780 boolean field_under_player_is_free =
12781 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12782 boolean player_is_standing_on_valid_field =
12783 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12784 (IS_WALKABLE(Tile[jx][jy]) &&
12785 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12787 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12788 player->programmed_action = MV_DOWN;
12793 MovePlayerOneStep()
12794 -----------------------------------------------------------------------------
12795 dx, dy: direction (non-diagonal) to try to move the player to
12796 real_dx, real_dy: direction as read from input device (can be diagonal)
12799 boolean MovePlayerOneStep(struct PlayerInfo *player,
12800 int dx, int dy, int real_dx, int real_dy)
12802 int jx = player->jx, jy = player->jy;
12803 int new_jx = jx + dx, new_jy = jy + dy;
12805 boolean player_can_move = !player->cannot_move;
12807 if (!player->active || (!dx && !dy))
12808 return MP_NO_ACTION;
12810 player->MovDir = (dx < 0 ? MV_LEFT :
12811 dx > 0 ? MV_RIGHT :
12813 dy > 0 ? MV_DOWN : MV_NONE);
12815 if (!IN_LEV_FIELD(new_jx, new_jy))
12816 return MP_NO_ACTION;
12818 if (!player_can_move)
12820 if (player->MovPos == 0)
12822 player->is_moving = FALSE;
12823 player->is_digging = FALSE;
12824 player->is_collecting = FALSE;
12825 player->is_snapping = FALSE;
12826 player->is_pushing = FALSE;
12830 if (!network.enabled && game.centered_player_nr == -1 &&
12831 !AllPlayersInSight(player, new_jx, new_jy))
12832 return MP_NO_ACTION;
12834 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12835 if (can_move != MP_MOVING)
12838 // check if DigField() has caused relocation of the player
12839 if (player->jx != jx || player->jy != jy)
12840 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12842 StorePlayer[jx][jy] = 0;
12843 player->last_jx = jx;
12844 player->last_jy = jy;
12845 player->jx = new_jx;
12846 player->jy = new_jy;
12847 StorePlayer[new_jx][new_jy] = player->element_nr;
12849 if (player->move_delay_value_next != -1)
12851 player->move_delay_value = player->move_delay_value_next;
12852 player->move_delay_value_next = -1;
12856 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12858 player->step_counter++;
12860 PlayerVisit[jx][jy] = FrameCounter;
12862 player->is_moving = TRUE;
12865 // should better be called in MovePlayer(), but this breaks some tapes
12866 ScrollPlayer(player, SCROLL_INIT);
12872 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12874 int jx = player->jx, jy = player->jy;
12875 int old_jx = jx, old_jy = jy;
12876 int moved = MP_NO_ACTION;
12878 if (!player->active)
12883 if (player->MovPos == 0)
12885 player->is_moving = FALSE;
12886 player->is_digging = FALSE;
12887 player->is_collecting = FALSE;
12888 player->is_snapping = FALSE;
12889 player->is_pushing = FALSE;
12895 if (player->move_delay > 0)
12898 player->move_delay = -1; // set to "uninitialized" value
12900 // store if player is automatically moved to next field
12901 player->is_auto_moving = (player->programmed_action != MV_NONE);
12903 // remove the last programmed player action
12904 player->programmed_action = 0;
12906 if (player->MovPos)
12908 // should only happen if pre-1.2 tape recordings are played
12909 // this is only for backward compatibility
12911 int original_move_delay_value = player->move_delay_value;
12914 Debug("game:playing:MovePlayer",
12915 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12919 // scroll remaining steps with finest movement resolution
12920 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12922 while (player->MovPos)
12924 ScrollPlayer(player, SCROLL_GO_ON);
12925 ScrollScreen(NULL, SCROLL_GO_ON);
12927 AdvanceFrameAndPlayerCounters(player->index_nr);
12930 BackToFront_WithFrameDelay(0);
12933 player->move_delay_value = original_move_delay_value;
12936 player->is_active = FALSE;
12938 if (player->last_move_dir & MV_HORIZONTAL)
12940 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12941 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12945 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12946 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12949 if (!moved && !player->is_active)
12951 player->is_moving = FALSE;
12952 player->is_digging = FALSE;
12953 player->is_collecting = FALSE;
12954 player->is_snapping = FALSE;
12955 player->is_pushing = FALSE;
12961 if (moved & MP_MOVING && !ScreenMovPos &&
12962 (player->index_nr == game.centered_player_nr ||
12963 game.centered_player_nr == -1))
12965 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12967 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12969 // actual player has left the screen -- scroll in that direction
12970 if (jx != old_jx) // player has moved horizontally
12971 scroll_x += (jx - old_jx);
12972 else // player has moved vertically
12973 scroll_y += (jy - old_jy);
12977 int offset_raw = game.scroll_delay_value;
12979 if (jx != old_jx) // player has moved horizontally
12981 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12982 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12983 int new_scroll_x = jx - MIDPOSX + offset_x;
12985 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12986 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12987 scroll_x = new_scroll_x;
12989 // don't scroll over playfield boundaries
12990 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12992 // don't scroll more than one field at a time
12993 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12995 // don't scroll against the player's moving direction
12996 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12997 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12998 scroll_x = old_scroll_x;
13000 else // player has moved vertically
13002 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13003 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13004 int new_scroll_y = jy - MIDPOSY + offset_y;
13006 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13007 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13008 scroll_y = new_scroll_y;
13010 // don't scroll over playfield boundaries
13011 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13013 // don't scroll more than one field at a time
13014 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13016 // don't scroll against the player's moving direction
13017 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13018 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13019 scroll_y = old_scroll_y;
13023 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13025 if (!network.enabled && game.centered_player_nr == -1 &&
13026 !AllPlayersInVisibleScreen())
13028 scroll_x = old_scroll_x;
13029 scroll_y = old_scroll_y;
13033 ScrollScreen(player, SCROLL_INIT);
13034 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13039 player->StepFrame = 0;
13041 if (moved & MP_MOVING)
13043 if (old_jx != jx && old_jy == jy)
13044 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13045 else if (old_jx == jx && old_jy != jy)
13046 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13048 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13050 player->last_move_dir = player->MovDir;
13051 player->is_moving = TRUE;
13052 player->is_snapping = FALSE;
13053 player->is_switching = FALSE;
13054 player->is_dropping = FALSE;
13055 player->is_dropping_pressed = FALSE;
13056 player->drop_pressed_delay = 0;
13059 // should better be called here than above, but this breaks some tapes
13060 ScrollPlayer(player, SCROLL_INIT);
13065 CheckGravityMovementWhenNotMoving(player);
13067 player->is_moving = FALSE;
13069 /* at this point, the player is allowed to move, but cannot move right now
13070 (e.g. because of something blocking the way) -- ensure that the player
13071 is also allowed to move in the next frame (in old versions before 3.1.1,
13072 the player was forced to wait again for eight frames before next try) */
13074 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13075 player->move_delay = 0; // allow direct movement in the next frame
13078 if (player->move_delay == -1) // not yet initialized by DigField()
13079 player->move_delay = player->move_delay_value;
13081 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13083 TestIfPlayerTouchesBadThing(jx, jy);
13084 TestIfPlayerTouchesCustomElement(jx, jy);
13087 if (!player->active)
13088 RemovePlayer(player);
13093 void ScrollPlayer(struct PlayerInfo *player, int mode)
13095 int jx = player->jx, jy = player->jy;
13096 int last_jx = player->last_jx, last_jy = player->last_jy;
13097 int move_stepsize = TILEX / player->move_delay_value;
13099 if (!player->active)
13102 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13105 if (mode == SCROLL_INIT)
13107 player->actual_frame_counter = FrameCounter;
13108 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13110 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13111 Tile[last_jx][last_jy] == EL_EMPTY)
13113 int last_field_block_delay = 0; // start with no blocking at all
13114 int block_delay_adjustment = player->block_delay_adjustment;
13116 // if player blocks last field, add delay for exactly one move
13117 if (player->block_last_field)
13119 last_field_block_delay += player->move_delay_value;
13121 // when blocking enabled, prevent moving up despite gravity
13122 if (player->gravity && player->MovDir == MV_UP)
13123 block_delay_adjustment = -1;
13126 // add block delay adjustment (also possible when not blocking)
13127 last_field_block_delay += block_delay_adjustment;
13129 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13130 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13133 if (player->MovPos != 0) // player has not yet reached destination
13136 else if (!FrameReached(&player->actual_frame_counter, 1))
13139 if (player->MovPos != 0)
13141 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13142 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13144 // before DrawPlayer() to draw correct player graphic for this case
13145 if (player->MovPos == 0)
13146 CheckGravityMovement(player);
13149 if (player->MovPos == 0) // player reached destination field
13151 if (player->move_delay_reset_counter > 0)
13153 player->move_delay_reset_counter--;
13155 if (player->move_delay_reset_counter == 0)
13157 // continue with normal speed after quickly moving through gate
13158 HALVE_PLAYER_SPEED(player);
13160 // be able to make the next move without delay
13161 player->move_delay = 0;
13165 player->last_jx = jx;
13166 player->last_jy = jy;
13168 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13169 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13170 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13171 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13172 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13173 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13174 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13175 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13177 ExitPlayer(player);
13179 if (game.players_still_needed == 0 &&
13180 (game.friends_still_needed == 0 ||
13181 IS_SP_ELEMENT(Tile[jx][jy])))
13185 // this breaks one level: "machine", level 000
13187 int move_direction = player->MovDir;
13188 int enter_side = MV_DIR_OPPOSITE(move_direction);
13189 int leave_side = move_direction;
13190 int old_jx = last_jx;
13191 int old_jy = last_jy;
13192 int old_element = Tile[old_jx][old_jy];
13193 int new_element = Tile[jx][jy];
13195 if (IS_CUSTOM_ELEMENT(old_element))
13196 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13198 player->index_bit, leave_side);
13200 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13201 CE_PLAYER_LEAVES_X,
13202 player->index_bit, leave_side);
13204 if (IS_CUSTOM_ELEMENT(new_element))
13205 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13206 player->index_bit, enter_side);
13208 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13209 CE_PLAYER_ENTERS_X,
13210 player->index_bit, enter_side);
13212 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13213 CE_MOVE_OF_X, move_direction);
13216 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13218 TestIfPlayerTouchesBadThing(jx, jy);
13219 TestIfPlayerTouchesCustomElement(jx, jy);
13221 /* needed because pushed element has not yet reached its destination,
13222 so it would trigger a change event at its previous field location */
13223 if (!player->is_pushing)
13224 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13226 if (level.finish_dig_collect &&
13227 (player->is_digging || player->is_collecting))
13229 int last_element = player->last_removed_element;
13230 int move_direction = player->MovDir;
13231 int enter_side = MV_DIR_OPPOSITE(move_direction);
13232 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13233 CE_PLAYER_COLLECTS_X);
13235 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13236 player->index_bit, enter_side);
13238 player->last_removed_element = EL_UNDEFINED;
13241 if (!player->active)
13242 RemovePlayer(player);
13245 if (level.use_step_counter)
13255 if (TimeLeft <= 10 && setup.time_limit && !game.LevelSolved)
13256 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13258 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13260 DisplayGameControlValues();
13262 if (!TimeLeft && setup.time_limit && !game.LevelSolved)
13263 for (i = 0; i < MAX_PLAYERS; i++)
13264 KillPlayer(&stored_player[i]);
13266 else if (game.no_time_limit && !game.all_players_gone)
13268 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13270 DisplayGameControlValues();
13274 if (tape.single_step && tape.recording && !tape.pausing &&
13275 !player->programmed_action)
13276 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13278 if (!player->programmed_action)
13279 CheckSaveEngineSnapshot(player);
13283 void ScrollScreen(struct PlayerInfo *player, int mode)
13285 static unsigned int screen_frame_counter = 0;
13287 if (mode == SCROLL_INIT)
13289 // set scrolling step size according to actual player's moving speed
13290 ScrollStepSize = TILEX / player->move_delay_value;
13292 screen_frame_counter = FrameCounter;
13293 ScreenMovDir = player->MovDir;
13294 ScreenMovPos = player->MovPos;
13295 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13298 else if (!FrameReached(&screen_frame_counter, 1))
13303 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13304 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13305 redraw_mask |= REDRAW_FIELD;
13308 ScreenMovDir = MV_NONE;
13311 void CheckNextToConditions(int x, int y)
13313 int element = Tile[x][y];
13315 if (IS_PLAYER(x, y))
13316 TestIfPlayerNextToCustomElement(x, y);
13318 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13319 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13320 TestIfElementNextToCustomElement(x, y);
13323 void TestIfPlayerNextToCustomElement(int x, int y)
13325 static int xy[4][2] =
13332 static int trigger_sides[4][2] =
13334 // center side border side
13335 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13336 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13337 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13338 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13342 if (!IS_PLAYER(x, y))
13345 struct PlayerInfo *player = PLAYERINFO(x, y);
13347 if (player->is_moving)
13350 for (i = 0; i < NUM_DIRECTIONS; i++)
13352 int xx = x + xy[i][0];
13353 int yy = y + xy[i][1];
13354 int border_side = trigger_sides[i][1];
13355 int border_element;
13357 if (!IN_LEV_FIELD(xx, yy))
13360 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13361 continue; // center and border element not connected
13363 border_element = Tile[xx][yy];
13365 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13366 player->index_bit, border_side);
13367 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13368 CE_PLAYER_NEXT_TO_X,
13369 player->index_bit, border_side);
13371 /* use player element that is initially defined in the level playfield,
13372 not the player element that corresponds to the runtime player number
13373 (example: a level that contains EL_PLAYER_3 as the only player would
13374 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13376 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13377 CE_NEXT_TO_X, border_side);
13381 void TestIfPlayerTouchesCustomElement(int x, int y)
13383 static int xy[4][2] =
13390 static int trigger_sides[4][2] =
13392 // center side border side
13393 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13394 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13395 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13396 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13398 static int touch_dir[4] =
13400 MV_LEFT | MV_RIGHT,
13405 int center_element = Tile[x][y]; // should always be non-moving!
13408 for (i = 0; i < NUM_DIRECTIONS; i++)
13410 int xx = x + xy[i][0];
13411 int yy = y + xy[i][1];
13412 int center_side = trigger_sides[i][0];
13413 int border_side = trigger_sides[i][1];
13414 int border_element;
13416 if (!IN_LEV_FIELD(xx, yy))
13419 if (IS_PLAYER(x, y)) // player found at center element
13421 struct PlayerInfo *player = PLAYERINFO(x, y);
13423 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13424 border_element = Tile[xx][yy]; // may be moving!
13425 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13426 border_element = Tile[xx][yy];
13427 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13428 border_element = MovingOrBlocked2Element(xx, yy);
13430 continue; // center and border element do not touch
13432 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13433 player->index_bit, border_side);
13434 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13435 CE_PLAYER_TOUCHES_X,
13436 player->index_bit, border_side);
13439 /* use player element that is initially defined in the level playfield,
13440 not the player element that corresponds to the runtime player number
13441 (example: a level that contains EL_PLAYER_3 as the only player would
13442 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13443 int player_element = PLAYERINFO(x, y)->initial_element;
13445 CheckElementChangeBySide(xx, yy, border_element, player_element,
13446 CE_TOUCHING_X, border_side);
13449 else if (IS_PLAYER(xx, yy)) // player found at border element
13451 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13453 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13455 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13456 continue; // center and border element do not touch
13459 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13460 player->index_bit, center_side);
13461 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13462 CE_PLAYER_TOUCHES_X,
13463 player->index_bit, center_side);
13466 /* use player element that is initially defined in the level playfield,
13467 not the player element that corresponds to the runtime player number
13468 (example: a level that contains EL_PLAYER_3 as the only player would
13469 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13470 int player_element = PLAYERINFO(xx, yy)->initial_element;
13472 CheckElementChangeBySide(x, y, center_element, player_element,
13473 CE_TOUCHING_X, center_side);
13481 void TestIfElementNextToCustomElement(int x, int y)
13483 static int xy[4][2] =
13490 static int trigger_sides[4][2] =
13492 // center side border side
13493 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13494 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13495 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13496 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13498 int center_element = Tile[x][y]; // should always be non-moving!
13501 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13504 for (i = 0; i < NUM_DIRECTIONS; i++)
13506 int xx = x + xy[i][0];
13507 int yy = y + xy[i][1];
13508 int border_side = trigger_sides[i][1];
13509 int border_element;
13511 if (!IN_LEV_FIELD(xx, yy))
13514 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13515 continue; // center and border element not connected
13517 border_element = Tile[xx][yy];
13519 // check for change of center element (but change it only once)
13520 if (CheckElementChangeBySide(x, y, center_element, border_element,
13521 CE_NEXT_TO_X, border_side))
13526 void TestIfElementTouchesCustomElement(int x, int y)
13528 static int xy[4][2] =
13535 static int trigger_sides[4][2] =
13537 // center side border side
13538 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13539 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13540 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13541 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13543 static int touch_dir[4] =
13545 MV_LEFT | MV_RIGHT,
13550 boolean change_center_element = FALSE;
13551 int center_element = Tile[x][y]; // should always be non-moving!
13552 int border_element_old[NUM_DIRECTIONS];
13555 for (i = 0; i < NUM_DIRECTIONS; i++)
13557 int xx = x + xy[i][0];
13558 int yy = y + xy[i][1];
13559 int border_element;
13561 border_element_old[i] = -1;
13563 if (!IN_LEV_FIELD(xx, yy))
13566 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13567 border_element = Tile[xx][yy]; // may be moving!
13568 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13569 border_element = Tile[xx][yy];
13570 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13571 border_element = MovingOrBlocked2Element(xx, yy);
13573 continue; // center and border element do not touch
13575 border_element_old[i] = border_element;
13578 for (i = 0; i < NUM_DIRECTIONS; i++)
13580 int xx = x + xy[i][0];
13581 int yy = y + xy[i][1];
13582 int center_side = trigger_sides[i][0];
13583 int border_element = border_element_old[i];
13585 if (border_element == -1)
13588 // check for change of border element
13589 CheckElementChangeBySide(xx, yy, border_element, center_element,
13590 CE_TOUCHING_X, center_side);
13592 // (center element cannot be player, so we dont have to check this here)
13595 for (i = 0; i < NUM_DIRECTIONS; i++)
13597 int xx = x + xy[i][0];
13598 int yy = y + xy[i][1];
13599 int border_side = trigger_sides[i][1];
13600 int border_element = border_element_old[i];
13602 if (border_element == -1)
13605 // check for change of center element (but change it only once)
13606 if (!change_center_element)
13607 change_center_element =
13608 CheckElementChangeBySide(x, y, center_element, border_element,
13609 CE_TOUCHING_X, border_side);
13611 if (IS_PLAYER(xx, yy))
13613 /* use player element that is initially defined in the level playfield,
13614 not the player element that corresponds to the runtime player number
13615 (example: a level that contains EL_PLAYER_3 as the only player would
13616 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13617 int player_element = PLAYERINFO(xx, yy)->initial_element;
13619 CheckElementChangeBySide(x, y, center_element, player_element,
13620 CE_TOUCHING_X, border_side);
13625 void TestIfElementHitsCustomElement(int x, int y, int direction)
13627 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13628 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13629 int hitx = x + dx, hity = y + dy;
13630 int hitting_element = Tile[x][y];
13631 int touched_element;
13633 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13636 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13637 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13639 if (IN_LEV_FIELD(hitx, hity))
13641 int opposite_direction = MV_DIR_OPPOSITE(direction);
13642 int hitting_side = direction;
13643 int touched_side = opposite_direction;
13644 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13645 MovDir[hitx][hity] != direction ||
13646 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13652 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13653 CE_HITTING_X, touched_side);
13655 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13656 CE_HIT_BY_X, hitting_side);
13658 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13659 CE_HIT_BY_SOMETHING, opposite_direction);
13661 if (IS_PLAYER(hitx, hity))
13663 /* use player element that is initially defined in the level playfield,
13664 not the player element that corresponds to the runtime player number
13665 (example: a level that contains EL_PLAYER_3 as the only player would
13666 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13667 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13669 CheckElementChangeBySide(x, y, hitting_element, player_element,
13670 CE_HITTING_X, touched_side);
13675 // "hitting something" is also true when hitting the playfield border
13676 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13677 CE_HITTING_SOMETHING, direction);
13680 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13682 int i, kill_x = -1, kill_y = -1;
13684 int bad_element = -1;
13685 static int test_xy[4][2] =
13692 static int test_dir[4] =
13700 for (i = 0; i < NUM_DIRECTIONS; i++)
13702 int test_x, test_y, test_move_dir, test_element;
13704 test_x = good_x + test_xy[i][0];
13705 test_y = good_y + test_xy[i][1];
13707 if (!IN_LEV_FIELD(test_x, test_y))
13711 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13713 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13715 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13716 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13718 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13719 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13723 bad_element = test_element;
13729 if (kill_x != -1 || kill_y != -1)
13731 if (IS_PLAYER(good_x, good_y))
13733 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13735 if (player->shield_deadly_time_left > 0 &&
13736 !IS_INDESTRUCTIBLE(bad_element))
13737 Bang(kill_x, kill_y);
13738 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13739 KillPlayer(player);
13742 Bang(good_x, good_y);
13746 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13748 int i, kill_x = -1, kill_y = -1;
13749 int bad_element = Tile[bad_x][bad_y];
13750 static int test_xy[4][2] =
13757 static int touch_dir[4] =
13759 MV_LEFT | MV_RIGHT,
13764 static int test_dir[4] =
13772 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13775 for (i = 0; i < NUM_DIRECTIONS; i++)
13777 int test_x, test_y, test_move_dir, test_element;
13779 test_x = bad_x + test_xy[i][0];
13780 test_y = bad_y + test_xy[i][1];
13782 if (!IN_LEV_FIELD(test_x, test_y))
13786 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13788 test_element = Tile[test_x][test_y];
13790 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13791 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13793 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13794 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13796 // good thing is player or penguin that does not move away
13797 if (IS_PLAYER(test_x, test_y))
13799 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13801 if (bad_element == EL_ROBOT && player->is_moving)
13802 continue; // robot does not kill player if he is moving
13804 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13806 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13807 continue; // center and border element do not touch
13815 else if (test_element == EL_PENGUIN)
13825 if (kill_x != -1 || kill_y != -1)
13827 if (IS_PLAYER(kill_x, kill_y))
13829 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13831 if (player->shield_deadly_time_left > 0 &&
13832 !IS_INDESTRUCTIBLE(bad_element))
13833 Bang(bad_x, bad_y);
13834 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13835 KillPlayer(player);
13838 Bang(kill_x, kill_y);
13842 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13844 int bad_element = Tile[bad_x][bad_y];
13845 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13846 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13847 int test_x = bad_x + dx, test_y = bad_y + dy;
13848 int test_move_dir, test_element;
13849 int kill_x = -1, kill_y = -1;
13851 if (!IN_LEV_FIELD(test_x, test_y))
13855 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13857 test_element = Tile[test_x][test_y];
13859 if (test_move_dir != bad_move_dir)
13861 // good thing can be player or penguin that does not move away
13862 if (IS_PLAYER(test_x, test_y))
13864 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13866 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13867 player as being hit when he is moving towards the bad thing, because
13868 the "get hit by" condition would be lost after the player stops) */
13869 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13870 return; // player moves away from bad thing
13875 else if (test_element == EL_PENGUIN)
13882 if (kill_x != -1 || kill_y != -1)
13884 if (IS_PLAYER(kill_x, kill_y))
13886 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13888 if (player->shield_deadly_time_left > 0 &&
13889 !IS_INDESTRUCTIBLE(bad_element))
13890 Bang(bad_x, bad_y);
13891 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13892 KillPlayer(player);
13895 Bang(kill_x, kill_y);
13899 void TestIfPlayerTouchesBadThing(int x, int y)
13901 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13904 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13906 TestIfGoodThingHitsBadThing(x, y, move_dir);
13909 void TestIfBadThingTouchesPlayer(int x, int y)
13911 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13914 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13916 TestIfBadThingHitsGoodThing(x, y, move_dir);
13919 void TestIfFriendTouchesBadThing(int x, int y)
13921 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13924 void TestIfBadThingTouchesFriend(int x, int y)
13926 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13929 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13931 int i, kill_x = bad_x, kill_y = bad_y;
13932 static int xy[4][2] =
13940 for (i = 0; i < NUM_DIRECTIONS; i++)
13944 x = bad_x + xy[i][0];
13945 y = bad_y + xy[i][1];
13946 if (!IN_LEV_FIELD(x, y))
13949 element = Tile[x][y];
13950 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13951 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13959 if (kill_x != bad_x || kill_y != bad_y)
13960 Bang(bad_x, bad_y);
13963 void KillPlayer(struct PlayerInfo *player)
13965 int jx = player->jx, jy = player->jy;
13967 if (!player->active)
13971 Debug("game:playing:KillPlayer",
13972 "0: killed == %d, active == %d, reanimated == %d",
13973 player->killed, player->active, player->reanimated);
13976 /* the following code was introduced to prevent an infinite loop when calling
13978 -> CheckTriggeredElementChangeExt()
13979 -> ExecuteCustomElementAction()
13981 -> (infinitely repeating the above sequence of function calls)
13982 which occurs when killing the player while having a CE with the setting
13983 "kill player X when explosion of <player X>"; the solution using a new
13984 field "player->killed" was chosen for backwards compatibility, although
13985 clever use of the fields "player->active" etc. would probably also work */
13987 if (player->killed)
13991 player->killed = TRUE;
13993 // remove accessible field at the player's position
13994 Tile[jx][jy] = EL_EMPTY;
13996 // deactivate shield (else Bang()/Explode() would not work right)
13997 player->shield_normal_time_left = 0;
13998 player->shield_deadly_time_left = 0;
14001 Debug("game:playing:KillPlayer",
14002 "1: killed == %d, active == %d, reanimated == %d",
14003 player->killed, player->active, player->reanimated);
14009 Debug("game:playing:KillPlayer",
14010 "2: killed == %d, active == %d, reanimated == %d",
14011 player->killed, player->active, player->reanimated);
14014 if (player->reanimated) // killed player may have been reanimated
14015 player->killed = player->reanimated = FALSE;
14017 BuryPlayer(player);
14020 static void KillPlayerUnlessEnemyProtected(int x, int y)
14022 if (!PLAYER_ENEMY_PROTECTED(x, y))
14023 KillPlayer(PLAYERINFO(x, y));
14026 static void KillPlayerUnlessExplosionProtected(int x, int y)
14028 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14029 KillPlayer(PLAYERINFO(x, y));
14032 void BuryPlayer(struct PlayerInfo *player)
14034 int jx = player->jx, jy = player->jy;
14036 if (!player->active)
14039 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14040 PlayLevelSound(jx, jy, SND_GAME_LOSING);
14042 RemovePlayer(player);
14044 player->buried = TRUE;
14046 if (game.all_players_gone)
14047 game.GameOver = TRUE;
14050 void RemovePlayer(struct PlayerInfo *player)
14052 int jx = player->jx, jy = player->jy;
14053 int i, found = FALSE;
14055 player->present = FALSE;
14056 player->active = FALSE;
14058 // required for some CE actions (even if the player is not active anymore)
14059 player->MovPos = 0;
14061 if (!ExplodeField[jx][jy])
14062 StorePlayer[jx][jy] = 0;
14064 if (player->is_moving)
14065 TEST_DrawLevelField(player->last_jx, player->last_jy);
14067 for (i = 0; i < MAX_PLAYERS; i++)
14068 if (stored_player[i].active)
14073 game.all_players_gone = TRUE;
14074 game.GameOver = TRUE;
14077 game.exit_x = game.robot_wheel_x = jx;
14078 game.exit_y = game.robot_wheel_y = jy;
14081 void ExitPlayer(struct PlayerInfo *player)
14083 DrawPlayer(player); // needed here only to cleanup last field
14084 RemovePlayer(player);
14086 if (game.players_still_needed > 0)
14087 game.players_still_needed--;
14090 static void SetFieldForSnapping(int x, int y, int element, int direction,
14091 int player_index_bit)
14093 struct ElementInfo *ei = &element_info[element];
14094 int direction_bit = MV_DIR_TO_BIT(direction);
14095 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14096 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14097 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14099 Tile[x][y] = EL_ELEMENT_SNAPPING;
14100 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14101 MovDir[x][y] = direction;
14102 Store[x][y] = element;
14103 Store2[x][y] = player_index_bit;
14105 ResetGfxAnimation(x, y);
14107 GfxElement[x][y] = element;
14108 GfxAction[x][y] = action;
14109 GfxDir[x][y] = direction;
14110 GfxFrame[x][y] = -1;
14113 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14114 int player_index_bit)
14116 TestIfElementTouchesCustomElement(x, y); // for empty space
14118 if (level.finish_dig_collect)
14120 int dig_side = MV_DIR_OPPOSITE(direction);
14121 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14122 CE_PLAYER_COLLECTS_X);
14124 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14125 player_index_bit, dig_side);
14126 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14127 player_index_bit, dig_side);
14132 =============================================================================
14133 checkDiagonalPushing()
14134 -----------------------------------------------------------------------------
14135 check if diagonal input device direction results in pushing of object
14136 (by checking if the alternative direction is walkable, diggable, ...)
14137 =============================================================================
14140 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14141 int x, int y, int real_dx, int real_dy)
14143 int jx, jy, dx, dy, xx, yy;
14145 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14148 // diagonal direction: check alternative direction
14153 xx = jx + (dx == 0 ? real_dx : 0);
14154 yy = jy + (dy == 0 ? real_dy : 0);
14156 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14160 =============================================================================
14162 -----------------------------------------------------------------------------
14163 x, y: field next to player (non-diagonal) to try to dig to
14164 real_dx, real_dy: direction as read from input device (can be diagonal)
14165 =============================================================================
14168 static int DigField(struct PlayerInfo *player,
14169 int oldx, int oldy, int x, int y,
14170 int real_dx, int real_dy, int mode)
14172 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14173 boolean player_was_pushing = player->is_pushing;
14174 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14175 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14176 int jx = oldx, jy = oldy;
14177 int dx = x - jx, dy = y - jy;
14178 int nextx = x + dx, nexty = y + dy;
14179 int move_direction = (dx == -1 ? MV_LEFT :
14180 dx == +1 ? MV_RIGHT :
14182 dy == +1 ? MV_DOWN : MV_NONE);
14183 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14184 int dig_side = MV_DIR_OPPOSITE(move_direction);
14185 int old_element = Tile[jx][jy];
14186 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14189 if (is_player) // function can also be called by EL_PENGUIN
14191 if (player->MovPos == 0)
14193 player->is_digging = FALSE;
14194 player->is_collecting = FALSE;
14197 if (player->MovPos == 0) // last pushing move finished
14198 player->is_pushing = FALSE;
14200 if (mode == DF_NO_PUSH) // player just stopped pushing
14202 player->is_switching = FALSE;
14203 player->push_delay = -1;
14205 return MP_NO_ACTION;
14208 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14209 old_element = Back[jx][jy];
14211 // in case of element dropped at player position, check background
14212 else if (Back[jx][jy] != EL_EMPTY &&
14213 game.engine_version >= VERSION_IDENT(2,2,0,0))
14214 old_element = Back[jx][jy];
14216 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14217 return MP_NO_ACTION; // field has no opening in this direction
14219 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
14220 return MP_NO_ACTION; // field has no opening in this direction
14222 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14226 Tile[jx][jy] = player->artwork_element;
14227 InitMovingField(jx, jy, MV_DOWN);
14228 Store[jx][jy] = EL_ACID;
14229 ContinueMoving(jx, jy);
14230 BuryPlayer(player);
14232 return MP_DONT_RUN_INTO;
14235 if (player_can_move && DONT_RUN_INTO(element))
14237 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14239 return MP_DONT_RUN_INTO;
14242 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14243 return MP_NO_ACTION;
14245 collect_count = element_info[element].collect_count_initial;
14247 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14248 return MP_NO_ACTION;
14250 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14251 player_can_move = player_can_move_or_snap;
14253 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14254 game.engine_version >= VERSION_IDENT(2,2,0,0))
14256 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14257 player->index_bit, dig_side);
14258 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14259 player->index_bit, dig_side);
14261 if (element == EL_DC_LANDMINE)
14264 if (Tile[x][y] != element) // field changed by snapping
14267 return MP_NO_ACTION;
14270 if (player->gravity && is_player && !player->is_auto_moving &&
14271 canFallDown(player) && move_direction != MV_DOWN &&
14272 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14273 return MP_NO_ACTION; // player cannot walk here due to gravity
14275 if (player_can_move &&
14276 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14278 int sound_element = SND_ELEMENT(element);
14279 int sound_action = ACTION_WALKING;
14281 if (IS_RND_GATE(element))
14283 if (!player->key[RND_GATE_NR(element)])
14284 return MP_NO_ACTION;
14286 else if (IS_RND_GATE_GRAY(element))
14288 if (!player->key[RND_GATE_GRAY_NR(element)])
14289 return MP_NO_ACTION;
14291 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14293 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14294 return MP_NO_ACTION;
14296 else if (element == EL_EXIT_OPEN ||
14297 element == EL_EM_EXIT_OPEN ||
14298 element == EL_EM_EXIT_OPENING ||
14299 element == EL_STEEL_EXIT_OPEN ||
14300 element == EL_EM_STEEL_EXIT_OPEN ||
14301 element == EL_EM_STEEL_EXIT_OPENING ||
14302 element == EL_SP_EXIT_OPEN ||
14303 element == EL_SP_EXIT_OPENING)
14305 sound_action = ACTION_PASSING; // player is passing exit
14307 else if (element == EL_EMPTY)
14309 sound_action = ACTION_MOVING; // nothing to walk on
14312 // play sound from background or player, whatever is available
14313 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14314 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14316 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14318 else if (player_can_move &&
14319 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14321 if (!ACCESS_FROM(element, opposite_direction))
14322 return MP_NO_ACTION; // field not accessible from this direction
14324 if (CAN_MOVE(element)) // only fixed elements can be passed!
14325 return MP_NO_ACTION;
14327 if (IS_EM_GATE(element))
14329 if (!player->key[EM_GATE_NR(element)])
14330 return MP_NO_ACTION;
14332 else if (IS_EM_GATE_GRAY(element))
14334 if (!player->key[EM_GATE_GRAY_NR(element)])
14335 return MP_NO_ACTION;
14337 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14339 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14340 return MP_NO_ACTION;
14342 else if (IS_EMC_GATE(element))
14344 if (!player->key[EMC_GATE_NR(element)])
14345 return MP_NO_ACTION;
14347 else if (IS_EMC_GATE_GRAY(element))
14349 if (!player->key[EMC_GATE_GRAY_NR(element)])
14350 return MP_NO_ACTION;
14352 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14354 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14355 return MP_NO_ACTION;
14357 else if (element == EL_DC_GATE_WHITE ||
14358 element == EL_DC_GATE_WHITE_GRAY ||
14359 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14361 if (player->num_white_keys == 0)
14362 return MP_NO_ACTION;
14364 player->num_white_keys--;
14366 else if (IS_SP_PORT(element))
14368 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14369 element == EL_SP_GRAVITY_PORT_RIGHT ||
14370 element == EL_SP_GRAVITY_PORT_UP ||
14371 element == EL_SP_GRAVITY_PORT_DOWN)
14372 player->gravity = !player->gravity;
14373 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14374 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14375 element == EL_SP_GRAVITY_ON_PORT_UP ||
14376 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14377 player->gravity = TRUE;
14378 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14379 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14380 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14381 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14382 player->gravity = FALSE;
14385 // automatically move to the next field with double speed
14386 player->programmed_action = move_direction;
14388 if (player->move_delay_reset_counter == 0)
14390 player->move_delay_reset_counter = 2; // two double speed steps
14392 DOUBLE_PLAYER_SPEED(player);
14395 PlayLevelSoundAction(x, y, ACTION_PASSING);
14397 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14401 if (mode != DF_SNAP)
14403 GfxElement[x][y] = GFX_ELEMENT(element);
14404 player->is_digging = TRUE;
14407 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14409 // use old behaviour for old levels (digging)
14410 if (!level.finish_dig_collect)
14412 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14413 player->index_bit, dig_side);
14415 // if digging triggered player relocation, finish digging tile
14416 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14417 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14420 if (mode == DF_SNAP)
14422 if (level.block_snap_field)
14423 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14425 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14427 // use old behaviour for old levels (snapping)
14428 if (!level.finish_dig_collect)
14429 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14430 player->index_bit, dig_side);
14433 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14437 if (is_player && mode != DF_SNAP)
14439 GfxElement[x][y] = element;
14440 player->is_collecting = TRUE;
14443 if (element == EL_SPEED_PILL)
14445 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14447 else if (element == EL_EXTRA_TIME && level.time > 0)
14449 TimeLeft += level.extra_time;
14451 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14453 DisplayGameControlValues();
14455 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14457 player->shield_normal_time_left += level.shield_normal_time;
14458 if (element == EL_SHIELD_DEADLY)
14459 player->shield_deadly_time_left += level.shield_deadly_time;
14461 else if (element == EL_DYNAMITE ||
14462 element == EL_EM_DYNAMITE ||
14463 element == EL_SP_DISK_RED)
14465 if (player->inventory_size < MAX_INVENTORY_SIZE)
14466 player->inventory_element[player->inventory_size++] = element;
14468 DrawGameDoorValues();
14470 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14472 player->dynabomb_count++;
14473 player->dynabombs_left++;
14475 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14477 player->dynabomb_size++;
14479 else if (element == EL_DYNABOMB_INCREASE_POWER)
14481 player->dynabomb_xl = TRUE;
14483 else if (IS_KEY(element))
14485 player->key[KEY_NR(element)] = TRUE;
14487 DrawGameDoorValues();
14489 else if (element == EL_DC_KEY_WHITE)
14491 player->num_white_keys++;
14493 // display white keys?
14494 // DrawGameDoorValues();
14496 else if (IS_ENVELOPE(element))
14498 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14500 if (!wait_for_snapping)
14501 player->show_envelope = element;
14503 else if (element == EL_EMC_LENSES)
14505 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14507 RedrawAllInvisibleElementsForLenses();
14509 else if (element == EL_EMC_MAGNIFIER)
14511 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14513 RedrawAllInvisibleElementsForMagnifier();
14515 else if (IS_DROPPABLE(element) ||
14516 IS_THROWABLE(element)) // can be collected and dropped
14520 if (collect_count == 0)
14521 player->inventory_infinite_element = element;
14523 for (i = 0; i < collect_count; i++)
14524 if (player->inventory_size < MAX_INVENTORY_SIZE)
14525 player->inventory_element[player->inventory_size++] = element;
14527 DrawGameDoorValues();
14529 else if (collect_count > 0)
14531 game.gems_still_needed -= collect_count;
14532 if (game.gems_still_needed < 0)
14533 game.gems_still_needed = 0;
14535 game.snapshot.collected_item = TRUE;
14537 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14539 DisplayGameControlValues();
14542 RaiseScoreElement(element);
14543 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14545 // use old behaviour for old levels (collecting)
14546 if (!level.finish_dig_collect && is_player)
14548 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14549 player->index_bit, dig_side);
14551 // if collecting triggered player relocation, finish collecting tile
14552 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14553 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14556 if (mode == DF_SNAP)
14558 if (level.block_snap_field)
14559 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14561 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14563 // use old behaviour for old levels (snapping)
14564 if (!level.finish_dig_collect)
14565 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14566 player->index_bit, dig_side);
14569 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14571 if (mode == DF_SNAP && element != EL_BD_ROCK)
14572 return MP_NO_ACTION;
14574 if (CAN_FALL(element) && dy)
14575 return MP_NO_ACTION;
14577 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14578 !(element == EL_SPRING && level.use_spring_bug))
14579 return MP_NO_ACTION;
14581 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14582 ((move_direction & MV_VERTICAL &&
14583 ((element_info[element].move_pattern & MV_LEFT &&
14584 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14585 (element_info[element].move_pattern & MV_RIGHT &&
14586 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14587 (move_direction & MV_HORIZONTAL &&
14588 ((element_info[element].move_pattern & MV_UP &&
14589 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14590 (element_info[element].move_pattern & MV_DOWN &&
14591 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14592 return MP_NO_ACTION;
14594 // do not push elements already moving away faster than player
14595 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14596 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14597 return MP_NO_ACTION;
14599 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14601 if (player->push_delay_value == -1 || !player_was_pushing)
14602 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14604 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14606 if (player->push_delay_value == -1)
14607 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14609 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14611 if (!player->is_pushing)
14612 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14615 player->is_pushing = TRUE;
14616 player->is_active = TRUE;
14618 if (!(IN_LEV_FIELD(nextx, nexty) &&
14619 (IS_FREE(nextx, nexty) ||
14620 (IS_SB_ELEMENT(element) &&
14621 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14622 (IS_CUSTOM_ELEMENT(element) &&
14623 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14624 return MP_NO_ACTION;
14626 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14627 return MP_NO_ACTION;
14629 if (player->push_delay == -1) // new pushing; restart delay
14630 player->push_delay = 0;
14632 if (player->push_delay < player->push_delay_value &&
14633 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14634 element != EL_SPRING && element != EL_BALLOON)
14636 // make sure that there is no move delay before next try to push
14637 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14638 player->move_delay = 0;
14640 return MP_NO_ACTION;
14643 if (IS_CUSTOM_ELEMENT(element) &&
14644 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14646 if (!DigFieldByCE(nextx, nexty, element))
14647 return MP_NO_ACTION;
14650 if (IS_SB_ELEMENT(element))
14652 boolean sokoban_task_solved = FALSE;
14654 if (element == EL_SOKOBAN_FIELD_FULL)
14656 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14658 IncrementSokobanFieldsNeeded();
14659 IncrementSokobanObjectsNeeded();
14662 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14664 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14666 DecrementSokobanFieldsNeeded();
14667 DecrementSokobanObjectsNeeded();
14669 // sokoban object was pushed from empty field to sokoban field
14670 if (Back[x][y] == EL_EMPTY)
14671 sokoban_task_solved = TRUE;
14674 Tile[x][y] = EL_SOKOBAN_OBJECT;
14676 if (Back[x][y] == Back[nextx][nexty])
14677 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14678 else if (Back[x][y] != 0)
14679 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14682 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14685 if (sokoban_task_solved &&
14686 game.sokoban_fields_still_needed == 0 &&
14687 game.sokoban_objects_still_needed == 0 &&
14688 level.auto_exit_sokoban)
14690 game.players_still_needed = 0;
14694 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14698 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14700 InitMovingField(x, y, move_direction);
14701 GfxAction[x][y] = ACTION_PUSHING;
14703 if (mode == DF_SNAP)
14704 ContinueMoving(x, y);
14706 MovPos[x][y] = (dx != 0 ? dx : dy);
14708 Pushed[x][y] = TRUE;
14709 Pushed[nextx][nexty] = TRUE;
14711 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14712 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14714 player->push_delay_value = -1; // get new value later
14716 // check for element change _after_ element has been pushed
14717 if (game.use_change_when_pushing_bug)
14719 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14720 player->index_bit, dig_side);
14721 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14722 player->index_bit, dig_side);
14725 else if (IS_SWITCHABLE(element))
14727 if (PLAYER_SWITCHING(player, x, y))
14729 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14730 player->index_bit, dig_side);
14735 player->is_switching = TRUE;
14736 player->switch_x = x;
14737 player->switch_y = y;
14739 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14741 if (element == EL_ROBOT_WHEEL)
14743 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14745 game.robot_wheel_x = x;
14746 game.robot_wheel_y = y;
14747 game.robot_wheel_active = TRUE;
14749 TEST_DrawLevelField(x, y);
14751 else if (element == EL_SP_TERMINAL)
14755 SCAN_PLAYFIELD(xx, yy)
14757 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14761 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14763 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14765 ResetGfxAnimation(xx, yy);
14766 TEST_DrawLevelField(xx, yy);
14770 else if (IS_BELT_SWITCH(element))
14772 ToggleBeltSwitch(x, y);
14774 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14775 element == EL_SWITCHGATE_SWITCH_DOWN ||
14776 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14777 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14779 ToggleSwitchgateSwitch(x, y);
14781 else if (element == EL_LIGHT_SWITCH ||
14782 element == EL_LIGHT_SWITCH_ACTIVE)
14784 ToggleLightSwitch(x, y);
14786 else if (element == EL_TIMEGATE_SWITCH ||
14787 element == EL_DC_TIMEGATE_SWITCH)
14789 ActivateTimegateSwitch(x, y);
14791 else if (element == EL_BALLOON_SWITCH_LEFT ||
14792 element == EL_BALLOON_SWITCH_RIGHT ||
14793 element == EL_BALLOON_SWITCH_UP ||
14794 element == EL_BALLOON_SWITCH_DOWN ||
14795 element == EL_BALLOON_SWITCH_NONE ||
14796 element == EL_BALLOON_SWITCH_ANY)
14798 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14799 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14800 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14801 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14802 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14805 else if (element == EL_LAMP)
14807 Tile[x][y] = EL_LAMP_ACTIVE;
14808 game.lights_still_needed--;
14810 ResetGfxAnimation(x, y);
14811 TEST_DrawLevelField(x, y);
14813 else if (element == EL_TIME_ORB_FULL)
14815 Tile[x][y] = EL_TIME_ORB_EMPTY;
14817 if (level.time > 0 || level.use_time_orb_bug)
14819 TimeLeft += level.time_orb_time;
14820 game.no_time_limit = FALSE;
14822 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14824 DisplayGameControlValues();
14827 ResetGfxAnimation(x, y);
14828 TEST_DrawLevelField(x, y);
14830 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14831 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14835 game.ball_active = !game.ball_active;
14837 SCAN_PLAYFIELD(xx, yy)
14839 int e = Tile[xx][yy];
14841 if (game.ball_active)
14843 if (e == EL_EMC_MAGIC_BALL)
14844 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14845 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14846 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14850 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14851 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14852 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14853 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14858 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14859 player->index_bit, dig_side);
14861 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14862 player->index_bit, dig_side);
14864 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14865 player->index_bit, dig_side);
14871 if (!PLAYER_SWITCHING(player, x, y))
14873 player->is_switching = TRUE;
14874 player->switch_x = x;
14875 player->switch_y = y;
14877 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14878 player->index_bit, dig_side);
14879 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14880 player->index_bit, dig_side);
14882 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14883 player->index_bit, dig_side);
14884 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14885 player->index_bit, dig_side);
14888 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14889 player->index_bit, dig_side);
14890 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14891 player->index_bit, dig_side);
14893 return MP_NO_ACTION;
14896 player->push_delay = -1;
14898 if (is_player) // function can also be called by EL_PENGUIN
14900 if (Tile[x][y] != element) // really digged/collected something
14902 player->is_collecting = !player->is_digging;
14903 player->is_active = TRUE;
14905 player->last_removed_element = element;
14912 static boolean DigFieldByCE(int x, int y, int digging_element)
14914 int element = Tile[x][y];
14916 if (!IS_FREE(x, y))
14918 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14919 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14922 // no element can dig solid indestructible elements
14923 if (IS_INDESTRUCTIBLE(element) &&
14924 !IS_DIGGABLE(element) &&
14925 !IS_COLLECTIBLE(element))
14928 if (AmoebaNr[x][y] &&
14929 (element == EL_AMOEBA_FULL ||
14930 element == EL_BD_AMOEBA ||
14931 element == EL_AMOEBA_GROWING))
14933 AmoebaCnt[AmoebaNr[x][y]]--;
14934 AmoebaCnt2[AmoebaNr[x][y]]--;
14937 if (IS_MOVING(x, y))
14938 RemoveMovingField(x, y);
14942 TEST_DrawLevelField(x, y);
14945 // if digged element was about to explode, prevent the explosion
14946 ExplodeField[x][y] = EX_TYPE_NONE;
14948 PlayLevelSoundAction(x, y, action);
14951 Store[x][y] = EL_EMPTY;
14953 // this makes it possible to leave the removed element again
14954 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14955 Store[x][y] = element;
14960 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14962 int jx = player->jx, jy = player->jy;
14963 int x = jx + dx, y = jy + dy;
14964 int snap_direction = (dx == -1 ? MV_LEFT :
14965 dx == +1 ? MV_RIGHT :
14967 dy == +1 ? MV_DOWN : MV_NONE);
14968 boolean can_continue_snapping = (level.continuous_snapping &&
14969 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14971 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14974 if (!player->active || !IN_LEV_FIELD(x, y))
14982 if (player->MovPos == 0)
14983 player->is_pushing = FALSE;
14985 player->is_snapping = FALSE;
14987 if (player->MovPos == 0)
14989 player->is_moving = FALSE;
14990 player->is_digging = FALSE;
14991 player->is_collecting = FALSE;
14997 // prevent snapping with already pressed snap key when not allowed
14998 if (player->is_snapping && !can_continue_snapping)
15001 player->MovDir = snap_direction;
15003 if (player->MovPos == 0)
15005 player->is_moving = FALSE;
15006 player->is_digging = FALSE;
15007 player->is_collecting = FALSE;
15010 player->is_dropping = FALSE;
15011 player->is_dropping_pressed = FALSE;
15012 player->drop_pressed_delay = 0;
15014 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15017 player->is_snapping = TRUE;
15018 player->is_active = TRUE;
15020 if (player->MovPos == 0)
15022 player->is_moving = FALSE;
15023 player->is_digging = FALSE;
15024 player->is_collecting = FALSE;
15027 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15028 TEST_DrawLevelField(player->last_jx, player->last_jy);
15030 TEST_DrawLevelField(x, y);
15035 static boolean DropElement(struct PlayerInfo *player)
15037 int old_element, new_element;
15038 int dropx = player->jx, dropy = player->jy;
15039 int drop_direction = player->MovDir;
15040 int drop_side = drop_direction;
15041 int drop_element = get_next_dropped_element(player);
15043 /* do not drop an element on top of another element; when holding drop key
15044 pressed without moving, dropped element must move away before the next
15045 element can be dropped (this is especially important if the next element
15046 is dynamite, which can be placed on background for historical reasons) */
15047 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15050 if (IS_THROWABLE(drop_element))
15052 dropx += GET_DX_FROM_DIR(drop_direction);
15053 dropy += GET_DY_FROM_DIR(drop_direction);
15055 if (!IN_LEV_FIELD(dropx, dropy))
15059 old_element = Tile[dropx][dropy]; // old element at dropping position
15060 new_element = drop_element; // default: no change when dropping
15062 // check if player is active, not moving and ready to drop
15063 if (!player->active || player->MovPos || player->drop_delay > 0)
15066 // check if player has anything that can be dropped
15067 if (new_element == EL_UNDEFINED)
15070 // only set if player has anything that can be dropped
15071 player->is_dropping_pressed = TRUE;
15073 // check if drop key was pressed long enough for EM style dynamite
15074 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15077 // check if anything can be dropped at the current position
15078 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15081 // collected custom elements can only be dropped on empty fields
15082 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15085 if (old_element != EL_EMPTY)
15086 Back[dropx][dropy] = old_element; // store old element on this field
15088 ResetGfxAnimation(dropx, dropy);
15089 ResetRandomAnimationValue(dropx, dropy);
15091 if (player->inventory_size > 0 ||
15092 player->inventory_infinite_element != EL_UNDEFINED)
15094 if (player->inventory_size > 0)
15096 player->inventory_size--;
15098 DrawGameDoorValues();
15100 if (new_element == EL_DYNAMITE)
15101 new_element = EL_DYNAMITE_ACTIVE;
15102 else if (new_element == EL_EM_DYNAMITE)
15103 new_element = EL_EM_DYNAMITE_ACTIVE;
15104 else if (new_element == EL_SP_DISK_RED)
15105 new_element = EL_SP_DISK_RED_ACTIVE;
15108 Tile[dropx][dropy] = new_element;
15110 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15111 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15112 el2img(Tile[dropx][dropy]), 0);
15114 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15116 // needed if previous element just changed to "empty" in the last frame
15117 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15119 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15120 player->index_bit, drop_side);
15121 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15123 player->index_bit, drop_side);
15125 TestIfElementTouchesCustomElement(dropx, dropy);
15127 else // player is dropping a dyna bomb
15129 player->dynabombs_left--;
15131 Tile[dropx][dropy] = new_element;
15133 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15134 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15135 el2img(Tile[dropx][dropy]), 0);
15137 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15140 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15141 InitField_WithBug1(dropx, dropy, FALSE);
15143 new_element = Tile[dropx][dropy]; // element might have changed
15145 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15146 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15148 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15149 MovDir[dropx][dropy] = drop_direction;
15151 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15153 // do not cause impact style collision by dropping elements that can fall
15154 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15157 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15158 player->is_dropping = TRUE;
15160 player->drop_pressed_delay = 0;
15161 player->is_dropping_pressed = FALSE;
15163 player->drop_x = dropx;
15164 player->drop_y = dropy;
15169 // ----------------------------------------------------------------------------
15170 // game sound playing functions
15171 // ----------------------------------------------------------------------------
15173 static int *loop_sound_frame = NULL;
15174 static int *loop_sound_volume = NULL;
15176 void InitPlayLevelSound(void)
15178 int num_sounds = getSoundListSize();
15180 checked_free(loop_sound_frame);
15181 checked_free(loop_sound_volume);
15183 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15184 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15187 static void PlayLevelSound(int x, int y, int nr)
15189 int sx = SCREENX(x), sy = SCREENY(y);
15190 int volume, stereo_position;
15191 int max_distance = 8;
15192 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15194 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15195 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15198 if (!IN_LEV_FIELD(x, y) ||
15199 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15200 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15203 volume = SOUND_MAX_VOLUME;
15205 if (!IN_SCR_FIELD(sx, sy))
15207 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15208 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15210 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15213 stereo_position = (SOUND_MAX_LEFT +
15214 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15215 (SCR_FIELDX + 2 * max_distance));
15217 if (IS_LOOP_SOUND(nr))
15219 /* This assures that quieter loop sounds do not overwrite louder ones,
15220 while restarting sound volume comparison with each new game frame. */
15222 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15225 loop_sound_volume[nr] = volume;
15226 loop_sound_frame[nr] = FrameCounter;
15229 PlaySoundExt(nr, volume, stereo_position, type);
15232 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15234 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15235 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15236 y < LEVELY(BY1) ? LEVELY(BY1) :
15237 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15241 static void PlayLevelSoundAction(int x, int y, int action)
15243 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15246 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15248 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15250 if (sound_effect != SND_UNDEFINED)
15251 PlayLevelSound(x, y, sound_effect);
15254 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15257 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15259 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15260 PlayLevelSound(x, y, sound_effect);
15263 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15265 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15267 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15268 PlayLevelSound(x, y, sound_effect);
15271 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15273 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15275 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15276 StopSound(sound_effect);
15279 static int getLevelMusicNr(void)
15281 if (levelset.music[level_nr] != MUS_UNDEFINED)
15282 return levelset.music[level_nr]; // from config file
15284 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15287 static void FadeLevelSounds(void)
15292 static void FadeLevelMusic(void)
15294 int music_nr = getLevelMusicNr();
15295 char *curr_music = getCurrentlyPlayingMusicFilename();
15296 char *next_music = getMusicInfoEntryFilename(music_nr);
15298 if (!strEqual(curr_music, next_music))
15302 void FadeLevelSoundsAndMusic(void)
15308 static void PlayLevelMusic(void)
15310 int music_nr = getLevelMusicNr();
15311 char *curr_music = getCurrentlyPlayingMusicFilename();
15312 char *next_music = getMusicInfoEntryFilename(music_nr);
15314 if (!strEqual(curr_music, next_music))
15315 PlayMusicLoop(music_nr);
15318 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15320 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15322 int x = xx - offset;
15323 int y = yy - offset;
15328 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15332 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15336 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15340 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15344 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15348 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15352 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15355 case SOUND_android_clone:
15356 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15359 case SOUND_android_move:
15360 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15364 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15368 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15372 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15375 case SOUND_eater_eat:
15376 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15380 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15383 case SOUND_collect:
15384 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15387 case SOUND_diamond:
15388 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15392 // !!! CHECK THIS !!!
15394 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15396 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15400 case SOUND_wonderfall:
15401 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15405 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15409 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15413 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15417 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15421 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15425 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15429 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15433 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15436 case SOUND_exit_open:
15437 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15440 case SOUND_exit_leave:
15441 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15444 case SOUND_dynamite:
15445 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15449 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15453 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15457 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15461 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15465 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15469 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15473 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15478 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15480 int element = map_element_SP_to_RND(element_sp);
15481 int action = map_action_SP_to_RND(action_sp);
15482 int offset = (setup.sp_show_border_elements ? 0 : 1);
15483 int x = xx - offset;
15484 int y = yy - offset;
15486 PlayLevelSoundElementAction(x, y, element, action);
15489 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15491 int element = map_element_MM_to_RND(element_mm);
15492 int action = map_action_MM_to_RND(action_mm);
15494 int x = xx - offset;
15495 int y = yy - offset;
15497 if (!IS_MM_ELEMENT(element))
15498 element = EL_MM_DEFAULT;
15500 PlayLevelSoundElementAction(x, y, element, action);
15503 void PlaySound_MM(int sound_mm)
15505 int sound = map_sound_MM_to_RND(sound_mm);
15507 if (sound == SND_UNDEFINED)
15513 void PlaySoundLoop_MM(int sound_mm)
15515 int sound = map_sound_MM_to_RND(sound_mm);
15517 if (sound == SND_UNDEFINED)
15520 PlaySoundLoop(sound);
15523 void StopSound_MM(int sound_mm)
15525 int sound = map_sound_MM_to_RND(sound_mm);
15527 if (sound == SND_UNDEFINED)
15533 void RaiseScore(int value)
15535 game.score += value;
15537 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15539 DisplayGameControlValues();
15542 void RaiseScoreElement(int element)
15547 case EL_BD_DIAMOND:
15548 case EL_EMERALD_YELLOW:
15549 case EL_EMERALD_RED:
15550 case EL_EMERALD_PURPLE:
15551 case EL_SP_INFOTRON:
15552 RaiseScore(level.score[SC_EMERALD]);
15555 RaiseScore(level.score[SC_DIAMOND]);
15558 RaiseScore(level.score[SC_CRYSTAL]);
15561 RaiseScore(level.score[SC_PEARL]);
15564 case EL_BD_BUTTERFLY:
15565 case EL_SP_ELECTRON:
15566 RaiseScore(level.score[SC_BUG]);
15569 case EL_BD_FIREFLY:
15570 case EL_SP_SNIKSNAK:
15571 RaiseScore(level.score[SC_SPACESHIP]);
15574 case EL_DARK_YAMYAM:
15575 RaiseScore(level.score[SC_YAMYAM]);
15578 RaiseScore(level.score[SC_ROBOT]);
15581 RaiseScore(level.score[SC_PACMAN]);
15584 RaiseScore(level.score[SC_NUT]);
15587 case EL_EM_DYNAMITE:
15588 case EL_SP_DISK_RED:
15589 case EL_DYNABOMB_INCREASE_NUMBER:
15590 case EL_DYNABOMB_INCREASE_SIZE:
15591 case EL_DYNABOMB_INCREASE_POWER:
15592 RaiseScore(level.score[SC_DYNAMITE]);
15594 case EL_SHIELD_NORMAL:
15595 case EL_SHIELD_DEADLY:
15596 RaiseScore(level.score[SC_SHIELD]);
15598 case EL_EXTRA_TIME:
15599 RaiseScore(level.extra_time_score);
15613 case EL_DC_KEY_WHITE:
15614 RaiseScore(level.score[SC_KEY]);
15617 RaiseScore(element_info[element].collect_score);
15622 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15624 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15628 // prevent short reactivation of overlay buttons while closing door
15629 SetOverlayActive(FALSE);
15631 // door may still be open due to skipped or envelope style request
15632 CloseDoor(DOOR_CLOSE_1);
15635 if (network.enabled)
15636 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15640 FadeSkipNextFadeIn();
15642 SetGameStatus(GAME_MODE_MAIN);
15647 else // continue playing the game
15649 if (tape.playing && tape.deactivate_display)
15650 TapeDeactivateDisplayOff(TRUE);
15652 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15654 if (tape.playing && tape.deactivate_display)
15655 TapeDeactivateDisplayOn();
15659 void RequestQuitGame(boolean escape_key_pressed)
15661 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15662 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15663 level_editor_test_game);
15664 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15667 RequestQuitGameExt(skip_request, quick_quit,
15668 "Do you really want to quit the game?");
15671 void RequestRestartGame(char *message)
15673 game.restart_game_message = NULL;
15675 boolean has_started_game = hasStartedNetworkGame();
15676 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15678 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15680 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15684 // needed in case of envelope request to close game panel
15685 CloseDoor(DOOR_CLOSE_1);
15687 SetGameStatus(GAME_MODE_MAIN);
15693 void CheckGameOver(void)
15695 static boolean last_game_over = FALSE;
15696 static int game_over_delay = 0;
15697 int game_over_delay_value = 50;
15698 boolean game_over = checkGameFailed();
15700 // do not handle game over if request dialog is already active
15701 if (game.request_active)
15704 // do not ask to play again if game was never actually played
15705 if (!game.GamePlayed)
15710 last_game_over = FALSE;
15711 game_over_delay = game_over_delay_value;
15716 if (game_over_delay > 0)
15723 if (last_game_over != game_over)
15724 game.restart_game_message = (hasStartedNetworkGame() ?
15725 "Game over! Play it again?" :
15728 last_game_over = game_over;
15731 boolean checkGameSolved(void)
15733 // set for all game engines if level was solved
15734 return game.LevelSolved_GameEnd;
15737 boolean checkGameFailed(void)
15739 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15740 return (game_em.game_over && !game_em.level_solved);
15741 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15742 return (game_sp.game_over && !game_sp.level_solved);
15743 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15744 return (game_mm.game_over && !game_mm.level_solved);
15745 else // GAME_ENGINE_TYPE_RND
15746 return (game.GameOver && !game.LevelSolved);
15749 boolean checkGameEnded(void)
15751 return (checkGameSolved() || checkGameFailed());
15755 // ----------------------------------------------------------------------------
15756 // random generator functions
15757 // ----------------------------------------------------------------------------
15759 unsigned int InitEngineRandom_RND(int seed)
15761 game.num_random_calls = 0;
15763 return InitEngineRandom(seed);
15766 unsigned int RND(int max)
15770 game.num_random_calls++;
15772 return GetEngineRandom(max);
15779 // ----------------------------------------------------------------------------
15780 // game engine snapshot handling functions
15781 // ----------------------------------------------------------------------------
15783 struct EngineSnapshotInfo
15785 // runtime values for custom element collect score
15786 int collect_score[NUM_CUSTOM_ELEMENTS];
15788 // runtime values for group element choice position
15789 int choice_pos[NUM_GROUP_ELEMENTS];
15791 // runtime values for belt position animations
15792 int belt_graphic[4][NUM_BELT_PARTS];
15793 int belt_anim_mode[4][NUM_BELT_PARTS];
15796 static struct EngineSnapshotInfo engine_snapshot_rnd;
15797 static char *snapshot_level_identifier = NULL;
15798 static int snapshot_level_nr = -1;
15800 static void SaveEngineSnapshotValues_RND(void)
15802 static int belt_base_active_element[4] =
15804 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15805 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15806 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15807 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15811 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15813 int element = EL_CUSTOM_START + i;
15815 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15818 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15820 int element = EL_GROUP_START + i;
15822 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15825 for (i = 0; i < 4; i++)
15827 for (j = 0; j < NUM_BELT_PARTS; j++)
15829 int element = belt_base_active_element[i] + j;
15830 int graphic = el2img(element);
15831 int anim_mode = graphic_info[graphic].anim_mode;
15833 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15834 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15839 static void LoadEngineSnapshotValues_RND(void)
15841 unsigned int num_random_calls = game.num_random_calls;
15844 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15846 int element = EL_CUSTOM_START + i;
15848 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15851 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15853 int element = EL_GROUP_START + i;
15855 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15858 for (i = 0; i < 4; i++)
15860 for (j = 0; j < NUM_BELT_PARTS; j++)
15862 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15863 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15865 graphic_info[graphic].anim_mode = anim_mode;
15869 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15871 InitRND(tape.random_seed);
15872 for (i = 0; i < num_random_calls; i++)
15876 if (game.num_random_calls != num_random_calls)
15878 Error("number of random calls out of sync");
15879 Error("number of random calls should be %d", num_random_calls);
15880 Error("number of random calls is %d", game.num_random_calls);
15882 Fail("this should not happen -- please debug");
15886 void FreeEngineSnapshotSingle(void)
15888 FreeSnapshotSingle();
15890 setString(&snapshot_level_identifier, NULL);
15891 snapshot_level_nr = -1;
15894 void FreeEngineSnapshotList(void)
15896 FreeSnapshotList();
15899 static ListNode *SaveEngineSnapshotBuffers(void)
15901 ListNode *buffers = NULL;
15903 // copy some special values to a structure better suited for the snapshot
15905 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15906 SaveEngineSnapshotValues_RND();
15907 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15908 SaveEngineSnapshotValues_EM();
15909 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15910 SaveEngineSnapshotValues_SP(&buffers);
15911 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15912 SaveEngineSnapshotValues_MM(&buffers);
15914 // save values stored in special snapshot structure
15916 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15917 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15918 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15919 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15920 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15921 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15922 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15923 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15925 // save further RND engine values
15927 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15928 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15929 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15931 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15932 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15933 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15934 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15935 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15937 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15938 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15939 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15941 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15943 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15944 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15946 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15947 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15948 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15949 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15950 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15951 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15952 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15953 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15954 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15955 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15956 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15957 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15958 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15959 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15960 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15961 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15962 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15963 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15965 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15966 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15968 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15969 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15970 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15972 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15973 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15975 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15976 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15977 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
15978 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15979 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15980 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15982 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15983 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15986 ListNode *node = engine_snapshot_list_rnd;
15989 while (node != NULL)
15991 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15996 Debug("game:playing:SaveEngineSnapshotBuffers",
15997 "size of engine snapshot: %d bytes", num_bytes);
16003 void SaveEngineSnapshotSingle(void)
16005 ListNode *buffers = SaveEngineSnapshotBuffers();
16007 // finally save all snapshot buffers to single snapshot
16008 SaveSnapshotSingle(buffers);
16010 // save level identification information
16011 setString(&snapshot_level_identifier, leveldir_current->identifier);
16012 snapshot_level_nr = level_nr;
16015 boolean CheckSaveEngineSnapshotToList(void)
16017 boolean save_snapshot =
16018 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16019 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16020 game.snapshot.changed_action) ||
16021 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16022 game.snapshot.collected_item));
16024 game.snapshot.changed_action = FALSE;
16025 game.snapshot.collected_item = FALSE;
16026 game.snapshot.save_snapshot = save_snapshot;
16028 return save_snapshot;
16031 void SaveEngineSnapshotToList(void)
16033 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16037 ListNode *buffers = SaveEngineSnapshotBuffers();
16039 // finally save all snapshot buffers to snapshot list
16040 SaveSnapshotToList(buffers);
16043 void SaveEngineSnapshotToListInitial(void)
16045 FreeEngineSnapshotList();
16047 SaveEngineSnapshotToList();
16050 static void LoadEngineSnapshotValues(void)
16052 // restore special values from snapshot structure
16054 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16055 LoadEngineSnapshotValues_RND();
16056 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16057 LoadEngineSnapshotValues_EM();
16058 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16059 LoadEngineSnapshotValues_SP();
16060 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16061 LoadEngineSnapshotValues_MM();
16064 void LoadEngineSnapshotSingle(void)
16066 LoadSnapshotSingle();
16068 LoadEngineSnapshotValues();
16071 static void LoadEngineSnapshot_Undo(int steps)
16073 LoadSnapshotFromList_Older(steps);
16075 LoadEngineSnapshotValues();
16078 static void LoadEngineSnapshot_Redo(int steps)
16080 LoadSnapshotFromList_Newer(steps);
16082 LoadEngineSnapshotValues();
16085 boolean CheckEngineSnapshotSingle(void)
16087 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16088 snapshot_level_nr == level_nr);
16091 boolean CheckEngineSnapshotList(void)
16093 return CheckSnapshotList();
16097 // ---------- new game button stuff -------------------------------------------
16104 boolean *setup_value;
16105 boolean allowed_on_tape;
16106 boolean is_touch_button;
16108 } gamebutton_info[NUM_GAME_BUTTONS] =
16111 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16112 GAME_CTRL_ID_STOP, NULL,
16113 TRUE, FALSE, "stop game"
16116 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16117 GAME_CTRL_ID_PAUSE, NULL,
16118 TRUE, FALSE, "pause game"
16121 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16122 GAME_CTRL_ID_PLAY, NULL,
16123 TRUE, FALSE, "play game"
16126 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16127 GAME_CTRL_ID_UNDO, NULL,
16128 TRUE, FALSE, "undo step"
16131 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16132 GAME_CTRL_ID_REDO, NULL,
16133 TRUE, FALSE, "redo step"
16136 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16137 GAME_CTRL_ID_SAVE, NULL,
16138 TRUE, FALSE, "save game"
16141 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16142 GAME_CTRL_ID_PAUSE2, NULL,
16143 TRUE, FALSE, "pause game"
16146 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16147 GAME_CTRL_ID_LOAD, NULL,
16148 TRUE, FALSE, "load game"
16151 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16152 GAME_CTRL_ID_PANEL_STOP, NULL,
16153 FALSE, FALSE, "stop game"
16156 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16157 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16158 FALSE, FALSE, "pause game"
16161 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16162 GAME_CTRL_ID_PANEL_PLAY, NULL,
16163 FALSE, FALSE, "play game"
16166 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16167 GAME_CTRL_ID_TOUCH_STOP, NULL,
16168 FALSE, TRUE, "stop game"
16171 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16172 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16173 FALSE, TRUE, "pause game"
16176 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16177 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16178 TRUE, FALSE, "background music on/off"
16181 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16182 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16183 TRUE, FALSE, "sound loops on/off"
16186 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16187 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16188 TRUE, FALSE, "normal sounds on/off"
16191 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16192 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16193 FALSE, FALSE, "background music on/off"
16196 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16197 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16198 FALSE, FALSE, "sound loops on/off"
16201 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16202 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16203 FALSE, FALSE, "normal sounds on/off"
16207 void CreateGameButtons(void)
16211 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16213 int graphic = gamebutton_info[i].graphic;
16214 struct GraphicInfo *gfx = &graphic_info[graphic];
16215 struct XY *pos = gamebutton_info[i].pos;
16216 struct GadgetInfo *gi;
16219 unsigned int event_mask;
16220 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16221 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16222 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16223 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16224 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16225 int gd_x = gfx->src_x;
16226 int gd_y = gfx->src_y;
16227 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16228 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16229 int gd_xa = gfx->src_x + gfx->active_xoffset;
16230 int gd_ya = gfx->src_y + gfx->active_yoffset;
16231 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16232 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16233 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16234 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16237 if (gfx->bitmap == NULL)
16239 game_gadget[id] = NULL;
16244 if (id == GAME_CTRL_ID_STOP ||
16245 id == GAME_CTRL_ID_PANEL_STOP ||
16246 id == GAME_CTRL_ID_TOUCH_STOP ||
16247 id == GAME_CTRL_ID_PLAY ||
16248 id == GAME_CTRL_ID_PANEL_PLAY ||
16249 id == GAME_CTRL_ID_SAVE ||
16250 id == GAME_CTRL_ID_LOAD)
16252 button_type = GD_TYPE_NORMAL_BUTTON;
16254 event_mask = GD_EVENT_RELEASED;
16256 else if (id == GAME_CTRL_ID_UNDO ||
16257 id == GAME_CTRL_ID_REDO)
16259 button_type = GD_TYPE_NORMAL_BUTTON;
16261 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16265 button_type = GD_TYPE_CHECK_BUTTON;
16266 checked = (gamebutton_info[i].setup_value != NULL ?
16267 *gamebutton_info[i].setup_value : FALSE);
16268 event_mask = GD_EVENT_PRESSED;
16271 gi = CreateGadget(GDI_CUSTOM_ID, id,
16272 GDI_IMAGE_ID, graphic,
16273 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16276 GDI_WIDTH, gfx->width,
16277 GDI_HEIGHT, gfx->height,
16278 GDI_TYPE, button_type,
16279 GDI_STATE, GD_BUTTON_UNPRESSED,
16280 GDI_CHECKED, checked,
16281 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16282 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16283 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16284 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16285 GDI_DIRECT_DRAW, FALSE,
16286 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16287 GDI_EVENT_MASK, event_mask,
16288 GDI_CALLBACK_ACTION, HandleGameButtons,
16292 Fail("cannot create gadget");
16294 game_gadget[id] = gi;
16298 void FreeGameButtons(void)
16302 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16303 FreeGadget(game_gadget[i]);
16306 static void UnmapGameButtonsAtSamePosition(int id)
16310 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16312 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16313 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16314 UnmapGadget(game_gadget[i]);
16317 static void UnmapGameButtonsAtSamePosition_All(void)
16319 if (setup.show_load_save_buttons)
16321 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16322 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16323 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16325 else if (setup.show_undo_redo_buttons)
16327 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16328 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16329 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16333 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16334 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16335 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16337 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16338 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16339 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16343 void MapLoadSaveButtons(void)
16345 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16346 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16348 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16349 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16352 void MapUndoRedoButtons(void)
16354 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16355 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16357 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16358 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16361 void ModifyPauseButtons(void)
16365 GAME_CTRL_ID_PAUSE,
16366 GAME_CTRL_ID_PAUSE2,
16367 GAME_CTRL_ID_PANEL_PAUSE,
16368 GAME_CTRL_ID_TOUCH_PAUSE,
16373 for (i = 0; ids[i] > -1; i++)
16374 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16377 static void MapGameButtonsExt(boolean on_tape)
16381 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16382 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16383 MapGadget(game_gadget[i]);
16385 UnmapGameButtonsAtSamePosition_All();
16387 RedrawGameButtons();
16390 static void UnmapGameButtonsExt(boolean on_tape)
16394 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16395 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16396 UnmapGadget(game_gadget[i]);
16399 static void RedrawGameButtonsExt(boolean on_tape)
16403 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16404 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16405 RedrawGadget(game_gadget[i]);
16408 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16413 gi->checked = state;
16416 static void RedrawSoundButtonGadget(int id)
16418 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16419 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16420 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16421 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16422 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16423 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16426 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16427 RedrawGadget(game_gadget[id2]);
16430 void MapGameButtons(void)
16432 MapGameButtonsExt(FALSE);
16435 void UnmapGameButtons(void)
16437 UnmapGameButtonsExt(FALSE);
16440 void RedrawGameButtons(void)
16442 RedrawGameButtonsExt(FALSE);
16445 void MapGameButtonsOnTape(void)
16447 MapGameButtonsExt(TRUE);
16450 void UnmapGameButtonsOnTape(void)
16452 UnmapGameButtonsExt(TRUE);
16455 void RedrawGameButtonsOnTape(void)
16457 RedrawGameButtonsExt(TRUE);
16460 static void GameUndoRedoExt(void)
16462 ClearPlayerAction();
16464 tape.pausing = TRUE;
16467 UpdateAndDisplayGameControlValues();
16469 DrawCompleteVideoDisplay();
16470 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16471 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16472 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16474 ModifyPauseButtons();
16479 static void GameUndo(int steps)
16481 if (!CheckEngineSnapshotList())
16484 int tape_property_bits = tape.property_bits;
16486 LoadEngineSnapshot_Undo(steps);
16488 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16493 static void GameRedo(int steps)
16495 if (!CheckEngineSnapshotList())
16498 int tape_property_bits = tape.property_bits;
16500 LoadEngineSnapshot_Redo(steps);
16502 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16507 static void HandleGameButtonsExt(int id, int button)
16509 static boolean game_undo_executed = FALSE;
16510 int steps = BUTTON_STEPSIZE(button);
16511 boolean handle_game_buttons =
16512 (game_status == GAME_MODE_PLAYING ||
16513 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16515 if (!handle_game_buttons)
16520 case GAME_CTRL_ID_STOP:
16521 case GAME_CTRL_ID_PANEL_STOP:
16522 case GAME_CTRL_ID_TOUCH_STOP:
16523 if (game_status == GAME_MODE_MAIN)
16529 RequestQuitGame(FALSE);
16533 case GAME_CTRL_ID_PAUSE:
16534 case GAME_CTRL_ID_PAUSE2:
16535 case GAME_CTRL_ID_PANEL_PAUSE:
16536 case GAME_CTRL_ID_TOUCH_PAUSE:
16537 if (network.enabled && game_status == GAME_MODE_PLAYING)
16540 SendToServer_ContinuePlaying();
16542 SendToServer_PausePlaying();
16545 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16547 game_undo_executed = FALSE;
16551 case GAME_CTRL_ID_PLAY:
16552 case GAME_CTRL_ID_PANEL_PLAY:
16553 if (game_status == GAME_MODE_MAIN)
16555 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16557 else if (tape.pausing)
16559 if (network.enabled)
16560 SendToServer_ContinuePlaying();
16562 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16566 case GAME_CTRL_ID_UNDO:
16567 // Important: When using "save snapshot when collecting an item" mode,
16568 // load last (current) snapshot for first "undo" after pressing "pause"
16569 // (else the last-but-one snapshot would be loaded, because the snapshot
16570 // pointer already points to the last snapshot when pressing "pause",
16571 // which is fine for "every step/move" mode, but not for "every collect")
16572 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16573 !game_undo_executed)
16576 game_undo_executed = TRUE;
16581 case GAME_CTRL_ID_REDO:
16585 case GAME_CTRL_ID_SAVE:
16589 case GAME_CTRL_ID_LOAD:
16593 case SOUND_CTRL_ID_MUSIC:
16594 case SOUND_CTRL_ID_PANEL_MUSIC:
16595 if (setup.sound_music)
16597 setup.sound_music = FALSE;
16601 else if (audio.music_available)
16603 setup.sound = setup.sound_music = TRUE;
16605 SetAudioMode(setup.sound);
16607 if (game_status == GAME_MODE_PLAYING)
16611 RedrawSoundButtonGadget(id);
16615 case SOUND_CTRL_ID_LOOPS:
16616 case SOUND_CTRL_ID_PANEL_LOOPS:
16617 if (setup.sound_loops)
16618 setup.sound_loops = FALSE;
16619 else if (audio.loops_available)
16621 setup.sound = setup.sound_loops = TRUE;
16623 SetAudioMode(setup.sound);
16626 RedrawSoundButtonGadget(id);
16630 case SOUND_CTRL_ID_SIMPLE:
16631 case SOUND_CTRL_ID_PANEL_SIMPLE:
16632 if (setup.sound_simple)
16633 setup.sound_simple = FALSE;
16634 else if (audio.sound_available)
16636 setup.sound = setup.sound_simple = TRUE;
16638 SetAudioMode(setup.sound);
16641 RedrawSoundButtonGadget(id);
16650 static void HandleGameButtons(struct GadgetInfo *gi)
16652 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16655 void HandleSoundButtonKeys(Key key)
16657 if (key == setup.shortcut.sound_simple)
16658 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16659 else if (key == setup.shortcut.sound_loops)
16660 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16661 else if (key == setup.shortcut.sound_music)
16662 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);