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_GEMS_TOTAL 2
93 #define GAME_PANEL_GEMS_COLLECTED 3
94 #define GAME_PANEL_GEMS_SCORE 4
95 #define GAME_PANEL_INVENTORY_COUNT 5
96 #define GAME_PANEL_INVENTORY_FIRST_1 6
97 #define GAME_PANEL_INVENTORY_FIRST_2 7
98 #define GAME_PANEL_INVENTORY_FIRST_3 8
99 #define GAME_PANEL_INVENTORY_FIRST_4 9
100 #define GAME_PANEL_INVENTORY_FIRST_5 10
101 #define GAME_PANEL_INVENTORY_FIRST_6 11
102 #define GAME_PANEL_INVENTORY_FIRST_7 12
103 #define GAME_PANEL_INVENTORY_FIRST_8 13
104 #define GAME_PANEL_INVENTORY_LAST_1 14
105 #define GAME_PANEL_INVENTORY_LAST_2 15
106 #define GAME_PANEL_INVENTORY_LAST_3 16
107 #define GAME_PANEL_INVENTORY_LAST_4 17
108 #define GAME_PANEL_INVENTORY_LAST_5 18
109 #define GAME_PANEL_INVENTORY_LAST_6 19
110 #define GAME_PANEL_INVENTORY_LAST_7 20
111 #define GAME_PANEL_INVENTORY_LAST_8 21
112 #define GAME_PANEL_KEY_1 22
113 #define GAME_PANEL_KEY_2 23
114 #define GAME_PANEL_KEY_3 24
115 #define GAME_PANEL_KEY_4 25
116 #define GAME_PANEL_KEY_5 26
117 #define GAME_PANEL_KEY_6 27
118 #define GAME_PANEL_KEY_7 28
119 #define GAME_PANEL_KEY_8 29
120 #define GAME_PANEL_KEY_WHITE 30
121 #define GAME_PANEL_KEY_WHITE_COUNT 31
122 #define GAME_PANEL_SCORE 32
123 #define GAME_PANEL_HIGHSCORE 33
124 #define GAME_PANEL_TIME 34
125 #define GAME_PANEL_TIME_HH 35
126 #define GAME_PANEL_TIME_MM 36
127 #define GAME_PANEL_TIME_SS 37
128 #define GAME_PANEL_TIME_ANIM 38
129 #define GAME_PANEL_HEALTH 39
130 #define GAME_PANEL_HEALTH_ANIM 40
131 #define GAME_PANEL_FRAME 41
132 #define GAME_PANEL_SHIELD_NORMAL 42
133 #define GAME_PANEL_SHIELD_NORMAL_TIME 43
134 #define GAME_PANEL_SHIELD_DEADLY 44
135 #define GAME_PANEL_SHIELD_DEADLY_TIME 45
136 #define GAME_PANEL_EXIT 46
137 #define GAME_PANEL_EMC_MAGIC_BALL 47
138 #define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 48
139 #define GAME_PANEL_LIGHT_SWITCH 49
140 #define GAME_PANEL_LIGHT_SWITCH_TIME 50
141 #define GAME_PANEL_TIMEGATE_SWITCH 51
142 #define GAME_PANEL_TIMEGATE_SWITCH_TIME 52
143 #define GAME_PANEL_SWITCHGATE_SWITCH 53
144 #define GAME_PANEL_EMC_LENSES 54
145 #define GAME_PANEL_EMC_LENSES_TIME 55
146 #define GAME_PANEL_EMC_MAGNIFIER 56
147 #define GAME_PANEL_EMC_MAGNIFIER_TIME 57
148 #define GAME_PANEL_BALLOON_SWITCH 58
149 #define GAME_PANEL_DYNABOMB_NUMBER 59
150 #define GAME_PANEL_DYNABOMB_SIZE 60
151 #define GAME_PANEL_DYNABOMB_POWER 61
152 #define GAME_PANEL_PENGUINS 62
153 #define GAME_PANEL_SOKOBAN_OBJECTS 63
154 #define GAME_PANEL_SOKOBAN_FIELDS 64
155 #define GAME_PANEL_ROBOT_WHEEL 65
156 #define GAME_PANEL_CONVEYOR_BELT_1 66
157 #define GAME_PANEL_CONVEYOR_BELT_2 67
158 #define GAME_PANEL_CONVEYOR_BELT_3 68
159 #define GAME_PANEL_CONVEYOR_BELT_4 69
160 #define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 70
161 #define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 71
162 #define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 72
163 #define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 73
164 #define GAME_PANEL_MAGIC_WALL 74
165 #define GAME_PANEL_MAGIC_WALL_TIME 75
166 #define GAME_PANEL_GRAVITY_STATE 76
167 #define GAME_PANEL_GRAPHIC_1 77
168 #define GAME_PANEL_GRAPHIC_2 78
169 #define GAME_PANEL_GRAPHIC_3 79
170 #define GAME_PANEL_GRAPHIC_4 80
171 #define GAME_PANEL_GRAPHIC_5 81
172 #define GAME_PANEL_GRAPHIC_6 82
173 #define GAME_PANEL_GRAPHIC_7 83
174 #define GAME_PANEL_GRAPHIC_8 84
175 #define GAME_PANEL_ELEMENT_1 85
176 #define GAME_PANEL_ELEMENT_2 86
177 #define GAME_PANEL_ELEMENT_3 87
178 #define GAME_PANEL_ELEMENT_4 88
179 #define GAME_PANEL_ELEMENT_5 89
180 #define GAME_PANEL_ELEMENT_6 90
181 #define GAME_PANEL_ELEMENT_7 91
182 #define GAME_PANEL_ELEMENT_8 92
183 #define GAME_PANEL_ELEMENT_COUNT_1 93
184 #define GAME_PANEL_ELEMENT_COUNT_2 94
185 #define GAME_PANEL_ELEMENT_COUNT_3 95
186 #define GAME_PANEL_ELEMENT_COUNT_4 96
187 #define GAME_PANEL_ELEMENT_COUNT_5 97
188 #define GAME_PANEL_ELEMENT_COUNT_6 98
189 #define GAME_PANEL_ELEMENT_COUNT_7 99
190 #define GAME_PANEL_ELEMENT_COUNT_8 100
191 #define GAME_PANEL_CE_SCORE_1 101
192 #define GAME_PANEL_CE_SCORE_2 102
193 #define GAME_PANEL_CE_SCORE_3 103
194 #define GAME_PANEL_CE_SCORE_4 104
195 #define GAME_PANEL_CE_SCORE_5 105
196 #define GAME_PANEL_CE_SCORE_6 106
197 #define GAME_PANEL_CE_SCORE_7 107
198 #define GAME_PANEL_CE_SCORE_8 108
199 #define GAME_PANEL_CE_SCORE_1_ELEMENT 109
200 #define GAME_PANEL_CE_SCORE_2_ELEMENT 110
201 #define GAME_PANEL_CE_SCORE_3_ELEMENT 111
202 #define GAME_PANEL_CE_SCORE_4_ELEMENT 112
203 #define GAME_PANEL_CE_SCORE_5_ELEMENT 113
204 #define GAME_PANEL_CE_SCORE_6_ELEMENT 114
205 #define GAME_PANEL_CE_SCORE_7_ELEMENT 115
206 #define GAME_PANEL_CE_SCORE_8_ELEMENT 116
207 #define GAME_PANEL_PLAYER_NAME 117
208 #define GAME_PANEL_LEVEL_NAME 118
209 #define GAME_PANEL_LEVEL_AUTHOR 119
211 #define NUM_GAME_PANEL_CONTROLS 120
213 struct GamePanelOrderInfo
219 static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
221 struct GamePanelControlInfo
225 struct TextPosInfo *pos;
228 int graphic, graphic_active;
230 int value, last_value;
231 int frame, last_frame;
236 static struct GamePanelControlInfo game_panel_controls[] =
239 GAME_PANEL_LEVEL_NUMBER,
240 &game.panel.level_number,
249 GAME_PANEL_GEMS_TOTAL,
250 &game.panel.gems_total,
254 GAME_PANEL_GEMS_COLLECTED,
255 &game.panel.gems_collected,
259 GAME_PANEL_GEMS_SCORE,
260 &game.panel.gems_score,
264 GAME_PANEL_INVENTORY_COUNT,
265 &game.panel.inventory_count,
269 GAME_PANEL_INVENTORY_FIRST_1,
270 &game.panel.inventory_first[0],
274 GAME_PANEL_INVENTORY_FIRST_2,
275 &game.panel.inventory_first[1],
279 GAME_PANEL_INVENTORY_FIRST_3,
280 &game.panel.inventory_first[2],
284 GAME_PANEL_INVENTORY_FIRST_4,
285 &game.panel.inventory_first[3],
289 GAME_PANEL_INVENTORY_FIRST_5,
290 &game.panel.inventory_first[4],
294 GAME_PANEL_INVENTORY_FIRST_6,
295 &game.panel.inventory_first[5],
299 GAME_PANEL_INVENTORY_FIRST_7,
300 &game.panel.inventory_first[6],
304 GAME_PANEL_INVENTORY_FIRST_8,
305 &game.panel.inventory_first[7],
309 GAME_PANEL_INVENTORY_LAST_1,
310 &game.panel.inventory_last[0],
314 GAME_PANEL_INVENTORY_LAST_2,
315 &game.panel.inventory_last[1],
319 GAME_PANEL_INVENTORY_LAST_3,
320 &game.panel.inventory_last[2],
324 GAME_PANEL_INVENTORY_LAST_4,
325 &game.panel.inventory_last[3],
329 GAME_PANEL_INVENTORY_LAST_5,
330 &game.panel.inventory_last[4],
334 GAME_PANEL_INVENTORY_LAST_6,
335 &game.panel.inventory_last[5],
339 GAME_PANEL_INVENTORY_LAST_7,
340 &game.panel.inventory_last[6],
344 GAME_PANEL_INVENTORY_LAST_8,
345 &game.panel.inventory_last[7],
389 GAME_PANEL_KEY_WHITE,
390 &game.panel.key_white,
394 GAME_PANEL_KEY_WHITE_COUNT,
395 &game.panel.key_white_count,
404 GAME_PANEL_HIGHSCORE,
405 &game.panel.highscore,
429 GAME_PANEL_TIME_ANIM,
430 &game.panel.time_anim,
433 IMG_GFX_GAME_PANEL_TIME_ANIM,
434 IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
442 GAME_PANEL_HEALTH_ANIM,
443 &game.panel.health_anim,
446 IMG_GFX_GAME_PANEL_HEALTH_ANIM,
447 IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
455 GAME_PANEL_SHIELD_NORMAL,
456 &game.panel.shield_normal,
460 GAME_PANEL_SHIELD_NORMAL_TIME,
461 &game.panel.shield_normal_time,
465 GAME_PANEL_SHIELD_DEADLY,
466 &game.panel.shield_deadly,
470 GAME_PANEL_SHIELD_DEADLY_TIME,
471 &game.panel.shield_deadly_time,
480 GAME_PANEL_EMC_MAGIC_BALL,
481 &game.panel.emc_magic_ball,
485 GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
486 &game.panel.emc_magic_ball_switch,
490 GAME_PANEL_LIGHT_SWITCH,
491 &game.panel.light_switch,
495 GAME_PANEL_LIGHT_SWITCH_TIME,
496 &game.panel.light_switch_time,
500 GAME_PANEL_TIMEGATE_SWITCH,
501 &game.panel.timegate_switch,
505 GAME_PANEL_TIMEGATE_SWITCH_TIME,
506 &game.panel.timegate_switch_time,
510 GAME_PANEL_SWITCHGATE_SWITCH,
511 &game.panel.switchgate_switch,
515 GAME_PANEL_EMC_LENSES,
516 &game.panel.emc_lenses,
520 GAME_PANEL_EMC_LENSES_TIME,
521 &game.panel.emc_lenses_time,
525 GAME_PANEL_EMC_MAGNIFIER,
526 &game.panel.emc_magnifier,
530 GAME_PANEL_EMC_MAGNIFIER_TIME,
531 &game.panel.emc_magnifier_time,
535 GAME_PANEL_BALLOON_SWITCH,
536 &game.panel.balloon_switch,
540 GAME_PANEL_DYNABOMB_NUMBER,
541 &game.panel.dynabomb_number,
545 GAME_PANEL_DYNABOMB_SIZE,
546 &game.panel.dynabomb_size,
550 GAME_PANEL_DYNABOMB_POWER,
551 &game.panel.dynabomb_power,
556 &game.panel.penguins,
560 GAME_PANEL_SOKOBAN_OBJECTS,
561 &game.panel.sokoban_objects,
565 GAME_PANEL_SOKOBAN_FIELDS,
566 &game.panel.sokoban_fields,
570 GAME_PANEL_ROBOT_WHEEL,
571 &game.panel.robot_wheel,
575 GAME_PANEL_CONVEYOR_BELT_1,
576 &game.panel.conveyor_belt[0],
580 GAME_PANEL_CONVEYOR_BELT_2,
581 &game.panel.conveyor_belt[1],
585 GAME_PANEL_CONVEYOR_BELT_3,
586 &game.panel.conveyor_belt[2],
590 GAME_PANEL_CONVEYOR_BELT_4,
591 &game.panel.conveyor_belt[3],
595 GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
596 &game.panel.conveyor_belt_switch[0],
600 GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
601 &game.panel.conveyor_belt_switch[1],
605 GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
606 &game.panel.conveyor_belt_switch[2],
610 GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
611 &game.panel.conveyor_belt_switch[3],
615 GAME_PANEL_MAGIC_WALL,
616 &game.panel.magic_wall,
620 GAME_PANEL_MAGIC_WALL_TIME,
621 &game.panel.magic_wall_time,
625 GAME_PANEL_GRAVITY_STATE,
626 &game.panel.gravity_state,
630 GAME_PANEL_GRAPHIC_1,
631 &game.panel.graphic[0],
635 GAME_PANEL_GRAPHIC_2,
636 &game.panel.graphic[1],
640 GAME_PANEL_GRAPHIC_3,
641 &game.panel.graphic[2],
645 GAME_PANEL_GRAPHIC_4,
646 &game.panel.graphic[3],
650 GAME_PANEL_GRAPHIC_5,
651 &game.panel.graphic[4],
655 GAME_PANEL_GRAPHIC_6,
656 &game.panel.graphic[5],
660 GAME_PANEL_GRAPHIC_7,
661 &game.panel.graphic[6],
665 GAME_PANEL_GRAPHIC_8,
666 &game.panel.graphic[7],
670 GAME_PANEL_ELEMENT_1,
671 &game.panel.element[0],
675 GAME_PANEL_ELEMENT_2,
676 &game.panel.element[1],
680 GAME_PANEL_ELEMENT_3,
681 &game.panel.element[2],
685 GAME_PANEL_ELEMENT_4,
686 &game.panel.element[3],
690 GAME_PANEL_ELEMENT_5,
691 &game.panel.element[4],
695 GAME_PANEL_ELEMENT_6,
696 &game.panel.element[5],
700 GAME_PANEL_ELEMENT_7,
701 &game.panel.element[6],
705 GAME_PANEL_ELEMENT_8,
706 &game.panel.element[7],
710 GAME_PANEL_ELEMENT_COUNT_1,
711 &game.panel.element_count[0],
715 GAME_PANEL_ELEMENT_COUNT_2,
716 &game.panel.element_count[1],
720 GAME_PANEL_ELEMENT_COUNT_3,
721 &game.panel.element_count[2],
725 GAME_PANEL_ELEMENT_COUNT_4,
726 &game.panel.element_count[3],
730 GAME_PANEL_ELEMENT_COUNT_5,
731 &game.panel.element_count[4],
735 GAME_PANEL_ELEMENT_COUNT_6,
736 &game.panel.element_count[5],
740 GAME_PANEL_ELEMENT_COUNT_7,
741 &game.panel.element_count[6],
745 GAME_PANEL_ELEMENT_COUNT_8,
746 &game.panel.element_count[7],
750 GAME_PANEL_CE_SCORE_1,
751 &game.panel.ce_score[0],
755 GAME_PANEL_CE_SCORE_2,
756 &game.panel.ce_score[1],
760 GAME_PANEL_CE_SCORE_3,
761 &game.panel.ce_score[2],
765 GAME_PANEL_CE_SCORE_4,
766 &game.panel.ce_score[3],
770 GAME_PANEL_CE_SCORE_5,
771 &game.panel.ce_score[4],
775 GAME_PANEL_CE_SCORE_6,
776 &game.panel.ce_score[5],
780 GAME_PANEL_CE_SCORE_7,
781 &game.panel.ce_score[6],
785 GAME_PANEL_CE_SCORE_8,
786 &game.panel.ce_score[7],
790 GAME_PANEL_CE_SCORE_1_ELEMENT,
791 &game.panel.ce_score_element[0],
795 GAME_PANEL_CE_SCORE_2_ELEMENT,
796 &game.panel.ce_score_element[1],
800 GAME_PANEL_CE_SCORE_3_ELEMENT,
801 &game.panel.ce_score_element[2],
805 GAME_PANEL_CE_SCORE_4_ELEMENT,
806 &game.panel.ce_score_element[3],
810 GAME_PANEL_CE_SCORE_5_ELEMENT,
811 &game.panel.ce_score_element[4],
815 GAME_PANEL_CE_SCORE_6_ELEMENT,
816 &game.panel.ce_score_element[5],
820 GAME_PANEL_CE_SCORE_7_ELEMENT,
821 &game.panel.ce_score_element[6],
825 GAME_PANEL_CE_SCORE_8_ELEMENT,
826 &game.panel.ce_score_element[7],
830 GAME_PANEL_PLAYER_NAME,
831 &game.panel.player_name,
835 GAME_PANEL_LEVEL_NAME,
836 &game.panel.level_name,
840 GAME_PANEL_LEVEL_AUTHOR,
841 &game.panel.level_author,
852 // values for delayed check of falling and moving elements and for collision
853 #define CHECK_DELAY_MOVING 3
854 #define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
855 #define CHECK_DELAY_COLLISION 2
856 #define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
858 // values for initial player move delay (initial delay counter value)
859 #define INITIAL_MOVE_DELAY_OFF -1
860 #define INITIAL_MOVE_DELAY_ON 0
862 // values for player movement speed (which is in fact a delay value)
863 #define MOVE_DELAY_MIN_SPEED 32
864 #define MOVE_DELAY_NORMAL_SPEED 8
865 #define MOVE_DELAY_HIGH_SPEED 4
866 #define MOVE_DELAY_MAX_SPEED 1
868 #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
869 #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
871 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
872 #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
874 // values for scroll positions
875 #define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
876 (x) > SBX_Right + MIDPOSX ? SBX_Right :\
878 #define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
879 (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
882 // values for other actions
883 #define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
884 #define MOVE_STEPSIZE_MIN (1)
885 #define MOVE_STEPSIZE_MAX (TILEX)
887 #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
888 #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
890 #define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
892 #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
893 RND(element_info[e].push_delay_random))
894 #define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
895 RND(element_info[e].drop_delay_random))
896 #define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
897 RND(element_info[e].move_delay_random))
898 #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
899 (element_info[e].move_delay_random))
900 #define GET_NEW_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
901 RND(element_info[e].step_delay_random))
902 #define GET_MAX_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
903 (element_info[e].step_delay_random))
904 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
905 RND(element_info[e].ce_value_random_initial))
906 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
907 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
908 RND((c)->delay_random * (c)->delay_frames))
909 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
910 RND((c)->delay_random))
913 #define GET_VALID_RUNTIME_ELEMENT(e) \
914 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
916 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
917 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
918 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
919 (be) + (e) - EL_SELF)
921 #define GET_PLAYER_FROM_BITS(p) \
922 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
924 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
925 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
926 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
927 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
928 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
929 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
930 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
931 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
932 RESOLVED_REFERENCE_ELEMENT(be, e) : \
935 #define CAN_GROW_INTO(e) \
936 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
938 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
939 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
942 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
943 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
944 (CAN_MOVE_INTO_ACID(e) && \
945 Tile[x][y] == EL_ACID) || \
948 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
949 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
950 (CAN_MOVE_INTO_ACID(e) && \
951 Tile[x][y] == EL_ACID) || \
954 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
955 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
957 (CAN_MOVE_INTO_ACID(e) && \
958 Tile[x][y] == EL_ACID) || \
959 (DONT_COLLIDE_WITH(e) && \
961 !PLAYER_ENEMY_PROTECTED(x, y))))
963 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
964 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
966 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
967 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
969 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
970 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
972 #define ANDROID_CAN_CLONE_FIELD(x, y) \
973 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
974 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
976 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
977 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
979 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
980 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
982 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
983 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
985 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
986 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
988 #define PIG_CAN_ENTER_FIELD(e, x, y) \
989 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
991 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
992 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
993 Tile[x][y] == EL_EM_EXIT_OPEN || \
994 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
995 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
996 IS_FOOD_PENGUIN(Tile[x][y])))
997 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
998 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
1000 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
1001 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
1003 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
1004 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
1006 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
1007 (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
1008 Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
1010 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
1012 #define CE_ENTER_FIELD_COND(e, x, y) \
1013 (!IS_PLAYER(x, y) && \
1014 IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
1016 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
1017 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
1019 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
1020 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1022 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1023 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1024 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1025 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1027 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1029 // game button identifiers
1030 #define GAME_CTRL_ID_STOP 0
1031 #define GAME_CTRL_ID_PAUSE 1
1032 #define GAME_CTRL_ID_PLAY 2
1033 #define GAME_CTRL_ID_UNDO 3
1034 #define GAME_CTRL_ID_REDO 4
1035 #define GAME_CTRL_ID_SAVE 5
1036 #define GAME_CTRL_ID_PAUSE2 6
1037 #define GAME_CTRL_ID_LOAD 7
1038 #define GAME_CTRL_ID_RESTART 8
1039 #define GAME_CTRL_ID_PANEL_STOP 9
1040 #define GAME_CTRL_ID_PANEL_PAUSE 10
1041 #define GAME_CTRL_ID_PANEL_PLAY 11
1042 #define GAME_CTRL_ID_PANEL_RESTART 12
1043 #define GAME_CTRL_ID_TOUCH_STOP 13
1044 #define GAME_CTRL_ID_TOUCH_PAUSE 14
1045 #define GAME_CTRL_ID_TOUCH_RESTART 15
1046 #define SOUND_CTRL_ID_MUSIC 16
1047 #define SOUND_CTRL_ID_LOOPS 17
1048 #define SOUND_CTRL_ID_SIMPLE 18
1049 #define SOUND_CTRL_ID_PANEL_MUSIC 19
1050 #define SOUND_CTRL_ID_PANEL_LOOPS 20
1051 #define SOUND_CTRL_ID_PANEL_SIMPLE 21
1053 #define NUM_GAME_BUTTONS 22
1056 // forward declaration for internal use
1058 static void CreateField(int, int, int);
1060 static void ResetGfxAnimation(int, int);
1062 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1063 static void AdvanceFrameAndPlayerCounters(int);
1065 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1066 static boolean MovePlayer(struct PlayerInfo *, int, int);
1067 static void ScrollPlayer(struct PlayerInfo *, int);
1068 static void ScrollScreen(struct PlayerInfo *, int);
1070 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1071 static boolean DigFieldByCE(int, int, int);
1072 static boolean SnapField(struct PlayerInfo *, int, int);
1073 static boolean DropElement(struct PlayerInfo *);
1075 static void InitBeltMovement(void);
1076 static void CloseAllOpenTimegates(void);
1077 static void CheckGravityMovement(struct PlayerInfo *);
1078 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1079 static void KillPlayerUnlessEnemyProtected(int, int);
1080 static void KillPlayerUnlessExplosionProtected(int, int);
1082 static void CheckNextToConditions(int, int);
1083 static void TestIfPlayerNextToCustomElement(int, int);
1084 static void TestIfPlayerTouchesCustomElement(int, int);
1085 static void TestIfElementNextToCustomElement(int, int);
1086 static void TestIfElementTouchesCustomElement(int, int);
1087 static void TestIfElementHitsCustomElement(int, int, int);
1089 static void HandleElementChange(int, int, int);
1090 static void ExecuteCustomElementAction(int, int, int, int);
1091 static boolean ChangeElement(int, int, int, int);
1093 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int, int, int);
1094 #define CheckTriggeredElementChange(x, y, e, ev) \
1095 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, -1)
1096 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1097 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1098 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1099 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1100 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1101 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1102 #define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
1103 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1105 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1106 #define CheckElementChange(x, y, e, te, ev) \
1107 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1108 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1109 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1110 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1111 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1112 #define CheckElementChangeByMouse(x, y, e, ev, s) \
1113 CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
1115 static void PlayLevelSound(int, int, int);
1116 static void PlayLevelSoundNearest(int, int, int);
1117 static void PlayLevelSoundAction(int, int, int);
1118 static void PlayLevelSoundElementAction(int, int, int, int);
1119 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1120 static void PlayLevelSoundActionIfLoop(int, int, int);
1121 static void StopLevelSoundActionIfLoop(int, int, int);
1122 static void PlayLevelMusic(void);
1123 static void FadeLevelSoundsAndMusic(void);
1125 static void HandleGameButtons(struct GadgetInfo *);
1127 int AmoebaNeighbourNr(int, int);
1128 void AmoebaToDiamond(int, int);
1129 void ContinueMoving(int, int);
1130 void Bang(int, int);
1131 void InitMovDir(int, int);
1132 void InitAmoebaNr(int, int);
1133 void NewHighScore(int, boolean);
1135 void TestIfGoodThingHitsBadThing(int, int, int);
1136 void TestIfBadThingHitsGoodThing(int, int, int);
1137 void TestIfPlayerTouchesBadThing(int, int);
1138 void TestIfPlayerRunsIntoBadThing(int, int, int);
1139 void TestIfBadThingTouchesPlayer(int, int);
1140 void TestIfBadThingRunsIntoPlayer(int, int, int);
1141 void TestIfFriendTouchesBadThing(int, int);
1142 void TestIfBadThingTouchesFriend(int, int);
1143 void TestIfBadThingTouchesOtherBadThing(int, int);
1144 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1146 void KillPlayer(struct PlayerInfo *);
1147 void BuryPlayer(struct PlayerInfo *);
1148 void RemovePlayer(struct PlayerInfo *);
1149 void ExitPlayer(struct PlayerInfo *);
1151 static int getInvisibleActiveFromInvisibleElement(int);
1152 static int getInvisibleFromInvisibleActiveElement(int);
1154 static void TestFieldAfterSnapping(int, int, int, int, int);
1156 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1158 // for detection of endless loops, caused by custom element programming
1159 // (using maximal playfield width x 10 is just a rough approximation)
1160 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1162 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1164 if (recursion_loop_detected) \
1167 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1169 recursion_loop_detected = TRUE; \
1170 recursion_loop_element = (e); \
1173 recursion_loop_depth++; \
1176 #define RECURSION_LOOP_DETECTION_END() \
1178 recursion_loop_depth--; \
1181 static int recursion_loop_depth;
1182 static boolean recursion_loop_detected;
1183 static boolean recursion_loop_element;
1185 static int map_player_action[MAX_PLAYERS];
1188 // ----------------------------------------------------------------------------
1189 // definition of elements that automatically change to other elements after
1190 // a specified time, eventually calling a function when changing
1191 // ----------------------------------------------------------------------------
1193 // forward declaration for changer functions
1194 static void InitBuggyBase(int, int);
1195 static void WarnBuggyBase(int, int);
1197 static void InitTrap(int, int);
1198 static void ActivateTrap(int, int);
1199 static void ChangeActiveTrap(int, int);
1201 static void InitRobotWheel(int, int);
1202 static void RunRobotWheel(int, int);
1203 static void StopRobotWheel(int, int);
1205 static void InitTimegateWheel(int, int);
1206 static void RunTimegateWheel(int, int);
1208 static void InitMagicBallDelay(int, int);
1209 static void ActivateMagicBall(int, int);
1211 struct ChangingElementInfo
1216 void (*pre_change_function)(int x, int y);
1217 void (*change_function)(int x, int y);
1218 void (*post_change_function)(int x, int y);
1221 static struct ChangingElementInfo change_delay_list[] =
1256 EL_STEEL_EXIT_OPENING,
1264 EL_STEEL_EXIT_CLOSING,
1265 EL_STEEL_EXIT_CLOSED,
1288 EL_EM_STEEL_EXIT_OPENING,
1289 EL_EM_STEEL_EXIT_OPEN,
1296 EL_EM_STEEL_EXIT_CLOSING,
1320 EL_SWITCHGATE_OPENING,
1328 EL_SWITCHGATE_CLOSING,
1329 EL_SWITCHGATE_CLOSED,
1336 EL_TIMEGATE_OPENING,
1344 EL_TIMEGATE_CLOSING,
1353 EL_ACID_SPLASH_LEFT,
1361 EL_ACID_SPLASH_RIGHT,
1370 EL_SP_BUGGY_BASE_ACTIVATING,
1377 EL_SP_BUGGY_BASE_ACTIVATING,
1378 EL_SP_BUGGY_BASE_ACTIVE,
1385 EL_SP_BUGGY_BASE_ACTIVE,
1409 EL_ROBOT_WHEEL_ACTIVE,
1417 EL_TIMEGATE_SWITCH_ACTIVE,
1425 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1426 EL_DC_TIMEGATE_SWITCH,
1433 EL_EMC_MAGIC_BALL_ACTIVE,
1434 EL_EMC_MAGIC_BALL_ACTIVE,
1441 EL_EMC_SPRING_BUMPER_ACTIVE,
1442 EL_EMC_SPRING_BUMPER,
1449 EL_DIAGONAL_SHRINKING,
1457 EL_DIAGONAL_GROWING,
1478 int push_delay_fixed, push_delay_random;
1482 { EL_SPRING, 0, 0 },
1483 { EL_BALLOON, 0, 0 },
1485 { EL_SOKOBAN_OBJECT, 2, 0 },
1486 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1487 { EL_SATELLITE, 2, 0 },
1488 { EL_SP_DISK_YELLOW, 2, 0 },
1490 { EL_UNDEFINED, 0, 0 },
1498 move_stepsize_list[] =
1500 { EL_AMOEBA_DROP, 2 },
1501 { EL_AMOEBA_DROPPING, 2 },
1502 { EL_QUICKSAND_FILLING, 1 },
1503 { EL_QUICKSAND_EMPTYING, 1 },
1504 { EL_QUICKSAND_FAST_FILLING, 2 },
1505 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1506 { EL_MAGIC_WALL_FILLING, 2 },
1507 { EL_MAGIC_WALL_EMPTYING, 2 },
1508 { EL_BD_MAGIC_WALL_FILLING, 2 },
1509 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1510 { EL_DC_MAGIC_WALL_FILLING, 2 },
1511 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1513 { EL_UNDEFINED, 0 },
1521 collect_count_list[] =
1524 { EL_BD_DIAMOND, 1 },
1525 { EL_EMERALD_YELLOW, 1 },
1526 { EL_EMERALD_RED, 1 },
1527 { EL_EMERALD_PURPLE, 1 },
1529 { EL_SP_INFOTRON, 1 },
1533 { EL_UNDEFINED, 0 },
1541 access_direction_list[] =
1543 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1544 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1545 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1546 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1547 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1548 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1549 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1550 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1551 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1552 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1553 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1555 { EL_SP_PORT_LEFT, MV_RIGHT },
1556 { EL_SP_PORT_RIGHT, MV_LEFT },
1557 { EL_SP_PORT_UP, MV_DOWN },
1558 { EL_SP_PORT_DOWN, MV_UP },
1559 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1560 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1561 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1562 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1563 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1564 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1565 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1566 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1567 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1568 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1569 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1570 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1571 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1572 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1573 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1575 { EL_UNDEFINED, MV_NONE }
1578 static struct XY xy_topdown[] =
1586 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1588 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1589 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1590 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1591 IS_JUST_CHANGING(x, y))
1593 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1595 // static variables for playfield scan mode (scanning forward or backward)
1596 static int playfield_scan_start_x = 0;
1597 static int playfield_scan_start_y = 0;
1598 static int playfield_scan_delta_x = 1;
1599 static int playfield_scan_delta_y = 1;
1601 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1602 (y) >= 0 && (y) <= lev_fieldy - 1; \
1603 (y) += playfield_scan_delta_y) \
1604 for ((x) = playfield_scan_start_x; \
1605 (x) >= 0 && (x) <= lev_fieldx - 1; \
1606 (x) += playfield_scan_delta_x)
1609 void DEBUG_SetMaximumDynamite(void)
1613 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1614 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1615 local_player->inventory_element[local_player->inventory_size++] =
1620 static void InitPlayfieldScanModeVars(void)
1622 if (game.use_reverse_scan_direction)
1624 playfield_scan_start_x = lev_fieldx - 1;
1625 playfield_scan_start_y = lev_fieldy - 1;
1627 playfield_scan_delta_x = -1;
1628 playfield_scan_delta_y = -1;
1632 playfield_scan_start_x = 0;
1633 playfield_scan_start_y = 0;
1635 playfield_scan_delta_x = 1;
1636 playfield_scan_delta_y = 1;
1640 static void InitPlayfieldScanMode(int mode)
1642 game.use_reverse_scan_direction =
1643 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1645 InitPlayfieldScanModeVars();
1648 static int get_move_delay_from_stepsize(int move_stepsize)
1651 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1653 // make sure that stepsize value is always a power of 2
1654 move_stepsize = (1 << log_2(move_stepsize));
1656 return TILEX / move_stepsize;
1659 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1662 int player_nr = player->index_nr;
1663 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1664 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1666 // do no immediately change move delay -- the player might just be moving
1667 player->move_delay_value_next = move_delay;
1669 // information if player can move must be set separately
1670 player->cannot_move = cannot_move;
1674 player->move_delay = game.initial_move_delay[player_nr];
1675 player->move_delay_value = game.initial_move_delay_value[player_nr];
1677 player->move_delay_value_next = -1;
1679 player->move_delay_reset_counter = 0;
1683 void GetPlayerConfig(void)
1685 GameFrameDelay = setup.game_frame_delay;
1687 if (!audio.sound_available)
1688 setup.sound_simple = FALSE;
1690 if (!audio.loops_available)
1691 setup.sound_loops = FALSE;
1693 if (!audio.music_available)
1694 setup.sound_music = FALSE;
1696 if (!video.fullscreen_available)
1697 setup.fullscreen = FALSE;
1699 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1701 SetAudioMode(setup.sound);
1704 int GetElementFromGroupElement(int element)
1706 if (IS_GROUP_ELEMENT(element))
1708 struct ElementGroupInfo *group = element_info[element].group;
1709 int last_anim_random_frame = gfx.anim_random_frame;
1712 if (group->choice_mode == ANIM_RANDOM)
1713 gfx.anim_random_frame = RND(group->num_elements_resolved);
1715 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1716 group->choice_mode, 0,
1719 if (group->choice_mode == ANIM_RANDOM)
1720 gfx.anim_random_frame = last_anim_random_frame;
1722 group->choice_pos++;
1724 element = group->element_resolved[element_pos];
1730 static void IncrementSokobanFieldsNeeded(void)
1732 if (level.sb_fields_needed)
1733 game.sokoban_fields_still_needed++;
1736 static void IncrementSokobanObjectsNeeded(void)
1738 if (level.sb_objects_needed)
1739 game.sokoban_objects_still_needed++;
1742 static void DecrementSokobanFieldsNeeded(void)
1744 if (game.sokoban_fields_still_needed > 0)
1745 game.sokoban_fields_still_needed--;
1748 static void DecrementSokobanObjectsNeeded(void)
1750 if (game.sokoban_objects_still_needed > 0)
1751 game.sokoban_objects_still_needed--;
1754 static void InitPlayerField(int x, int y, int element, boolean init_game)
1756 if (element == EL_SP_MURPHY)
1760 if (stored_player[0].present)
1762 Tile[x][y] = EL_SP_MURPHY_CLONE;
1768 stored_player[0].initial_element = element;
1769 stored_player[0].use_murphy = TRUE;
1771 if (!level.use_artwork_element[0])
1772 stored_player[0].artwork_element = EL_SP_MURPHY;
1775 Tile[x][y] = EL_PLAYER_1;
1781 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1782 int jx = player->jx, jy = player->jy;
1784 player->present = TRUE;
1786 player->block_last_field = (element == EL_SP_MURPHY ?
1787 level.sp_block_last_field :
1788 level.block_last_field);
1790 // ---------- initialize player's last field block delay ------------------
1792 // always start with reliable default value (no adjustment needed)
1793 player->block_delay_adjustment = 0;
1795 // special case 1: in Supaplex, Murphy blocks last field one more frame
1796 if (player->block_last_field && element == EL_SP_MURPHY)
1797 player->block_delay_adjustment = 1;
1799 // special case 2: in game engines before 3.1.1, blocking was different
1800 if (game.use_block_last_field_bug)
1801 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1803 if (!network.enabled || player->connected_network)
1805 player->active = TRUE;
1807 // remove potentially duplicate players
1808 if (IN_LEV_FIELD(jx, jy) && StorePlayer[jx][jy] == Tile[x][y])
1809 StorePlayer[jx][jy] = 0;
1811 StorePlayer[x][y] = Tile[x][y];
1813 #if DEBUG_INIT_PLAYER
1814 Debug("game:init:player", "- player element %d activated",
1815 player->element_nr);
1816 Debug("game:init:player", " (local player is %d and currently %s)",
1817 local_player->element_nr,
1818 local_player->active ? "active" : "not active");
1822 Tile[x][y] = EL_EMPTY;
1824 player->jx = player->last_jx = x;
1825 player->jy = player->last_jy = y;
1828 // always check if player was just killed and should be reanimated
1830 int player_nr = GET_PLAYER_NR(element);
1831 struct PlayerInfo *player = &stored_player[player_nr];
1833 if (player->active && player->killed)
1834 player->reanimated = TRUE; // if player was just killed, reanimate him
1838 static void InitField(int x, int y, boolean init_game)
1840 int element = Tile[x][y];
1849 InitPlayerField(x, y, element, init_game);
1852 case EL_SOKOBAN_FIELD_PLAYER:
1853 element = Tile[x][y] = EL_PLAYER_1;
1854 InitField(x, y, init_game);
1856 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1857 InitField(x, y, init_game);
1860 case EL_SOKOBAN_FIELD_EMPTY:
1861 IncrementSokobanFieldsNeeded();
1864 case EL_SOKOBAN_OBJECT:
1865 IncrementSokobanObjectsNeeded();
1869 if (x < lev_fieldx - 1 && Tile[x + 1][y] == EL_ACID)
1870 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1871 else if (x > 0 && Tile[x - 1][y] == EL_ACID)
1872 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1873 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPLEFT)
1874 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1875 else if (y > 0 && Tile[x][y - 1] == EL_ACID)
1876 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1877 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPRIGHT)
1878 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1887 case EL_SPACESHIP_RIGHT:
1888 case EL_SPACESHIP_UP:
1889 case EL_SPACESHIP_LEFT:
1890 case EL_SPACESHIP_DOWN:
1891 case EL_BD_BUTTERFLY:
1892 case EL_BD_BUTTERFLY_RIGHT:
1893 case EL_BD_BUTTERFLY_UP:
1894 case EL_BD_BUTTERFLY_LEFT:
1895 case EL_BD_BUTTERFLY_DOWN:
1897 case EL_BD_FIREFLY_RIGHT:
1898 case EL_BD_FIREFLY_UP:
1899 case EL_BD_FIREFLY_LEFT:
1900 case EL_BD_FIREFLY_DOWN:
1901 case EL_PACMAN_RIGHT:
1903 case EL_PACMAN_LEFT:
1904 case EL_PACMAN_DOWN:
1906 case EL_YAMYAM_LEFT:
1907 case EL_YAMYAM_RIGHT:
1909 case EL_YAMYAM_DOWN:
1910 case EL_DARK_YAMYAM:
1913 case EL_SP_SNIKSNAK:
1914 case EL_SP_ELECTRON:
1920 case EL_SPRING_LEFT:
1921 case EL_SPRING_RIGHT:
1925 case EL_AMOEBA_FULL:
1930 case EL_AMOEBA_DROP:
1931 if (y == lev_fieldy - 1)
1933 Tile[x][y] = EL_AMOEBA_GROWING;
1934 Store[x][y] = EL_AMOEBA_WET;
1938 case EL_DYNAMITE_ACTIVE:
1939 case EL_SP_DISK_RED_ACTIVE:
1940 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1941 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1942 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1943 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1944 MovDelay[x][y] = 96;
1947 case EL_EM_DYNAMITE_ACTIVE:
1948 MovDelay[x][y] = 32;
1952 game.lights_still_needed++;
1956 game.friends_still_needed++;
1961 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1964 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1965 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1966 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1967 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1968 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1969 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1970 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1971 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1972 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1973 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1974 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1975 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1978 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1979 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1980 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1982 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1984 game.belt_dir[belt_nr] = belt_dir;
1985 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1987 else // more than one switch -- set it like the first switch
1989 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1994 case EL_LIGHT_SWITCH_ACTIVE:
1996 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1999 case EL_INVISIBLE_STEELWALL:
2000 case EL_INVISIBLE_WALL:
2001 case EL_INVISIBLE_SAND:
2002 if (game.light_time_left > 0 ||
2003 game.lenses_time_left > 0)
2004 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
2007 case EL_EMC_MAGIC_BALL:
2008 if (game.ball_active)
2009 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
2012 case EL_EMC_MAGIC_BALL_SWITCH:
2013 if (game.ball_active)
2014 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
2017 case EL_TRIGGER_PLAYER:
2018 case EL_TRIGGER_ELEMENT:
2019 case EL_TRIGGER_CE_VALUE:
2020 case EL_TRIGGER_CE_SCORE:
2022 case EL_ANY_ELEMENT:
2023 case EL_CURRENT_CE_VALUE:
2024 case EL_CURRENT_CE_SCORE:
2041 // reference elements should not be used on the playfield
2042 Tile[x][y] = EL_EMPTY;
2046 if (IS_CUSTOM_ELEMENT(element))
2048 if (CAN_MOVE(element))
2051 if (!element_info[element].use_last_ce_value || init_game)
2052 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2054 else if (IS_GROUP_ELEMENT(element))
2056 Tile[x][y] = GetElementFromGroupElement(element);
2058 InitField(x, y, init_game);
2060 else if (IS_EMPTY_ELEMENT(element))
2062 GfxElementEmpty[x][y] = element;
2063 Tile[x][y] = EL_EMPTY;
2065 if (element_info[element].use_gfx_element)
2066 game.use_masked_elements = TRUE;
2073 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2076 static void InitField_WithBug1(int x, int y, boolean init_game)
2078 InitField(x, y, init_game);
2080 // not needed to call InitMovDir() -- already done by InitField()!
2081 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2082 CAN_MOVE(Tile[x][y]))
2086 static void InitField_WithBug2(int x, int y, boolean init_game)
2088 int old_element = Tile[x][y];
2090 InitField(x, y, init_game);
2092 // not needed to call InitMovDir() -- already done by InitField()!
2093 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2094 CAN_MOVE(old_element) &&
2095 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2098 /* this case is in fact a combination of not less than three bugs:
2099 first, it calls InitMovDir() for elements that can move, although this is
2100 already done by InitField(); then, it checks the element that was at this
2101 field _before_ the call to InitField() (which can change it); lastly, it
2102 was not called for "mole with direction" elements, which were treated as
2103 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2107 static int get_key_element_from_nr(int key_nr)
2109 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2110 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2111 EL_EM_KEY_1 : EL_KEY_1);
2113 return key_base_element + key_nr;
2116 static int get_next_dropped_element(struct PlayerInfo *player)
2118 return (player->inventory_size > 0 ?
2119 player->inventory_element[player->inventory_size - 1] :
2120 player->inventory_infinite_element != EL_UNDEFINED ?
2121 player->inventory_infinite_element :
2122 player->dynabombs_left > 0 ?
2123 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2127 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2129 // pos >= 0: get element from bottom of the stack;
2130 // pos < 0: get element from top of the stack
2134 int min_inventory_size = -pos;
2135 int inventory_pos = player->inventory_size - min_inventory_size;
2136 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2138 return (player->inventory_size >= min_inventory_size ?
2139 player->inventory_element[inventory_pos] :
2140 player->inventory_infinite_element != EL_UNDEFINED ?
2141 player->inventory_infinite_element :
2142 player->dynabombs_left >= min_dynabombs_left ?
2143 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2148 int min_dynabombs_left = pos + 1;
2149 int min_inventory_size = pos + 1 - player->dynabombs_left;
2150 int inventory_pos = pos - player->dynabombs_left;
2152 return (player->inventory_infinite_element != EL_UNDEFINED ?
2153 player->inventory_infinite_element :
2154 player->dynabombs_left >= min_dynabombs_left ?
2155 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2156 player->inventory_size >= min_inventory_size ?
2157 player->inventory_element[inventory_pos] :
2162 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2164 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2165 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2168 if (gpo1->sort_priority != gpo2->sort_priority)
2169 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2171 compare_result = gpo1->nr - gpo2->nr;
2173 return compare_result;
2176 int getPlayerInventorySize(int player_nr)
2178 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2179 return game_em.ply[player_nr]->dynamite;
2180 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2181 return game_sp.red_disk_count;
2183 return stored_player[player_nr].inventory_size;
2186 static void InitGameControlValues(void)
2190 for (i = 0; game_panel_controls[i].nr != -1; i++)
2192 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2193 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2194 struct TextPosInfo *pos = gpc->pos;
2196 int type = gpc->type;
2200 Error("'game_panel_controls' structure corrupted at %d", i);
2202 Fail("this should not happen -- please debug");
2205 // force update of game controls after initialization
2206 gpc->value = gpc->last_value = -1;
2207 gpc->frame = gpc->last_frame = -1;
2208 gpc->gfx_frame = -1;
2210 // determine panel value width for later calculation of alignment
2211 if (type == TYPE_INTEGER || type == TYPE_STRING)
2213 pos->width = pos->size * getFontWidth(pos->font);
2214 pos->height = getFontHeight(pos->font);
2216 else if (type == TYPE_ELEMENT)
2218 pos->width = pos->size;
2219 pos->height = pos->size;
2222 // fill structure for game panel draw order
2224 gpo->sort_priority = pos->sort_priority;
2227 // sort game panel controls according to sort_priority and control number
2228 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2229 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2232 static void UpdatePlayfieldElementCount(void)
2234 boolean use_element_count = FALSE;
2237 // first check if it is needed at all to calculate playfield element count
2238 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2239 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2240 use_element_count = TRUE;
2242 if (!use_element_count)
2245 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2246 element_info[i].element_count = 0;
2248 SCAN_PLAYFIELD(x, y)
2250 element_info[Tile[x][y]].element_count++;
2253 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2254 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2255 if (IS_IN_GROUP(j, i))
2256 element_info[EL_GROUP_START + i].element_count +=
2257 element_info[j].element_count;
2260 static void UpdateGameControlValues(void)
2263 int time = (game.LevelSolved ?
2264 game.LevelSolved_CountingTime :
2265 level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2266 game_bd.time_played :
2267 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2269 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2270 game_sp.time_played :
2271 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2272 game_mm.energy_left :
2273 game.no_level_time_limit ? TimePlayed : TimeLeft);
2274 int score = (game.LevelSolved ?
2275 game.LevelSolved_CountingScore :
2276 level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2278 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2279 game_em.lev->score :
2280 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2282 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2285 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2286 game_bd.gems_still_needed :
2287 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2288 game_em.lev->gems_needed :
2289 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2290 game_sp.infotrons_still_needed :
2291 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2292 game_mm.kettles_still_needed :
2293 game.gems_still_needed);
2294 int gems_total = level.gems_needed;
2295 int gems_collected = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2296 game_bd.game->cave->diamonds_collected :
2298 int gems_score = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2299 game_bd.game->cave->diamond_value :
2300 level.score[SC_EMERALD]);
2301 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2302 game_bd.gems_still_needed > 0 :
2303 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2304 game_em.lev->gems_needed > 0 :
2305 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2306 game_sp.infotrons_still_needed > 0 :
2307 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2308 game_mm.kettles_still_needed > 0 ||
2309 game_mm.lights_still_needed > 0 :
2310 game.gems_still_needed > 0 ||
2311 game.sokoban_fields_still_needed > 0 ||
2312 game.sokoban_objects_still_needed > 0 ||
2313 game.lights_still_needed > 0);
2314 int health = (game.LevelSolved ?
2315 game.LevelSolved_CountingHealth :
2316 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2317 MM_HEALTH(game_mm.laser_overload_value) :
2319 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2321 UpdatePlayfieldElementCount();
2323 // update game panel control values
2325 // used instead of "level_nr" (for network games)
2326 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2327 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2328 game_panel_controls[GAME_PANEL_GEMS_TOTAL].value = gems_total;
2329 game_panel_controls[GAME_PANEL_GEMS_COLLECTED].value = gems_collected;
2330 game_panel_controls[GAME_PANEL_GEMS_SCORE].value = gems_score;
2332 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2333 for (i = 0; i < MAX_NUM_KEYS; i++)
2334 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2335 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2336 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2338 if (game.centered_player_nr == -1)
2340 for (i = 0; i < MAX_PLAYERS; i++)
2342 // only one player in Supaplex game engine
2343 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2346 for (k = 0; k < MAX_NUM_KEYS; k++)
2348 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2350 if (game_em.ply[i]->keys & (1 << k))
2351 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2352 get_key_element_from_nr(k);
2354 else if (stored_player[i].key[k])
2355 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2356 get_key_element_from_nr(k);
2359 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2360 getPlayerInventorySize(i);
2362 if (stored_player[i].num_white_keys > 0)
2363 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2366 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2367 stored_player[i].num_white_keys;
2372 int player_nr = game.centered_player_nr;
2374 for (k = 0; k < MAX_NUM_KEYS; k++)
2376 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2378 if (game_em.ply[player_nr]->keys & (1 << k))
2379 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2380 get_key_element_from_nr(k);
2382 else if (stored_player[player_nr].key[k])
2383 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2384 get_key_element_from_nr(k);
2387 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2388 getPlayerInventorySize(player_nr);
2390 if (stored_player[player_nr].num_white_keys > 0)
2391 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2393 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2394 stored_player[player_nr].num_white_keys;
2397 // re-arrange keys on game panel, if needed or if defined by style settings
2398 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2400 int nr = GAME_PANEL_KEY_1 + i;
2401 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2402 struct TextPosInfo *pos = gpc->pos;
2404 // skip check if key is not in the player's inventory
2405 if (gpc->value == EL_EMPTY)
2408 // check if keys should be arranged on panel from left to right
2409 if (pos->style == STYLE_LEFTMOST_POSITION)
2411 // check previous key positions (left from current key)
2412 for (k = 0; k < i; k++)
2414 int nr_new = GAME_PANEL_KEY_1 + k;
2416 if (game_panel_controls[nr_new].value == EL_EMPTY)
2418 game_panel_controls[nr_new].value = gpc->value;
2419 gpc->value = EL_EMPTY;
2426 // check if "undefined" keys can be placed at some other position
2427 if (pos->x == -1 && pos->y == -1)
2429 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2431 // 1st try: display key at the same position as normal or EM keys
2432 if (game_panel_controls[nr_new].value == EL_EMPTY)
2434 game_panel_controls[nr_new].value = gpc->value;
2438 // 2nd try: display key at the next free position in the key panel
2439 for (k = 0; k < STD_NUM_KEYS; k++)
2441 nr_new = GAME_PANEL_KEY_1 + k;
2443 if (game_panel_controls[nr_new].value == EL_EMPTY)
2445 game_panel_controls[nr_new].value = gpc->value;
2454 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2456 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2457 get_inventory_element_from_pos(local_player, i);
2458 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2459 get_inventory_element_from_pos(local_player, -i - 1);
2462 game_panel_controls[GAME_PANEL_SCORE].value = score;
2463 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2465 game_panel_controls[GAME_PANEL_TIME].value = time;
2467 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2468 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2469 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2471 if (level.time == 0)
2472 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2474 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2476 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2477 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2479 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2481 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2482 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2484 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2485 local_player->shield_normal_time_left;
2486 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2487 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2489 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2490 local_player->shield_deadly_time_left;
2492 game_panel_controls[GAME_PANEL_EXIT].value =
2493 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2495 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2496 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2497 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2498 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2499 EL_EMC_MAGIC_BALL_SWITCH);
2501 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2502 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2503 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2504 game.light_time_left;
2506 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2507 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2508 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2509 game.timegate_time_left;
2511 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2512 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2514 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2515 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2516 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2517 game.lenses_time_left;
2519 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2520 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2521 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2522 game.magnify_time_left;
2524 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2525 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2526 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2527 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2528 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2529 EL_BALLOON_SWITCH_NONE);
2531 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2532 local_player->dynabomb_count;
2533 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2534 local_player->dynabomb_size;
2535 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2536 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2538 game_panel_controls[GAME_PANEL_PENGUINS].value =
2539 game.friends_still_needed;
2541 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2542 game.sokoban_objects_still_needed;
2543 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2544 game.sokoban_fields_still_needed;
2546 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2547 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2549 for (i = 0; i < NUM_BELTS; i++)
2551 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2552 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2553 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2554 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2555 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2558 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2559 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2560 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2561 game.magic_wall_time_left;
2563 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2564 local_player->gravity;
2566 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2567 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2569 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2570 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2571 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2572 game.panel.element[i].id : EL_UNDEFINED);
2574 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2575 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2576 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2577 element_info[game.panel.element_count[i].id].element_count : 0);
2579 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2580 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2581 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2582 element_info[game.panel.ce_score[i].id].collect_score : 0);
2584 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2585 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2586 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2587 element_info[game.panel.ce_score_element[i].id].collect_score :
2590 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2591 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2592 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2594 // update game panel control frames
2596 for (i = 0; game_panel_controls[i].nr != -1; i++)
2598 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2600 if (gpc->type == TYPE_ELEMENT)
2602 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2604 int last_anim_random_frame = gfx.anim_random_frame;
2605 int element = gpc->value;
2606 int graphic = el2panelimg(element);
2607 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2609 graphic_info[graphic].anim_global_anim_sync ?
2610 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2612 if (gpc->value != gpc->last_value)
2615 gpc->gfx_random = init_gfx_random;
2621 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2622 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2623 gpc->gfx_random = init_gfx_random;
2626 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2627 gfx.anim_random_frame = gpc->gfx_random;
2629 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2630 gpc->gfx_frame = element_info[element].collect_score;
2632 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2634 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2635 gfx.anim_random_frame = last_anim_random_frame;
2638 else if (gpc->type == TYPE_GRAPHIC)
2640 if (gpc->graphic != IMG_UNDEFINED)
2642 int last_anim_random_frame = gfx.anim_random_frame;
2643 int graphic = gpc->graphic;
2644 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2646 graphic_info[graphic].anim_global_anim_sync ?
2647 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2649 if (gpc->value != gpc->last_value)
2652 gpc->gfx_random = init_gfx_random;
2658 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2659 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2660 gpc->gfx_random = init_gfx_random;
2663 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2664 gfx.anim_random_frame = gpc->gfx_random;
2666 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2668 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2669 gfx.anim_random_frame = last_anim_random_frame;
2675 static void DisplayGameControlValues(void)
2677 boolean redraw_panel = FALSE;
2680 for (i = 0; game_panel_controls[i].nr != -1; i++)
2682 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2684 if (PANEL_DEACTIVATED(gpc->pos))
2687 if (gpc->value == gpc->last_value &&
2688 gpc->frame == gpc->last_frame)
2691 redraw_panel = TRUE;
2697 // copy default game door content to main double buffer
2699 // !!! CHECK AGAIN !!!
2700 SetPanelBackground();
2701 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2702 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2704 // redraw game control buttons
2705 RedrawGameButtons();
2707 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2709 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2711 int nr = game_panel_order[i].nr;
2712 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2713 struct TextPosInfo *pos = gpc->pos;
2714 int type = gpc->type;
2715 int value = gpc->value;
2716 int frame = gpc->frame;
2717 int size = pos->size;
2718 int font = pos->font;
2719 boolean draw_masked = pos->draw_masked;
2720 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2722 if (PANEL_DEACTIVATED(pos))
2725 if (pos->class == get_hash_from_key("extra_panel_items") &&
2726 !setup.prefer_extra_panel_items)
2729 gpc->last_value = value;
2730 gpc->last_frame = frame;
2732 if (type == TYPE_INTEGER)
2734 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2735 nr == GAME_PANEL_INVENTORY_COUNT ||
2736 nr == GAME_PANEL_SCORE ||
2737 nr == GAME_PANEL_HIGHSCORE ||
2738 nr == GAME_PANEL_TIME)
2740 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2742 if (use_dynamic_size) // use dynamic number of digits
2744 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 :
2745 nr == GAME_PANEL_INVENTORY_COUNT ||
2746 nr == GAME_PANEL_TIME ? 1000 : 100000);
2747 int size_add = (nr == GAME_PANEL_LEVEL_NUMBER ||
2748 nr == GAME_PANEL_INVENTORY_COUNT ||
2749 nr == GAME_PANEL_TIME ? 1 : 2);
2750 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 :
2751 nr == GAME_PANEL_INVENTORY_COUNT ||
2752 nr == GAME_PANEL_TIME ? 3 : 5);
2753 int size2 = size1 + size_add;
2754 int font1 = pos->font;
2755 int font2 = pos->font_alt;
2757 size = (value < value_change ? size1 : size2);
2758 font = (value < value_change ? font1 : font2);
2762 // correct text size if "digits" is zero or less
2764 size = strlen(int2str(value, size));
2766 // dynamically correct text alignment
2767 pos->width = size * getFontWidth(font);
2769 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2770 int2str(value, size), font, mask_mode);
2772 else if (type == TYPE_ELEMENT)
2774 int element, graphic;
2778 int dst_x = PANEL_XPOS(pos);
2779 int dst_y = PANEL_YPOS(pos);
2781 if (value != EL_UNDEFINED && value != EL_EMPTY)
2784 graphic = el2panelimg(value);
2787 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2788 element, EL_NAME(element), size);
2791 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2794 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2797 width = graphic_info[graphic].width * size / TILESIZE;
2798 height = graphic_info[graphic].height * size / TILESIZE;
2801 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2804 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2808 else if (type == TYPE_GRAPHIC)
2810 int graphic = gpc->graphic;
2811 int graphic_active = gpc->graphic_active;
2815 int dst_x = PANEL_XPOS(pos);
2816 int dst_y = PANEL_YPOS(pos);
2817 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2818 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2820 if (graphic != IMG_UNDEFINED && !skip)
2822 if (pos->style == STYLE_REVERSE)
2823 value = 100 - value;
2825 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2827 if (pos->direction & MV_HORIZONTAL)
2829 width = graphic_info[graphic_active].width * value / 100;
2830 height = graphic_info[graphic_active].height;
2832 if (pos->direction == MV_LEFT)
2834 src_x += graphic_info[graphic_active].width - width;
2835 dst_x += graphic_info[graphic_active].width - width;
2840 width = graphic_info[graphic_active].width;
2841 height = graphic_info[graphic_active].height * value / 100;
2843 if (pos->direction == MV_UP)
2845 src_y += graphic_info[graphic_active].height - height;
2846 dst_y += graphic_info[graphic_active].height - height;
2851 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2854 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2857 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2859 if (pos->direction & MV_HORIZONTAL)
2861 if (pos->direction == MV_RIGHT)
2868 dst_x = PANEL_XPOS(pos);
2871 width = graphic_info[graphic].width - width;
2875 if (pos->direction == MV_DOWN)
2882 dst_y = PANEL_YPOS(pos);
2885 height = graphic_info[graphic].height - height;
2889 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2892 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2896 else if (type == TYPE_STRING)
2898 boolean active = (value != 0);
2899 char *state_normal = "off";
2900 char *state_active = "on";
2901 char *state = (active ? state_active : state_normal);
2902 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2903 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2904 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2905 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2907 if (nr == GAME_PANEL_GRAVITY_STATE)
2909 int font1 = pos->font; // (used for normal state)
2910 int font2 = pos->font_alt; // (used for active state)
2912 font = (active ? font2 : font1);
2921 // don't truncate output if "chars" is zero or less
2924 // dynamically correct text alignment
2925 pos->width = size * getFontWidth(font);
2928 s_cut = getStringCopyN(s, size);
2930 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2931 s_cut, font, mask_mode);
2937 redraw_mask |= REDRAW_DOOR_1;
2940 SetGameStatus(GAME_MODE_PLAYING);
2943 void UpdateAndDisplayGameControlValues(void)
2945 if (tape.deactivate_display)
2948 UpdateGameControlValues();
2949 DisplayGameControlValues();
2952 void UpdateGameDoorValues(void)
2954 UpdateGameControlValues();
2957 void DrawGameDoorValues(void)
2959 DisplayGameControlValues();
2963 // ============================================================================
2965 // ----------------------------------------------------------------------------
2966 // initialize game engine due to level / tape version number
2967 // ============================================================================
2969 static void InitGameEngine(void)
2971 int i, j, k, l, x, y;
2973 // set game engine from tape file when re-playing, else from level file
2974 game.engine_version = (tape.playing ? tape.engine_version :
2975 level.game_version);
2977 // set single or multi-player game mode (needed for re-playing tapes)
2978 game.team_mode = setup.team_mode;
2982 int num_players = 0;
2984 for (i = 0; i < MAX_PLAYERS; i++)
2985 if (tape.player_participates[i])
2988 // multi-player tapes contain input data for more than one player
2989 game.team_mode = (num_players > 1);
2993 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2994 level.game_version);
2995 Debug("game:init:level", " tape.file_version == %06d",
2997 Debug("game:init:level", " tape.game_version == %06d",
2999 Debug("game:init:level", " tape.engine_version == %06d",
3000 tape.engine_version);
3001 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
3002 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
3005 // --------------------------------------------------------------------------
3006 // set flags for bugs and changes according to active game engine version
3007 // --------------------------------------------------------------------------
3011 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
3013 Bug was introduced in version:
3016 Bug was fixed in version:
3020 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
3021 but the property "can fall" was missing, which caused some levels to be
3022 unsolvable. This was fixed in version 4.2.0.0.
3024 Affected levels/tapes:
3025 An example for a tape that was fixed by this bugfix is tape 029 from the
3026 level set "rnd_sam_bateman".
3027 The wrong behaviour will still be used for all levels or tapes that were
3028 created/recorded with it. An example for this is tape 023 from the level
3029 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
3032 boolean use_amoeba_dropping_cannot_fall_bug =
3033 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
3034 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
3036 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3037 tape.game_version < VERSION_IDENT(4,2,0,0)));
3040 Summary of bugfix/change:
3041 Fixed move speed of elements entering or leaving magic wall.
3043 Fixed/changed in version:
3047 Before 2.0.1, move speed of elements entering or leaving magic wall was
3048 twice as fast as it is now.
3049 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
3051 Affected levels/tapes:
3052 The first condition is generally needed for all levels/tapes before version
3053 2.0.1, which might use the old behaviour before it was changed; known tapes
3054 that are affected: Tape 014 from the level set "rnd_conor_mancone".
3055 The second condition is an exception from the above case and is needed for
3056 the special case of tapes recorded with game (not engine!) version 2.0.1 or
3057 above, but before it was known that this change would break tapes like the
3058 above and was fixed in 4.2.0.0, so that the changed behaviour was active
3059 although the engine version while recording maybe was before 2.0.1. There
3060 are a lot of tapes that are affected by this exception, like tape 006 from
3061 the level set "rnd_conor_mancone".
3064 boolean use_old_move_stepsize_for_magic_wall =
3065 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
3067 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3068 tape.game_version < VERSION_IDENT(4,2,0,0)));
3071 Summary of bugfix/change:
3072 Fixed handling for custom elements that change when pushed by the player.
3074 Fixed/changed in version:
3078 Before 3.1.0, custom elements that "change when pushing" changed directly
3079 after the player started pushing them (until then handled in "DigField()").
3080 Since 3.1.0, these custom elements are not changed until the "pushing"
3081 move of the element is finished (now handled in "ContinueMoving()").
3083 Affected levels/tapes:
3084 The first condition is generally needed for all levels/tapes before version
3085 3.1.0, which might use the old behaviour before it was changed; known tapes
3086 that are affected are some tapes from the level set "Walpurgis Gardens" by
3088 The second condition is an exception from the above case and is needed for
3089 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3090 above (including some development versions of 3.1.0), but before it was
3091 known that this change would break tapes like the above and was fixed in
3092 3.1.1, so that the changed behaviour was active although the engine version
3093 while recording maybe was before 3.1.0. There is at least one tape that is
3094 affected by this exception, which is the tape for the one-level set "Bug
3095 Machine" by Juergen Bonhagen.
3098 game.use_change_when_pushing_bug =
3099 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3101 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3102 tape.game_version < VERSION_IDENT(3,1,1,0)));
3105 Summary of bugfix/change:
3106 Fixed handling for blocking the field the player leaves when moving.
3108 Fixed/changed in version:
3112 Before 3.1.1, when "block last field when moving" was enabled, the field
3113 the player is leaving when moving was blocked for the time of the move,
3114 and was directly unblocked afterwards. This resulted in the last field
3115 being blocked for exactly one less than the number of frames of one player
3116 move. Additionally, even when blocking was disabled, the last field was
3117 blocked for exactly one frame.
3118 Since 3.1.1, due to changes in player movement handling, the last field
3119 is not blocked at all when blocking is disabled. When blocking is enabled,
3120 the last field is blocked for exactly the number of frames of one player
3121 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3122 last field is blocked for exactly one more than the number of frames of
3125 Affected levels/tapes:
3126 (!!! yet to be determined -- probably many !!!)
3129 game.use_block_last_field_bug =
3130 (game.engine_version < VERSION_IDENT(3,1,1,0));
3132 /* various special flags and settings for native Emerald Mine game engine */
3134 game_em.use_single_button =
3135 (game.engine_version > VERSION_IDENT(4,0,0,2));
3137 game_em.use_push_delay =
3138 (game.engine_version > VERSION_IDENT(4,3,7,1));
3140 game_em.use_snap_key_bug =
3141 (game.engine_version < VERSION_IDENT(4,0,1,0));
3143 game_em.use_random_bug =
3144 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3146 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3148 game_em.use_old_explosions = use_old_em_engine;
3149 game_em.use_old_android = use_old_em_engine;
3150 game_em.use_old_push_elements = use_old_em_engine;
3151 game_em.use_old_push_into_acid = use_old_em_engine;
3153 game_em.use_wrap_around = !use_old_em_engine;
3155 // --------------------------------------------------------------------------
3157 // set maximal allowed number of custom element changes per game frame
3158 game.max_num_changes_per_frame = 1;
3160 // default scan direction: scan playfield from top/left to bottom/right
3161 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3163 // dynamically adjust element properties according to game engine version
3164 InitElementPropertiesEngine(game.engine_version);
3166 // ---------- initialize special element properties -------------------------
3168 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3169 if (use_amoeba_dropping_cannot_fall_bug)
3170 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3172 // ---------- initialize player's initial move delay ------------------------
3174 // dynamically adjust player properties according to level information
3175 for (i = 0; i < MAX_PLAYERS; i++)
3176 game.initial_move_delay_value[i] =
3177 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3179 // dynamically adjust player properties according to game engine version
3180 for (i = 0; i < MAX_PLAYERS; i++)
3181 game.initial_move_delay[i] =
3182 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3183 game.initial_move_delay_value[i] : 0);
3185 // ---------- initialize player's initial push delay ------------------------
3187 // dynamically adjust player properties according to game engine version
3188 game.initial_push_delay_value =
3189 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3191 // ---------- initialize changing elements ----------------------------------
3193 // initialize changing elements information
3194 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3196 struct ElementInfo *ei = &element_info[i];
3198 // this pointer might have been changed in the level editor
3199 ei->change = &ei->change_page[0];
3201 if (!IS_CUSTOM_ELEMENT(i))
3203 ei->change->target_element = EL_EMPTY_SPACE;
3204 ei->change->delay_fixed = 0;
3205 ei->change->delay_random = 0;
3206 ei->change->delay_frames = 1;
3209 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3211 ei->has_change_event[j] = FALSE;
3213 ei->event_page_nr[j] = 0;
3214 ei->event_page[j] = &ei->change_page[0];
3218 // add changing elements from pre-defined list
3219 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3221 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3222 struct ElementInfo *ei = &element_info[ch_delay->element];
3224 ei->change->target_element = ch_delay->target_element;
3225 ei->change->delay_fixed = ch_delay->change_delay;
3227 ei->change->pre_change_function = ch_delay->pre_change_function;
3228 ei->change->change_function = ch_delay->change_function;
3229 ei->change->post_change_function = ch_delay->post_change_function;
3231 ei->change->can_change = TRUE;
3232 ei->change->can_change_or_has_action = TRUE;
3234 ei->has_change_event[CE_DELAY] = TRUE;
3236 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3237 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3240 // ---------- initialize if element can trigger global animations -----------
3242 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3244 struct ElementInfo *ei = &element_info[i];
3246 ei->has_anim_event = FALSE;
3249 InitGlobalAnimEventsForCustomElements();
3251 // ---------- initialize internal run-time variables ------------------------
3253 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3255 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3257 for (j = 0; j < ei->num_change_pages; j++)
3259 ei->change_page[j].can_change_or_has_action =
3260 (ei->change_page[j].can_change |
3261 ei->change_page[j].has_action);
3265 // add change events from custom element configuration
3266 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3268 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3270 for (j = 0; j < ei->num_change_pages; j++)
3272 if (!ei->change_page[j].can_change_or_has_action)
3275 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3277 // only add event page for the first page found with this event
3278 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3280 ei->has_change_event[k] = TRUE;
3282 ei->event_page_nr[k] = j;
3283 ei->event_page[k] = &ei->change_page[j];
3289 // ---------- initialize reference elements in change conditions ------------
3291 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3293 int element = EL_CUSTOM_START + i;
3294 struct ElementInfo *ei = &element_info[element];
3296 for (j = 0; j < ei->num_change_pages; j++)
3298 int trigger_element = ei->change_page[j].initial_trigger_element;
3300 if (trigger_element >= EL_PREV_CE_8 &&
3301 trigger_element <= EL_NEXT_CE_8)
3302 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3304 ei->change_page[j].trigger_element = trigger_element;
3308 // ---------- initialize run-time trigger player and element ----------------
3310 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3312 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3314 for (j = 0; j < ei->num_change_pages; j++)
3316 struct ElementChangeInfo *change = &ei->change_page[j];
3318 change->actual_trigger_element = EL_EMPTY;
3319 change->actual_trigger_player = EL_EMPTY;
3320 change->actual_trigger_player_bits = CH_PLAYER_NONE;
3321 change->actual_trigger_side = CH_SIDE_NONE;
3322 change->actual_trigger_ce_value = 0;
3323 change->actual_trigger_ce_score = 0;
3324 change->actual_trigger_x = -1;
3325 change->actual_trigger_y = -1;
3329 // ---------- initialize trigger events -------------------------------------
3331 // initialize trigger events information
3332 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3333 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3334 trigger_events[i][j] = FALSE;
3336 // add trigger events from element change event properties
3337 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3339 struct ElementInfo *ei = &element_info[i];
3341 for (j = 0; j < ei->num_change_pages; j++)
3343 struct ElementChangeInfo *change = &ei->change_page[j];
3345 if (!change->can_change_or_has_action)
3348 if (change->has_event[CE_BY_OTHER_ACTION])
3350 int trigger_element = change->trigger_element;
3352 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3354 if (change->has_event[k])
3356 if (IS_GROUP_ELEMENT(trigger_element))
3358 struct ElementGroupInfo *group =
3359 element_info[trigger_element].group;
3361 for (l = 0; l < group->num_elements_resolved; l++)
3362 trigger_events[group->element_resolved[l]][k] = TRUE;
3364 else if (trigger_element == EL_ANY_ELEMENT)
3365 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3366 trigger_events[l][k] = TRUE;
3368 trigger_events[trigger_element][k] = TRUE;
3375 // ---------- initialize push delay -----------------------------------------
3377 // initialize push delay values to default
3378 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3380 if (!IS_CUSTOM_ELEMENT(i))
3382 // set default push delay values (corrected since version 3.0.7-1)
3383 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3385 element_info[i].push_delay_fixed = 2;
3386 element_info[i].push_delay_random = 8;
3390 element_info[i].push_delay_fixed = 8;
3391 element_info[i].push_delay_random = 8;
3396 // set push delay value for certain elements from pre-defined list
3397 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3399 int e = push_delay_list[i].element;
3401 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3402 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3405 // set push delay value for Supaplex elements for newer engine versions
3406 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3408 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3410 if (IS_SP_ELEMENT(i))
3412 // set SP push delay to just enough to push under a falling zonk
3413 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3415 element_info[i].push_delay_fixed = delay;
3416 element_info[i].push_delay_random = 0;
3421 // ---------- initialize move stepsize --------------------------------------
3423 // initialize move stepsize values to default
3424 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3425 if (!IS_CUSTOM_ELEMENT(i))
3426 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3428 // set move stepsize value for certain elements from pre-defined list
3429 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3431 int e = move_stepsize_list[i].element;
3433 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3435 // set move stepsize value for certain elements for older engine versions
3436 if (use_old_move_stepsize_for_magic_wall)
3438 if (e == EL_MAGIC_WALL_FILLING ||
3439 e == EL_MAGIC_WALL_EMPTYING ||
3440 e == EL_BD_MAGIC_WALL_FILLING ||
3441 e == EL_BD_MAGIC_WALL_EMPTYING)
3442 element_info[e].move_stepsize *= 2;
3446 // ---------- initialize collect score --------------------------------------
3448 // initialize collect score values for custom elements from initial value
3449 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3450 if (IS_CUSTOM_ELEMENT(i))
3451 element_info[i].collect_score = element_info[i].collect_score_initial;
3453 // ---------- initialize collect count --------------------------------------
3455 // initialize collect count values for non-custom elements
3456 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3457 if (!IS_CUSTOM_ELEMENT(i))
3458 element_info[i].collect_count_initial = 0;
3460 // add collect count values for all elements from pre-defined list
3461 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3462 element_info[collect_count_list[i].element].collect_count_initial =
3463 collect_count_list[i].count;
3465 // ---------- initialize access direction -----------------------------------
3467 // initialize access direction values to default (access from every side)
3468 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3469 if (!IS_CUSTOM_ELEMENT(i))
3470 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3472 // set access direction value for certain elements from pre-defined list
3473 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3474 element_info[access_direction_list[i].element].access_direction =
3475 access_direction_list[i].direction;
3477 // ---------- initialize explosion content ----------------------------------
3478 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3480 if (IS_CUSTOM_ELEMENT(i))
3483 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3485 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3487 element_info[i].content.e[x][y] =
3488 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3489 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3490 i == EL_PLAYER_3 ? EL_EMERALD :
3491 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3492 i == EL_MOLE ? EL_EMERALD_RED :
3493 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3494 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3495 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3496 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3497 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3498 i == EL_WALL_EMERALD ? EL_EMERALD :
3499 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3500 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3501 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3502 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3503 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3504 i == EL_WALL_PEARL ? EL_PEARL :
3505 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3510 // ---------- initialize recursion detection --------------------------------
3511 recursion_loop_depth = 0;
3512 recursion_loop_detected = FALSE;
3513 recursion_loop_element = EL_UNDEFINED;
3515 // ---------- initialize graphics engine ------------------------------------
3516 game.scroll_delay_value =
3517 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3518 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3519 !setup.forced_scroll_delay ? 0 :
3520 setup.scroll_delay ? setup.scroll_delay_value : 0);
3521 if (game.forced_scroll_delay_value == -1)
3522 game.scroll_delay_value =
3523 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3525 // ---------- initialize game engine snapshots ------------------------------
3526 for (i = 0; i < MAX_PLAYERS; i++)
3527 game.snapshot.last_action[i] = 0;
3528 game.snapshot.changed_action = FALSE;
3529 game.snapshot.collected_item = FALSE;
3530 game.snapshot.mode =
3531 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3532 SNAPSHOT_MODE_EVERY_STEP :
3533 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3534 SNAPSHOT_MODE_EVERY_MOVE :
3535 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3536 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3537 game.snapshot.save_snapshot = FALSE;
3539 // ---------- initialize level time for Supaplex engine ---------------------
3540 // Supaplex levels with time limit currently unsupported -- should be added
3541 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3544 // ---------- initialize flags for handling game actions --------------------
3546 // set flags for game actions to default values
3547 game.use_key_actions = TRUE;
3548 game.use_mouse_actions = FALSE;
3550 // when using Mirror Magic game engine, handle mouse events only
3551 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3553 game.use_key_actions = FALSE;
3554 game.use_mouse_actions = TRUE;
3557 // check for custom elements with mouse click events
3558 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3560 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3562 int element = EL_CUSTOM_START + i;
3564 if (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3565 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3566 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3567 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3568 game.use_mouse_actions = TRUE;
3573 static int get_num_special_action(int element, int action_first,
3576 int num_special_action = 0;
3579 for (i = action_first; i <= action_last; i++)
3581 boolean found = FALSE;
3583 for (j = 0; j < NUM_DIRECTIONS; j++)
3584 if (el_act_dir2img(element, i, j) !=
3585 el_act_dir2img(element, ACTION_DEFAULT, j))
3589 num_special_action++;
3594 return num_special_action;
3598 // ============================================================================
3600 // ----------------------------------------------------------------------------
3601 // initialize and start new game
3602 // ============================================================================
3604 #if DEBUG_INIT_PLAYER
3605 static void DebugPrintPlayerStatus(char *message)
3612 Debug("game:init:player", "%s:", message);
3614 for (i = 0; i < MAX_PLAYERS; i++)
3616 struct PlayerInfo *player = &stored_player[i];
3618 Debug("game:init:player",
3619 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3623 player->connected_locally,
3624 player->connected_network,
3626 (local_player == player ? " (local player)" : ""));
3633 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3634 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3635 int fade_mask = REDRAW_FIELD;
3636 boolean restarting = (game_status == GAME_MODE_PLAYING);
3637 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3638 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3639 int initial_move_dir = MV_DOWN;
3642 // required here to update video display before fading (FIX THIS)
3643 DrawMaskedBorder(REDRAW_DOOR_2);
3645 if (!game.restart_level)
3646 CloseDoor(DOOR_CLOSE_1);
3650 // force fading out global animations displayed during game play
3651 SetGameStatus(GAME_MODE_PSEUDO_RESTARTING);
3655 SetGameStatus(GAME_MODE_PLAYING);
3658 if (level_editor_test_game)
3659 FadeSkipNextFadeOut();
3661 FadeSetEnterScreen();
3664 fade_mask = REDRAW_ALL;
3666 FadeLevelSoundsAndMusic();
3668 ExpireSoundLoops(TRUE);
3674 // force restarting global animations displayed during game play
3675 RestartGlobalAnimsByStatus(GAME_MODE_PSEUDO_RESTARTING);
3677 // this is required for "transforming" fade modes like cross-fading
3678 // (else global animations will be stopped, but not restarted here)
3679 SetAnimStatusBeforeFading(GAME_MODE_PSEUDO_RESTARTING);
3681 SetGameStatus(GAME_MODE_PLAYING);
3684 if (level_editor_test_game)
3685 FadeSkipNextFadeIn();
3687 // needed if different viewport properties defined for playing
3688 ChangeViewportPropertiesIfNeeded();
3692 DrawCompleteVideoDisplay();
3694 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3697 InitGameControlValues();
3701 // initialize tape actions from game when recording tape
3702 tape.use_key_actions = game.use_key_actions;
3703 tape.use_mouse_actions = game.use_mouse_actions;
3705 // initialize visible playfield size when recording tape (for team mode)
3706 tape.scr_fieldx = SCR_FIELDX;
3707 tape.scr_fieldy = SCR_FIELDY;
3710 // don't play tapes over network
3711 network_playing = (network.enabled && !tape.playing);
3713 for (i = 0; i < MAX_PLAYERS; i++)
3715 struct PlayerInfo *player = &stored_player[i];
3717 player->index_nr = i;
3718 player->index_bit = (1 << i);
3719 player->element_nr = EL_PLAYER_1 + i;
3721 player->present = FALSE;
3722 player->active = FALSE;
3723 player->mapped = FALSE;
3725 player->killed = FALSE;
3726 player->reanimated = FALSE;
3727 player->buried = FALSE;
3730 player->effective_action = 0;
3731 player->programmed_action = 0;
3732 player->snap_action = 0;
3734 player->mouse_action.lx = 0;
3735 player->mouse_action.ly = 0;
3736 player->mouse_action.button = 0;
3737 player->mouse_action.button_hint = 0;
3739 player->effective_mouse_action.lx = 0;
3740 player->effective_mouse_action.ly = 0;
3741 player->effective_mouse_action.button = 0;
3742 player->effective_mouse_action.button_hint = 0;
3744 for (j = 0; j < MAX_NUM_KEYS; j++)
3745 player->key[j] = FALSE;
3747 player->num_white_keys = 0;
3749 player->dynabomb_count = 0;
3750 player->dynabomb_size = 1;
3751 player->dynabombs_left = 0;
3752 player->dynabomb_xl = FALSE;
3754 player->MovDir = initial_move_dir;
3757 player->GfxDir = initial_move_dir;
3758 player->GfxAction = ACTION_DEFAULT;
3760 player->StepFrame = 0;
3762 player->initial_element = player->element_nr;
3763 player->artwork_element =
3764 (level.use_artwork_element[i] ? level.artwork_element[i] :
3765 player->element_nr);
3766 player->use_murphy = FALSE;
3768 player->block_last_field = FALSE; // initialized in InitPlayerField()
3769 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3771 player->gravity = level.initial_player_gravity[i];
3773 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3775 player->actual_frame_counter.count = 0;
3776 player->actual_frame_counter.value = 1;
3778 player->step_counter = 0;
3780 player->last_move_dir = initial_move_dir;
3782 player->is_active = FALSE;
3784 player->is_waiting = FALSE;
3785 player->is_moving = FALSE;
3786 player->is_auto_moving = FALSE;
3787 player->is_digging = FALSE;
3788 player->is_snapping = FALSE;
3789 player->is_collecting = FALSE;
3790 player->is_pushing = FALSE;
3791 player->is_switching = FALSE;
3792 player->is_dropping = FALSE;
3793 player->is_dropping_pressed = FALSE;
3795 player->is_bored = FALSE;
3796 player->is_sleeping = FALSE;
3798 player->was_waiting = TRUE;
3799 player->was_moving = FALSE;
3800 player->was_snapping = FALSE;
3801 player->was_dropping = FALSE;
3803 player->force_dropping = FALSE;
3805 player->frame_counter_bored = -1;
3806 player->frame_counter_sleeping = -1;
3808 player->anim_delay_counter = 0;
3809 player->post_delay_counter = 0;
3811 player->dir_waiting = initial_move_dir;
3812 player->action_waiting = ACTION_DEFAULT;
3813 player->last_action_waiting = ACTION_DEFAULT;
3814 player->special_action_bored = ACTION_DEFAULT;
3815 player->special_action_sleeping = ACTION_DEFAULT;
3817 player->switch_x = -1;
3818 player->switch_y = -1;
3820 player->drop_x = -1;
3821 player->drop_y = -1;
3823 player->show_envelope = 0;
3825 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3827 player->push_delay = -1; // initialized when pushing starts
3828 player->push_delay_value = game.initial_push_delay_value;
3830 player->drop_delay = 0;
3831 player->drop_pressed_delay = 0;
3833 player->last_jx = -1;
3834 player->last_jy = -1;
3838 player->shield_normal_time_left = 0;
3839 player->shield_deadly_time_left = 0;
3841 player->last_removed_element = EL_UNDEFINED;
3843 player->inventory_infinite_element = EL_UNDEFINED;
3844 player->inventory_size = 0;
3846 if (level.use_initial_inventory[i])
3848 for (j = 0; j < level.initial_inventory_size[i]; j++)
3850 int element = level.initial_inventory_content[i][j];
3851 int collect_count = element_info[element].collect_count_initial;
3854 if (!IS_CUSTOM_ELEMENT(element))
3857 if (collect_count == 0)
3858 player->inventory_infinite_element = element;
3860 for (k = 0; k < collect_count; k++)
3861 if (player->inventory_size < MAX_INVENTORY_SIZE)
3862 player->inventory_element[player->inventory_size++] = element;
3866 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3867 SnapField(player, 0, 0);
3869 map_player_action[i] = i;
3872 network_player_action_received = FALSE;
3874 // initial null action
3875 if (network_playing)
3876 SendToServer_MovePlayer(MV_NONE);
3881 TimeLeft = level.time;
3886 ScreenMovDir = MV_NONE;
3890 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3892 game.robot_wheel_x = -1;
3893 game.robot_wheel_y = -1;
3898 game.all_players_gone = FALSE;
3900 game.LevelSolved = FALSE;
3901 game.GameOver = FALSE;
3903 game.GamePlayed = !tape.playing;
3905 game.LevelSolved_GameWon = FALSE;
3906 game.LevelSolved_GameEnd = FALSE;
3907 game.LevelSolved_SaveTape = FALSE;
3908 game.LevelSolved_SaveScore = FALSE;
3910 game.LevelSolved_CountingTime = 0;
3911 game.LevelSolved_CountingScore = 0;
3912 game.LevelSolved_CountingHealth = 0;
3914 game.RestartGameRequested = FALSE;
3916 game.panel.active = TRUE;
3918 game.no_level_time_limit = (level.time == 0);
3919 game.time_limit = (leveldir_current->time_limit && setup.time_limit);
3921 game.yamyam_content_nr = 0;
3922 game.robot_wheel_active = FALSE;
3923 game.magic_wall_active = FALSE;
3924 game.magic_wall_time_left = 0;
3925 game.light_time_left = 0;
3926 game.timegate_time_left = 0;
3927 game.switchgate_pos = 0;
3928 game.wind_direction = level.wind_direction_initial;
3930 game.time_final = 0;
3931 game.score_time_final = 0;
3934 game.score_final = 0;
3936 game.health = MAX_HEALTH;
3937 game.health_final = MAX_HEALTH;
3939 game.gems_still_needed = level.gems_needed;
3940 game.sokoban_fields_still_needed = 0;
3941 game.sokoban_objects_still_needed = 0;
3942 game.lights_still_needed = 0;
3943 game.players_still_needed = 0;
3944 game.friends_still_needed = 0;
3946 game.lenses_time_left = 0;
3947 game.magnify_time_left = 0;
3949 game.ball_active = level.ball_active_initial;
3950 game.ball_content_nr = 0;
3952 game.explosions_delayed = TRUE;
3954 game.envelope_active = FALSE;
3956 // special case: set custom artwork setting to initial value
3957 game.use_masked_elements = game.use_masked_elements_initial;
3959 for (i = 0; i < NUM_BELTS; i++)
3961 game.belt_dir[i] = MV_NONE;
3962 game.belt_dir_nr[i] = 3; // not moving, next moving left
3965 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3966 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3968 #if DEBUG_INIT_PLAYER
3969 DebugPrintPlayerStatus("Player status at level initialization");
3972 SCAN_PLAYFIELD(x, y)
3974 Tile[x][y] = Last[x][y] = level.field[x][y];
3975 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3976 ChangeDelay[x][y] = 0;
3977 ChangePage[x][y] = -1;
3978 CustomValue[x][y] = 0; // initialized in InitField()
3979 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3981 WasJustMoving[x][y] = 0;
3982 WasJustFalling[x][y] = 0;
3983 CheckCollision[x][y] = 0;
3984 CheckImpact[x][y] = 0;
3986 Pushed[x][y] = FALSE;
3988 ChangeCount[x][y] = 0;
3989 ChangeEvent[x][y] = -1;
3991 ExplodePhase[x][y] = 0;
3992 ExplodeDelay[x][y] = 0;
3993 ExplodeField[x][y] = EX_TYPE_NONE;
3995 RunnerVisit[x][y] = 0;
3996 PlayerVisit[x][y] = 0;
3999 GfxRandom[x][y] = INIT_GFX_RANDOM();
4000 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
4001 GfxElement[x][y] = EL_UNDEFINED;
4002 GfxElementEmpty[x][y] = EL_EMPTY;
4003 GfxAction[x][y] = ACTION_DEFAULT;
4004 GfxDir[x][y] = MV_NONE;
4005 GfxRedraw[x][y] = GFX_REDRAW_NONE;
4008 SCAN_PLAYFIELD(x, y)
4010 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
4012 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
4015 InitField(x, y, TRUE);
4017 ResetGfxAnimation(x, y);
4022 // required if level does not contain any "empty space" element
4023 if (element_info[EL_EMPTY].use_gfx_element)
4024 game.use_masked_elements = TRUE;
4026 for (i = 0; i < MAX_PLAYERS; i++)
4028 struct PlayerInfo *player = &stored_player[i];
4030 // set number of special actions for bored and sleeping animation
4031 player->num_special_action_bored =
4032 get_num_special_action(player->artwork_element,
4033 ACTION_BORING_1, ACTION_BORING_LAST);
4034 player->num_special_action_sleeping =
4035 get_num_special_action(player->artwork_element,
4036 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
4039 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
4040 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
4042 // initialize type of slippery elements
4043 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4045 if (!IS_CUSTOM_ELEMENT(i))
4047 // default: elements slip down either to the left or right randomly
4048 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
4050 // SP style elements prefer to slip down on the left side
4051 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
4052 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4054 // BD style elements prefer to slip down on the left side
4055 if (game.emulation == EMU_BOULDERDASH)
4056 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4060 // initialize explosion and ignition delay
4061 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4063 if (!IS_CUSTOM_ELEMENT(i))
4066 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
4067 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
4068 game.emulation == EMU_SUPAPLEX ? 3 : 2);
4069 int last_phase = (num_phase + 1) * delay;
4070 int half_phase = (num_phase / 2) * delay;
4072 element_info[i].explosion_delay = last_phase - 1;
4073 element_info[i].ignition_delay = half_phase;
4075 if (i == EL_BLACK_ORB)
4076 element_info[i].ignition_delay = 1;
4080 // correct non-moving belts to start moving left
4081 for (i = 0; i < NUM_BELTS; i++)
4082 if (game.belt_dir[i] == MV_NONE)
4083 game.belt_dir_nr[i] = 3; // not moving, next moving left
4085 #if USE_NEW_PLAYER_ASSIGNMENTS
4086 // use preferred player also in local single-player mode
4087 if (!network.enabled && !game.team_mode)
4089 int new_index_nr = setup.network_player_nr;
4091 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
4093 for (i = 0; i < MAX_PLAYERS; i++)
4094 stored_player[i].connected_locally = FALSE;
4096 stored_player[new_index_nr].connected_locally = TRUE;
4100 for (i = 0; i < MAX_PLAYERS; i++)
4102 stored_player[i].connected = FALSE;
4104 // in network game mode, the local player might not be the first player
4105 if (stored_player[i].connected_locally)
4106 local_player = &stored_player[i];
4109 if (!network.enabled)
4110 local_player->connected = TRUE;
4114 for (i = 0; i < MAX_PLAYERS; i++)
4115 stored_player[i].connected = tape.player_participates[i];
4117 else if (network.enabled)
4119 // add team mode players connected over the network (needed for correct
4120 // assignment of player figures from level to locally playing players)
4122 for (i = 0; i < MAX_PLAYERS; i++)
4123 if (stored_player[i].connected_network)
4124 stored_player[i].connected = TRUE;
4126 else if (game.team_mode)
4128 // try to guess locally connected team mode players (needed for correct
4129 // assignment of player figures from level to locally playing players)
4131 for (i = 0; i < MAX_PLAYERS; i++)
4132 if (setup.input[i].use_joystick ||
4133 setup.input[i].key.left != KSYM_UNDEFINED)
4134 stored_player[i].connected = TRUE;
4137 #if DEBUG_INIT_PLAYER
4138 DebugPrintPlayerStatus("Player status after level initialization");
4141 #if DEBUG_INIT_PLAYER
4142 Debug("game:init:player", "Reassigning players ...");
4145 // check if any connected player was not found in playfield
4146 for (i = 0; i < MAX_PLAYERS; i++)
4148 struct PlayerInfo *player = &stored_player[i];
4150 if (player->connected && !player->present)
4152 struct PlayerInfo *field_player = NULL;
4154 #if DEBUG_INIT_PLAYER
4155 Debug("game:init:player",
4156 "- looking for field player for player %d ...", i + 1);
4159 // assign first free player found that is present in the playfield
4161 // first try: look for unmapped playfield player that is not connected
4162 for (j = 0; j < MAX_PLAYERS; j++)
4163 if (field_player == NULL &&
4164 stored_player[j].present &&
4165 !stored_player[j].mapped &&
4166 !stored_player[j].connected)
4167 field_player = &stored_player[j];
4169 // second try: look for *any* unmapped playfield player
4170 for (j = 0; j < MAX_PLAYERS; j++)
4171 if (field_player == NULL &&
4172 stored_player[j].present &&
4173 !stored_player[j].mapped)
4174 field_player = &stored_player[j];
4176 if (field_player != NULL)
4178 int jx = field_player->jx, jy = field_player->jy;
4180 #if DEBUG_INIT_PLAYER
4181 Debug("game:init:player", "- found player %d",
4182 field_player->index_nr + 1);
4185 player->present = FALSE;
4186 player->active = FALSE;
4188 field_player->present = TRUE;
4189 field_player->active = TRUE;
4192 player->initial_element = field_player->initial_element;
4193 player->artwork_element = field_player->artwork_element;
4195 player->block_last_field = field_player->block_last_field;
4196 player->block_delay_adjustment = field_player->block_delay_adjustment;
4199 StorePlayer[jx][jy] = field_player->element_nr;
4201 field_player->jx = field_player->last_jx = jx;
4202 field_player->jy = field_player->last_jy = jy;
4204 if (local_player == player)
4205 local_player = field_player;
4207 map_player_action[field_player->index_nr] = i;
4209 field_player->mapped = TRUE;
4211 #if DEBUG_INIT_PLAYER
4212 Debug("game:init:player", "- map_player_action[%d] == %d",
4213 field_player->index_nr + 1, i + 1);
4218 if (player->connected && player->present)
4219 player->mapped = TRUE;
4222 #if DEBUG_INIT_PLAYER
4223 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4228 // check if any connected player was not found in playfield
4229 for (i = 0; i < MAX_PLAYERS; i++)
4231 struct PlayerInfo *player = &stored_player[i];
4233 if (player->connected && !player->present)
4235 for (j = 0; j < MAX_PLAYERS; j++)
4237 struct PlayerInfo *field_player = &stored_player[j];
4238 int jx = field_player->jx, jy = field_player->jy;
4240 // assign first free player found that is present in the playfield
4241 if (field_player->present && !field_player->connected)
4243 player->present = TRUE;
4244 player->active = TRUE;
4246 field_player->present = FALSE;
4247 field_player->active = FALSE;
4249 player->initial_element = field_player->initial_element;
4250 player->artwork_element = field_player->artwork_element;
4252 player->block_last_field = field_player->block_last_field;
4253 player->block_delay_adjustment = field_player->block_delay_adjustment;
4255 StorePlayer[jx][jy] = player->element_nr;
4257 player->jx = player->last_jx = jx;
4258 player->jy = player->last_jy = jy;
4268 Debug("game:init:player", "local_player->present == %d",
4269 local_player->present);
4272 // set focus to local player for network games, else to all players
4273 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4274 game.centered_player_nr_next = game.centered_player_nr;
4275 game.set_centered_player = FALSE;
4276 game.set_centered_player_wrap = FALSE;
4278 if (network_playing && tape.recording)
4280 // store client dependent player focus when recording network games
4281 tape.centered_player_nr_next = game.centered_player_nr_next;
4282 tape.set_centered_player = TRUE;
4287 // when playing a tape, eliminate all players who do not participate
4289 #if USE_NEW_PLAYER_ASSIGNMENTS
4291 if (!game.team_mode)
4293 for (i = 0; i < MAX_PLAYERS; i++)
4295 if (stored_player[i].active &&
4296 !tape.player_participates[map_player_action[i]])
4298 struct PlayerInfo *player = &stored_player[i];
4299 int jx = player->jx, jy = player->jy;
4301 #if DEBUG_INIT_PLAYER
4302 Debug("game:init:player", "Removing player %d at (%d, %d)",
4306 player->active = FALSE;
4307 StorePlayer[jx][jy] = 0;
4308 Tile[jx][jy] = EL_EMPTY;
4315 for (i = 0; i < MAX_PLAYERS; i++)
4317 if (stored_player[i].active &&
4318 !tape.player_participates[i])
4320 struct PlayerInfo *player = &stored_player[i];
4321 int jx = player->jx, jy = player->jy;
4323 player->active = FALSE;
4324 StorePlayer[jx][jy] = 0;
4325 Tile[jx][jy] = EL_EMPTY;
4330 else if (!network.enabled && !game.team_mode) // && !tape.playing
4332 // when in single player mode, eliminate all but the local player
4334 for (i = 0; i < MAX_PLAYERS; i++)
4336 struct PlayerInfo *player = &stored_player[i];
4338 if (player->active && player != local_player)
4340 int jx = player->jx, jy = player->jy;
4342 player->active = FALSE;
4343 player->present = FALSE;
4345 StorePlayer[jx][jy] = 0;
4346 Tile[jx][jy] = EL_EMPTY;
4351 for (i = 0; i < MAX_PLAYERS; i++)
4352 if (stored_player[i].active)
4353 game.players_still_needed++;
4355 if (level.solved_by_one_player)
4356 game.players_still_needed = 1;
4358 // when recording the game, store which players take part in the game
4361 #if USE_NEW_PLAYER_ASSIGNMENTS
4362 for (i = 0; i < MAX_PLAYERS; i++)
4363 if (stored_player[i].connected)
4364 tape.player_participates[i] = TRUE;
4366 for (i = 0; i < MAX_PLAYERS; i++)
4367 if (stored_player[i].active)
4368 tape.player_participates[i] = TRUE;
4372 #if DEBUG_INIT_PLAYER
4373 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4376 if (BorderElement == EL_EMPTY)
4379 SBX_Right = lev_fieldx - SCR_FIELDX;
4381 SBY_Lower = lev_fieldy - SCR_FIELDY;
4386 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4388 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4391 if (full_lev_fieldx <= SCR_FIELDX)
4392 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4393 if (full_lev_fieldy <= SCR_FIELDY)
4394 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4396 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4398 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4401 // if local player not found, look for custom element that might create
4402 // the player (make some assumptions about the right custom element)
4403 if (!local_player->present)
4405 int start_x = 0, start_y = 0;
4406 int found_rating = 0;
4407 int found_element = EL_UNDEFINED;
4408 int player_nr = local_player->index_nr;
4410 SCAN_PLAYFIELD(x, y)
4412 int element = Tile[x][y];
4417 if (level.use_start_element[player_nr] &&
4418 level.start_element[player_nr] == element &&
4425 found_element = element;
4428 if (!IS_CUSTOM_ELEMENT(element))
4431 if (CAN_CHANGE(element))
4433 for (i = 0; i < element_info[element].num_change_pages; i++)
4435 // check for player created from custom element as single target
4436 content = element_info[element].change_page[i].target_element;
4437 is_player = IS_PLAYER_ELEMENT(content);
4439 if (is_player && (found_rating < 3 ||
4440 (found_rating == 3 && element < found_element)))
4446 found_element = element;
4451 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4453 // check for player created from custom element as explosion content
4454 content = element_info[element].content.e[xx][yy];
4455 is_player = IS_PLAYER_ELEMENT(content);
4457 if (is_player && (found_rating < 2 ||
4458 (found_rating == 2 && element < found_element)))
4460 start_x = x + xx - 1;
4461 start_y = y + yy - 1;
4464 found_element = element;
4467 if (!CAN_CHANGE(element))
4470 for (i = 0; i < element_info[element].num_change_pages; i++)
4472 // check for player created from custom element as extended target
4474 element_info[element].change_page[i].target_content.e[xx][yy];
4476 is_player = IS_PLAYER_ELEMENT(content);
4478 if (is_player && (found_rating < 1 ||
4479 (found_rating == 1 && element < found_element)))
4481 start_x = x + xx - 1;
4482 start_y = y + yy - 1;
4485 found_element = element;
4491 scroll_x = SCROLL_POSITION_X(start_x);
4492 scroll_y = SCROLL_POSITION_Y(start_y);
4496 scroll_x = SCROLL_POSITION_X(local_player->jx);
4497 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4500 if (game.forced_scroll_x != ARG_UNDEFINED_VALUE)
4501 scroll_x = game.forced_scroll_x;
4502 if (game.forced_scroll_y != ARG_UNDEFINED_VALUE)
4503 scroll_y = game.forced_scroll_y;
4505 // !!! FIX THIS (START) !!!
4506 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4508 InitGameEngine_EM();
4510 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4512 InitGameEngine_SP();
4514 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4516 InitGameEngine_MM();
4520 DrawLevel(REDRAW_FIELD);
4523 // after drawing the level, correct some elements
4524 if (game.timegate_time_left == 0)
4525 CloseAllOpenTimegates();
4528 // blit playfield from scroll buffer to normal back buffer for fading in
4529 BlitScreenToBitmap(backbuffer);
4530 // !!! FIX THIS (END) !!!
4532 DrawMaskedBorder(fade_mask);
4537 // full screen redraw is required at this point in the following cases:
4538 // - special editor door undrawn when game was started from level editor
4539 // - drawing area (playfield) was changed and has to be removed completely
4540 redraw_mask = REDRAW_ALL;
4544 if (!game.restart_level)
4546 // copy default game door content to main double buffer
4548 // !!! CHECK AGAIN !!!
4549 SetPanelBackground();
4550 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4551 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4554 SetPanelBackground();
4555 SetDrawBackgroundMask(REDRAW_DOOR_1);
4557 UpdateAndDisplayGameControlValues();
4559 if (!game.restart_level)
4565 CreateGameButtons();
4570 // copy actual game door content to door double buffer for OpenDoor()
4571 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4573 OpenDoor(DOOR_OPEN_ALL);
4575 KeyboardAutoRepeatOffUnlessAutoplay();
4577 #if DEBUG_INIT_PLAYER
4578 DebugPrintPlayerStatus("Player status (final)");
4587 if (!game.restart_level && !tape.playing)
4589 LevelStats_incPlayed(level_nr);
4591 SaveLevelSetup_SeriesInfo();
4594 game.restart_level = FALSE;
4595 game.request_active = FALSE;
4597 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4598 InitGameActions_MM();
4600 SaveEngineSnapshotToListInitial();
4602 if (!game.restart_level)
4604 PlaySound(SND_GAME_STARTING);
4606 if (setup.sound_music)
4610 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4613 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4614 int actual_player_x, int actual_player_y)
4616 // this is used for non-R'n'D game engines to update certain engine values
4618 // needed to determine if sounds are played within the visible screen area
4619 scroll_x = actual_scroll_x;
4620 scroll_y = actual_scroll_y;
4622 // needed to get player position for "follow finger" playing input method
4623 local_player->jx = actual_player_x;
4624 local_player->jy = actual_player_y;
4627 void InitMovDir(int x, int y)
4629 int i, element = Tile[x][y];
4630 static int xy[4][2] =
4637 static int direction[3][4] =
4639 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4640 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4641 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4650 Tile[x][y] = EL_BUG;
4651 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4654 case EL_SPACESHIP_RIGHT:
4655 case EL_SPACESHIP_UP:
4656 case EL_SPACESHIP_LEFT:
4657 case EL_SPACESHIP_DOWN:
4658 Tile[x][y] = EL_SPACESHIP;
4659 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4662 case EL_BD_BUTTERFLY_RIGHT:
4663 case EL_BD_BUTTERFLY_UP:
4664 case EL_BD_BUTTERFLY_LEFT:
4665 case EL_BD_BUTTERFLY_DOWN:
4666 Tile[x][y] = EL_BD_BUTTERFLY;
4667 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4670 case EL_BD_FIREFLY_RIGHT:
4671 case EL_BD_FIREFLY_UP:
4672 case EL_BD_FIREFLY_LEFT:
4673 case EL_BD_FIREFLY_DOWN:
4674 Tile[x][y] = EL_BD_FIREFLY;
4675 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4678 case EL_PACMAN_RIGHT:
4680 case EL_PACMAN_LEFT:
4681 case EL_PACMAN_DOWN:
4682 Tile[x][y] = EL_PACMAN;
4683 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4686 case EL_YAMYAM_LEFT:
4687 case EL_YAMYAM_RIGHT:
4689 case EL_YAMYAM_DOWN:
4690 Tile[x][y] = EL_YAMYAM;
4691 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4694 case EL_SP_SNIKSNAK:
4695 MovDir[x][y] = MV_UP;
4698 case EL_SP_ELECTRON:
4699 MovDir[x][y] = MV_LEFT;
4706 Tile[x][y] = EL_MOLE;
4707 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4710 case EL_SPRING_LEFT:
4711 case EL_SPRING_RIGHT:
4712 Tile[x][y] = EL_SPRING;
4713 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4717 if (IS_CUSTOM_ELEMENT(element))
4719 struct ElementInfo *ei = &element_info[element];
4720 int move_direction_initial = ei->move_direction_initial;
4721 int move_pattern = ei->move_pattern;
4723 if (move_direction_initial == MV_START_PREVIOUS)
4725 if (MovDir[x][y] != MV_NONE)
4728 move_direction_initial = MV_START_AUTOMATIC;
4731 if (move_direction_initial == MV_START_RANDOM)
4732 MovDir[x][y] = 1 << RND(4);
4733 else if (move_direction_initial & MV_ANY_DIRECTION)
4734 MovDir[x][y] = move_direction_initial;
4735 else if (move_pattern == MV_ALL_DIRECTIONS ||
4736 move_pattern == MV_TURNING_LEFT ||
4737 move_pattern == MV_TURNING_RIGHT ||
4738 move_pattern == MV_TURNING_LEFT_RIGHT ||
4739 move_pattern == MV_TURNING_RIGHT_LEFT ||
4740 move_pattern == MV_TURNING_RANDOM)
4741 MovDir[x][y] = 1 << RND(4);
4742 else if (move_pattern == MV_HORIZONTAL)
4743 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4744 else if (move_pattern == MV_VERTICAL)
4745 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4746 else if (move_pattern & MV_ANY_DIRECTION)
4747 MovDir[x][y] = element_info[element].move_pattern;
4748 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4749 move_pattern == MV_ALONG_RIGHT_SIDE)
4751 // use random direction as default start direction
4752 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4753 MovDir[x][y] = 1 << RND(4);
4755 for (i = 0; i < NUM_DIRECTIONS; i++)
4757 int x1 = x + xy[i][0];
4758 int y1 = y + xy[i][1];
4760 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4762 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4763 MovDir[x][y] = direction[0][i];
4765 MovDir[x][y] = direction[1][i];
4774 MovDir[x][y] = 1 << RND(4);
4776 if (element != EL_BUG &&
4777 element != EL_SPACESHIP &&
4778 element != EL_BD_BUTTERFLY &&
4779 element != EL_BD_FIREFLY)
4782 for (i = 0; i < NUM_DIRECTIONS; i++)
4784 int x1 = x + xy[i][0];
4785 int y1 = y + xy[i][1];
4787 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4789 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4791 MovDir[x][y] = direction[0][i];
4794 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4795 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4797 MovDir[x][y] = direction[1][i];
4806 GfxDir[x][y] = MovDir[x][y];
4809 void InitAmoebaNr(int x, int y)
4812 int group_nr = AmoebaNeighbourNr(x, y);
4816 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4818 if (AmoebaCnt[i] == 0)
4826 AmoebaNr[x][y] = group_nr;
4827 AmoebaCnt[group_nr]++;
4828 AmoebaCnt2[group_nr]++;
4831 static void LevelSolved_SetFinalGameValues(void)
4833 game.time_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.time_played :
4834 game.no_level_time_limit ? TimePlayed : TimeLeft);
4835 game.score_time_final = (level.use_step_counter ? TimePlayed :
4836 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4838 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.score :
4839 level.game_engine_type == GAME_ENGINE_TYPE_EM ? game_em.lev->score :
4840 level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.score :
4843 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4844 MM_HEALTH(game_mm.laser_overload_value) :
4847 game.LevelSolved_CountingTime = game.time_final;
4848 game.LevelSolved_CountingScore = game.score_final;
4849 game.LevelSolved_CountingHealth = game.health_final;
4852 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4854 game.LevelSolved_CountingTime = time;
4855 game.LevelSolved_CountingScore = score;
4856 game.LevelSolved_CountingHealth = health;
4858 game_panel_controls[GAME_PANEL_TIME].value = time;
4859 game_panel_controls[GAME_PANEL_SCORE].value = score;
4860 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4862 DisplayGameControlValues();
4865 static void LevelSolved(void)
4867 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4868 game.players_still_needed > 0)
4871 game.LevelSolved = TRUE;
4872 game.GameOver = TRUE;
4876 // needed here to display correct panel values while player walks into exit
4877 LevelSolved_SetFinalGameValues();
4882 static int time_count_steps;
4883 static int time, time_final;
4884 static float score, score_final; // needed for time score < 10 for 10 seconds
4885 static int health, health_final;
4886 static int game_over_delay_1 = 0;
4887 static int game_over_delay_2 = 0;
4888 static int game_over_delay_3 = 0;
4889 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4890 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4892 if (!game.LevelSolved_GameWon)
4896 // do not start end game actions before the player stops moving (to exit)
4897 if (local_player->active && local_player->MovPos)
4900 // calculate final game values after player finished walking into exit
4901 LevelSolved_SetFinalGameValues();
4903 game.LevelSolved_GameWon = TRUE;
4904 game.LevelSolved_SaveTape = tape.recording;
4905 game.LevelSolved_SaveScore = !tape.playing;
4909 LevelStats_incSolved(level_nr);
4911 SaveLevelSetup_SeriesInfo();
4914 if (tape.auto_play) // tape might already be stopped here
4915 tape.auto_play_level_solved = TRUE;
4919 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4920 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4921 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4923 time = time_final = game.time_final;
4924 score = score_final = game.score_final;
4925 health = health_final = game.health_final;
4927 // update game panel values before (delayed) counting of score (if any)
4928 LevelSolved_DisplayFinalGameValues(time, score, health);
4930 // if level has time score defined, calculate new final game values
4933 int time_final_max = 999;
4934 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4935 int time_frames = 0;
4936 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4937 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4942 time_frames = time_frames_left;
4944 else if (game.no_level_time_limit && TimePlayed < time_final_max)
4946 time_final = time_final_max;
4947 time_frames = time_frames_final_max - time_frames_played;
4950 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4952 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4954 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
4956 // keep previous values (final values already processed here)
4958 score_final = score;
4960 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4963 score_final += health * time_score;
4966 game.score_final = score_final;
4967 game.health_final = health_final;
4970 // if not counting score after game, immediately update game panel values
4971 if (level_editor_test_game || !setup.count_score_after_game)
4974 score = score_final;
4976 LevelSolved_DisplayFinalGameValues(time, score, health);
4979 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4981 // check if last player has left the level
4982 if (game.exit_x >= 0 &&
4985 int x = game.exit_x;
4986 int y = game.exit_y;
4987 int element = Tile[x][y];
4989 // close exit door after last player
4990 if ((game.all_players_gone &&
4991 (element == EL_EXIT_OPEN ||
4992 element == EL_SP_EXIT_OPEN ||
4993 element == EL_STEEL_EXIT_OPEN)) ||
4994 element == EL_EM_EXIT_OPEN ||
4995 element == EL_EM_STEEL_EXIT_OPEN)
4999 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
5000 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
5001 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
5002 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
5003 EL_EM_STEEL_EXIT_CLOSING);
5005 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
5008 // player disappears
5009 DrawLevelField(x, y);
5012 for (i = 0; i < MAX_PLAYERS; i++)
5014 struct PlayerInfo *player = &stored_player[i];
5016 if (player->present)
5018 RemovePlayer(player);
5020 // player disappears
5021 DrawLevelField(player->jx, player->jy);
5026 PlaySound(SND_GAME_WINNING);
5029 if (setup.count_score_after_game)
5031 if (time != time_final)
5033 if (game_over_delay_1 > 0)
5035 game_over_delay_1--;
5040 int time_to_go = ABS(time_final - time);
5041 int time_count_dir = (time < time_final ? +1 : -1);
5043 if (time_to_go < time_count_steps)
5044 time_count_steps = 1;
5046 time += time_count_steps * time_count_dir;
5047 score += time_count_steps * time_score;
5049 // set final score to correct rounding differences after counting score
5050 if (time == time_final)
5051 score = score_final;
5053 LevelSolved_DisplayFinalGameValues(time, score, health);
5055 if (time == time_final)
5056 StopSound(SND_GAME_LEVELTIME_BONUS);
5057 else if (setup.sound_loops)
5058 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5060 PlaySound(SND_GAME_LEVELTIME_BONUS);
5065 if (health != health_final)
5067 if (game_over_delay_2 > 0)
5069 game_over_delay_2--;
5074 int health_count_dir = (health < health_final ? +1 : -1);
5076 health += health_count_dir;
5077 score += time_score;
5079 LevelSolved_DisplayFinalGameValues(time, score, health);
5081 if (health == health_final)
5082 StopSound(SND_GAME_LEVELTIME_BONUS);
5083 else if (setup.sound_loops)
5084 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5086 PlaySound(SND_GAME_LEVELTIME_BONUS);
5092 game.panel.active = FALSE;
5094 if (game_over_delay_3 > 0)
5096 game_over_delay_3--;
5106 // used instead of "level_nr" (needed for network games)
5107 int last_level_nr = levelset.level_nr;
5108 boolean tape_saved = FALSE;
5110 game.LevelSolved_GameEnd = TRUE;
5112 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5114 // make sure that request dialog to save tape does not open door again
5115 if (!global.use_envelope_request)
5116 CloseDoor(DOOR_CLOSE_1);
5119 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5121 // set unique basename for score tape (also saved in high score table)
5122 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5125 // if no tape is to be saved, close both doors simultaneously
5126 CloseDoor(DOOR_CLOSE_ALL);
5128 if (level_editor_test_game || score_info_tape_play)
5130 SetGameStatus(GAME_MODE_MAIN);
5137 if (!game.LevelSolved_SaveScore)
5139 SetGameStatus(GAME_MODE_MAIN);
5146 if (level_nr == leveldir_current->handicap_level)
5148 leveldir_current->handicap_level++;
5150 SaveLevelSetup_SeriesInfo();
5153 // save score and score tape before potentially erasing tape below
5154 NewHighScore(last_level_nr, tape_saved);
5156 if (setup.increment_levels &&
5157 level_nr < leveldir_current->last_level &&
5160 level_nr++; // advance to next level
5161 TapeErase(); // start with empty tape
5163 if (setup.auto_play_next_level)
5165 scores.continue_playing = TRUE;
5166 scores.next_level_nr = level_nr;
5168 LoadLevel(level_nr);
5170 SaveLevelSetup_SeriesInfo();
5174 if (scores.last_added >= 0 && setup.show_scores_after_game)
5176 SetGameStatus(GAME_MODE_SCORES);
5178 DrawHallOfFame(last_level_nr);
5180 else if (scores.continue_playing)
5182 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5186 SetGameStatus(GAME_MODE_MAIN);
5192 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5193 boolean one_score_entry_per_name)
5197 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5200 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5202 struct ScoreEntry *entry = &list->entry[i];
5203 boolean score_is_better = (new_entry->score > entry->score);
5204 boolean score_is_equal = (new_entry->score == entry->score);
5205 boolean time_is_better = (new_entry->time < entry->time);
5206 boolean time_is_equal = (new_entry->time == entry->time);
5207 boolean better_by_score = (score_is_better ||
5208 (score_is_equal && time_is_better));
5209 boolean better_by_time = (time_is_better ||
5210 (time_is_equal && score_is_better));
5211 boolean is_better = (level.rate_time_over_score ? better_by_time :
5213 boolean entry_is_empty = (entry->score == 0 &&
5216 // prevent adding server score entries if also existing in local score file
5217 // (special case: historic score entries have an empty tape basename entry)
5218 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5219 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5221 // add fields from server score entry not stored in local score entry
5222 // (currently, this means setting platform, version and country fields;
5223 // in rare cases, this may also correct an invalid score value, as
5224 // historic scores might have been truncated to 16-bit values locally)
5225 *entry = *new_entry;
5230 if (is_better || entry_is_empty)
5232 // player has made it to the hall of fame
5234 if (i < MAX_SCORE_ENTRIES - 1)
5236 int m = MAX_SCORE_ENTRIES - 1;
5239 if (one_score_entry_per_name)
5241 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5242 if (strEqual(list->entry[l].name, new_entry->name))
5245 if (m == i) // player's new highscore overwrites his old one
5249 for (l = m; l > i; l--)
5250 list->entry[l] = list->entry[l - 1];
5255 *entry = *new_entry;
5259 else if (one_score_entry_per_name &&
5260 strEqual(entry->name, new_entry->name))
5262 // player already in high score list with better score or time
5268 // special case: new score is beyond the last high score list position
5269 return MAX_SCORE_ENTRIES;
5272 void NewHighScore(int level_nr, boolean tape_saved)
5274 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5275 boolean one_per_name = FALSE;
5277 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5278 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5280 new_entry.score = game.score_final;
5281 new_entry.time = game.score_time_final;
5283 LoadScore(level_nr);
5285 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5287 if (scores.last_added >= MAX_SCORE_ENTRIES)
5289 scores.last_added = MAX_SCORE_ENTRIES - 1;
5290 scores.force_last_added = TRUE;
5292 scores.entry[scores.last_added] = new_entry;
5294 // store last added local score entry (before merging server scores)
5295 scores.last_added_local = scores.last_added;
5300 if (scores.last_added < 0)
5303 SaveScore(level_nr);
5305 // store last added local score entry (before merging server scores)
5306 scores.last_added_local = scores.last_added;
5308 if (!game.LevelSolved_SaveTape)
5311 SaveScoreTape(level_nr);
5313 if (setup.ask_for_using_api_server)
5315 setup.use_api_server =
5316 Request("Upload your score and tape to the high score server?", REQ_ASK);
5318 if (!setup.use_api_server)
5319 Request("Not using high score server! Use setup menu to enable again!",
5322 runtime.use_api_server = setup.use_api_server;
5324 // after asking for using API server once, do not ask again
5325 setup.ask_for_using_api_server = FALSE;
5327 SaveSetup_ServerSetup();
5330 SaveServerScore(level_nr, tape_saved);
5333 void MergeServerScore(void)
5335 struct ScoreEntry last_added_entry;
5336 boolean one_per_name = FALSE;
5339 if (scores.last_added >= 0)
5340 last_added_entry = scores.entry[scores.last_added];
5342 for (i = 0; i < server_scores.num_entries; i++)
5344 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5346 if (pos >= 0 && pos <= scores.last_added)
5347 scores.last_added++;
5350 if (scores.last_added >= MAX_SCORE_ENTRIES)
5352 scores.last_added = MAX_SCORE_ENTRIES - 1;
5353 scores.force_last_added = TRUE;
5355 scores.entry[scores.last_added] = last_added_entry;
5359 static int getElementMoveStepsizeExt(int x, int y, int direction)
5361 int element = Tile[x][y];
5362 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5363 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5364 int horiz_move = (dx != 0);
5365 int sign = (horiz_move ? dx : dy);
5366 int step = sign * element_info[element].move_stepsize;
5368 // special values for move stepsize for spring and things on conveyor belt
5371 if (CAN_FALL(element) &&
5372 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5373 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5374 else if (element == EL_SPRING)
5375 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5381 static int getElementMoveStepsize(int x, int y)
5383 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5386 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5388 if (player->GfxAction != action || player->GfxDir != dir)
5390 player->GfxAction = action;
5391 player->GfxDir = dir;
5393 player->StepFrame = 0;
5397 static void ResetGfxFrame(int x, int y)
5399 // profiling showed that "autotest" spends 10~20% of its time in this function
5400 if (DrawingDeactivatedField())
5403 int element = Tile[x][y];
5404 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5406 if (graphic_info[graphic].anim_global_sync)
5407 GfxFrame[x][y] = FrameCounter;
5408 else if (graphic_info[graphic].anim_global_anim_sync)
5409 GfxFrame[x][y] = getGlobalAnimSyncFrame();
5410 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5411 GfxFrame[x][y] = CustomValue[x][y];
5412 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5413 GfxFrame[x][y] = element_info[element].collect_score;
5414 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5415 GfxFrame[x][y] = ChangeDelay[x][y];
5418 static void ResetGfxAnimation(int x, int y)
5420 GfxAction[x][y] = ACTION_DEFAULT;
5421 GfxDir[x][y] = MovDir[x][y];
5424 ResetGfxFrame(x, y);
5427 static void ResetRandomAnimationValue(int x, int y)
5429 GfxRandom[x][y] = INIT_GFX_RANDOM();
5432 static void InitMovingField(int x, int y, int direction)
5434 int element = Tile[x][y];
5435 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5436 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5439 boolean is_moving_before, is_moving_after;
5441 // check if element was/is moving or being moved before/after mode change
5442 is_moving_before = (WasJustMoving[x][y] != 0);
5443 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5445 // reset animation only for moving elements which change direction of moving
5446 // or which just started or stopped moving
5447 // (else CEs with property "can move" / "not moving" are reset each frame)
5448 if (is_moving_before != is_moving_after ||
5449 direction != MovDir[x][y])
5450 ResetGfxAnimation(x, y);
5452 MovDir[x][y] = direction;
5453 GfxDir[x][y] = direction;
5455 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5456 direction == MV_DOWN && CAN_FALL(element) ?
5457 ACTION_FALLING : ACTION_MOVING);
5459 // this is needed for CEs with property "can move" / "not moving"
5461 if (is_moving_after)
5463 if (Tile[newx][newy] == EL_EMPTY)
5464 Tile[newx][newy] = EL_BLOCKED;
5466 MovDir[newx][newy] = MovDir[x][y];
5468 CustomValue[newx][newy] = CustomValue[x][y];
5470 GfxFrame[newx][newy] = GfxFrame[x][y];
5471 GfxRandom[newx][newy] = GfxRandom[x][y];
5472 GfxAction[newx][newy] = GfxAction[x][y];
5473 GfxDir[newx][newy] = GfxDir[x][y];
5477 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5479 int direction = MovDir[x][y];
5480 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5481 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5487 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5489 int direction = MovDir[x][y];
5490 int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0);
5491 int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0);
5493 *comes_from_x = oldx;
5494 *comes_from_y = oldy;
5497 static int MovingOrBlocked2Element(int x, int y)
5499 int element = Tile[x][y];
5501 if (element == EL_BLOCKED)
5505 Blocked2Moving(x, y, &oldx, &oldy);
5507 return Tile[oldx][oldy];
5513 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5515 // like MovingOrBlocked2Element(), but if element is moving
5516 // and (x, y) is the field the moving element is just leaving,
5517 // return EL_BLOCKED instead of the element value
5518 int element = Tile[x][y];
5520 if (IS_MOVING(x, y))
5522 if (element == EL_BLOCKED)
5526 Blocked2Moving(x, y, &oldx, &oldy);
5527 return Tile[oldx][oldy];
5536 static void RemoveField(int x, int y)
5538 Tile[x][y] = EL_EMPTY;
5544 CustomValue[x][y] = 0;
5547 ChangeDelay[x][y] = 0;
5548 ChangePage[x][y] = -1;
5549 Pushed[x][y] = FALSE;
5551 GfxElement[x][y] = EL_UNDEFINED;
5552 GfxAction[x][y] = ACTION_DEFAULT;
5553 GfxDir[x][y] = MV_NONE;
5556 static void RemoveMovingField(int x, int y)
5558 int oldx = x, oldy = y, newx = x, newy = y;
5559 int element = Tile[x][y];
5560 int next_element = EL_UNDEFINED;
5562 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5565 if (IS_MOVING(x, y))
5567 Moving2Blocked(x, y, &newx, &newy);
5569 if (Tile[newx][newy] != EL_BLOCKED)
5571 // element is moving, but target field is not free (blocked), but
5572 // already occupied by something different (example: acid pool);
5573 // in this case, only remove the moving field, but not the target
5575 RemoveField(oldx, oldy);
5577 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5579 TEST_DrawLevelField(oldx, oldy);
5584 else if (element == EL_BLOCKED)
5586 Blocked2Moving(x, y, &oldx, &oldy);
5587 if (!IS_MOVING(oldx, oldy))
5591 if (element == EL_BLOCKED &&
5592 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5593 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5594 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5595 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5596 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5597 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5598 next_element = get_next_element(Tile[oldx][oldy]);
5600 RemoveField(oldx, oldy);
5601 RemoveField(newx, newy);
5603 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5605 if (next_element != EL_UNDEFINED)
5606 Tile[oldx][oldy] = next_element;
5608 TEST_DrawLevelField(oldx, oldy);
5609 TEST_DrawLevelField(newx, newy);
5612 void DrawDynamite(int x, int y)
5614 int sx = SCREENX(x), sy = SCREENY(y);
5615 int graphic = el2img(Tile[x][y]);
5618 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5621 if (IS_WALKABLE_INSIDE(Back[x][y]))
5625 DrawLevelElement(x, y, Back[x][y]);
5626 else if (Store[x][y])
5627 DrawLevelElement(x, y, Store[x][y]);
5628 else if (game.use_masked_elements)
5629 DrawLevelElement(x, y, EL_EMPTY);
5631 frame = getGraphicAnimationFrameXY(graphic, x, y);
5633 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5634 DrawGraphicThruMask(sx, sy, graphic, frame);
5636 DrawGraphic(sx, sy, graphic, frame);
5639 static void CheckDynamite(int x, int y)
5641 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5645 if (MovDelay[x][y] != 0)
5648 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5654 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5659 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5661 boolean num_checked_players = 0;
5664 for (i = 0; i < MAX_PLAYERS; i++)
5666 if (stored_player[i].active)
5668 int sx = stored_player[i].jx;
5669 int sy = stored_player[i].jy;
5671 if (num_checked_players == 0)
5678 *sx1 = MIN(*sx1, sx);
5679 *sy1 = MIN(*sy1, sy);
5680 *sx2 = MAX(*sx2, sx);
5681 *sy2 = MAX(*sy2, sy);
5684 num_checked_players++;
5689 static boolean checkIfAllPlayersFitToScreen_RND(void)
5691 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5693 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5695 return (sx2 - sx1 < SCR_FIELDX &&
5696 sy2 - sy1 < SCR_FIELDY);
5699 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5701 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5703 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5705 *sx = (sx1 + sx2) / 2;
5706 *sy = (sy1 + sy2) / 2;
5709 static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
5710 boolean center_screen, boolean quick_relocation)
5712 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5713 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5714 boolean no_delay = (tape.warp_forward);
5715 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5716 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5717 int new_scroll_x, new_scroll_y;
5719 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5721 // case 1: quick relocation inside visible screen (without scrolling)
5728 if (!level.shifted_relocation || center_screen)
5730 // relocation _with_ centering of screen
5732 new_scroll_x = SCROLL_POSITION_X(x);
5733 new_scroll_y = SCROLL_POSITION_Y(y);
5737 // relocation _without_ centering of screen
5739 // apply distance between old and new player position to scroll position
5740 int shifted_scroll_x = scroll_x + (x - old_x);
5741 int shifted_scroll_y = scroll_y + (y - old_y);
5743 // make sure that shifted scroll position does not scroll beyond screen
5744 new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
5745 new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
5747 // special case for teleporting from one end of the playfield to the other
5748 // (this kludge prevents the destination area to be shifted by half a tile
5749 // against the source destination for even screen width or screen height;
5750 // probably most useful when used with high "game.forced_scroll_delay_value"
5751 // in combination with "game.forced_scroll_x" and "game.forced_scroll_y")
5752 if (quick_relocation)
5754 if (EVEN(SCR_FIELDX))
5756 // relocate (teleport) between left and right border (half or full)
5757 if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1)
5758 new_scroll_x = SBX_Right;
5759 else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right)
5760 new_scroll_x = SBX_Right - 1;
5761 else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1)
5762 new_scroll_x = SBX_Left;
5763 else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left)
5764 new_scroll_x = SBX_Left + 1;
5767 if (EVEN(SCR_FIELDY))
5769 // relocate (teleport) between top and bottom border (half or full)
5770 if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1)
5771 new_scroll_y = SBY_Lower;
5772 else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower)
5773 new_scroll_y = SBY_Lower - 1;
5774 else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1)
5775 new_scroll_y = SBY_Upper;
5776 else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper)
5777 new_scroll_y = SBY_Upper + 1;
5782 if (quick_relocation)
5784 // case 2: quick relocation (redraw without visible scrolling)
5786 scroll_x = new_scroll_x;
5787 scroll_y = new_scroll_y;
5794 // case 3: visible relocation (with scrolling to new position)
5796 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5798 SetVideoFrameDelay(wait_delay_value);
5800 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5802 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5803 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5805 if (dx == 0 && dy == 0) // no scrolling needed at all
5811 // set values for horizontal/vertical screen scrolling (half tile size)
5812 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5813 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5814 int pos_x = dx * TILEX / 2;
5815 int pos_y = dy * TILEY / 2;
5816 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5817 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5819 ScrollLevel(dx, dy);
5822 // scroll in two steps of half tile size to make things smoother
5823 BlitScreenToBitmapExt_RND(window, fx, fy);
5825 // scroll second step to align at full tile size
5826 BlitScreenToBitmap(window);
5832 SetVideoFrameDelay(frame_delay_value_old);
5835 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5837 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5838 int player_nr = GET_PLAYER_NR(el_player);
5839 struct PlayerInfo *player = &stored_player[player_nr];
5840 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5841 boolean no_delay = (tape.warp_forward);
5842 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5843 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5844 int old_jx = player->jx;
5845 int old_jy = player->jy;
5846 int old_element = Tile[old_jx][old_jy];
5847 int element = Tile[jx][jy];
5848 boolean player_relocated = (old_jx != jx || old_jy != jy);
5850 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5851 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5852 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5853 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5854 int leave_side_horiz = move_dir_horiz;
5855 int leave_side_vert = move_dir_vert;
5856 int enter_side = enter_side_horiz | enter_side_vert;
5857 int leave_side = leave_side_horiz | leave_side_vert;
5859 if (player->buried) // do not reanimate dead player
5862 if (!player_relocated) // no need to relocate the player
5865 if (IS_PLAYER(jx, jy)) // player already placed at new position
5867 RemoveField(jx, jy); // temporarily remove newly placed player
5868 DrawLevelField(jx, jy);
5871 if (player->present)
5873 while (player->MovPos)
5875 ScrollPlayer(player, SCROLL_GO_ON);
5876 ScrollScreen(NULL, SCROLL_GO_ON);
5878 AdvanceFrameAndPlayerCounters(player->index_nr);
5882 BackToFront_WithFrameDelay(wait_delay_value);
5885 DrawPlayer(player); // needed here only to cleanup last field
5886 DrawLevelField(player->jx, player->jy); // remove player graphic
5888 player->is_moving = FALSE;
5891 if (IS_CUSTOM_ELEMENT(old_element))
5892 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5894 player->index_bit, leave_side);
5896 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5898 player->index_bit, leave_side);
5900 Tile[jx][jy] = el_player;
5901 InitPlayerField(jx, jy, el_player, TRUE);
5903 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5904 possible that the relocation target field did not contain a player element,
5905 but a walkable element, to which the new player was relocated -- in this
5906 case, restore that (already initialized!) element on the player field */
5907 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5909 Tile[jx][jy] = element; // restore previously existing element
5912 // only visually relocate centered player
5913 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy,
5914 FALSE, level.instant_relocation);
5916 TestIfPlayerTouchesBadThing(jx, jy);
5917 TestIfPlayerTouchesCustomElement(jx, jy);
5919 if (IS_CUSTOM_ELEMENT(element))
5920 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5921 player->index_bit, enter_side);
5923 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5924 player->index_bit, enter_side);
5926 if (player->is_switching)
5928 /* ensure that relocation while still switching an element does not cause
5929 a new element to be treated as also switched directly after relocation
5930 (this is important for teleporter switches that teleport the player to
5931 a place where another teleporter switch is in the same direction, which
5932 would then incorrectly be treated as immediately switched before the
5933 direction key that caused the switch was released) */
5935 player->switch_x += jx - old_jx;
5936 player->switch_y += jy - old_jy;
5940 static void Explode(int ex, int ey, int phase, int mode)
5946 if (game.explosions_delayed)
5948 ExplodeField[ex][ey] = mode;
5952 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5954 int center_element = Tile[ex][ey];
5955 int ce_value = CustomValue[ex][ey];
5956 int ce_score = element_info[center_element].collect_score;
5957 int artwork_element, explosion_element; // set these values later
5959 // remove things displayed in background while burning dynamite
5960 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5963 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5965 // put moving element to center field (and let it explode there)
5966 center_element = MovingOrBlocked2Element(ex, ey);
5967 RemoveMovingField(ex, ey);
5968 Tile[ex][ey] = center_element;
5971 // now "center_element" is finally determined -- set related values now
5972 artwork_element = center_element; // for custom player artwork
5973 explosion_element = center_element; // for custom player artwork
5975 if (IS_PLAYER(ex, ey))
5977 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5979 artwork_element = stored_player[player_nr].artwork_element;
5981 if (level.use_explosion_element[player_nr])
5983 explosion_element = level.explosion_element[player_nr];
5984 artwork_element = explosion_element;
5988 if (mode == EX_TYPE_NORMAL ||
5989 mode == EX_TYPE_CENTER ||
5990 mode == EX_TYPE_CROSS)
5991 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5993 last_phase = element_info[explosion_element].explosion_delay + 1;
5995 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5997 int xx = x - ex + 1;
5998 int yy = y - ey + 1;
6001 if (!IN_LEV_FIELD(x, y) ||
6002 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
6003 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
6006 element = Tile[x][y];
6008 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
6010 element = MovingOrBlocked2Element(x, y);
6012 if (!IS_EXPLOSION_PROOF(element))
6013 RemoveMovingField(x, y);
6016 // indestructible elements can only explode in center (but not flames)
6017 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
6018 mode == EX_TYPE_BORDER)) ||
6019 element == EL_FLAMES)
6022 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
6023 behaviour, for example when touching a yamyam that explodes to rocks
6024 with active deadly shield, a rock is created under the player !!! */
6025 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
6027 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
6028 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
6029 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
6031 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
6034 if (IS_ACTIVE_BOMB(element))
6036 // re-activate things under the bomb like gate or penguin
6037 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
6044 // save walkable background elements while explosion on same tile
6045 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
6046 (x != ex || y != ey || mode == EX_TYPE_BORDER))
6047 Back[x][y] = element;
6049 // ignite explodable elements reached by other explosion
6050 if (element == EL_EXPLOSION)
6051 element = Store2[x][y];
6053 if (AmoebaNr[x][y] &&
6054 (element == EL_AMOEBA_FULL ||
6055 element == EL_BD_AMOEBA ||
6056 element == EL_AMOEBA_GROWING))
6058 AmoebaCnt[AmoebaNr[x][y]]--;
6059 AmoebaCnt2[AmoebaNr[x][y]]--;
6064 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
6066 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
6068 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
6070 if (PLAYERINFO(ex, ey)->use_murphy)
6071 Store[x][y] = EL_EMPTY;
6074 // !!! check this case -- currently needed for rnd_rado_negundo_v,
6075 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
6076 else if (IS_PLAYER_ELEMENT(center_element))
6077 Store[x][y] = EL_EMPTY;
6078 else if (center_element == EL_YAMYAM)
6079 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
6080 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
6081 Store[x][y] = element_info[center_element].content.e[xx][yy];
6083 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
6084 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
6085 // otherwise) -- FIX THIS !!!
6086 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
6087 Store[x][y] = element_info[element].content.e[1][1];
6089 else if (!CAN_EXPLODE(element))
6090 Store[x][y] = element_info[element].content.e[1][1];
6093 Store[x][y] = EL_EMPTY;
6095 if (IS_CUSTOM_ELEMENT(center_element))
6096 Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value :
6097 Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score :
6098 Store[x][y] >= EL_PREV_CE_8 &&
6099 Store[x][y] <= EL_NEXT_CE_8 ?
6100 RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) :
6103 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
6104 center_element == EL_AMOEBA_TO_DIAMOND)
6105 Store2[x][y] = element;
6107 Tile[x][y] = EL_EXPLOSION;
6108 GfxElement[x][y] = artwork_element;
6110 ExplodePhase[x][y] = 1;
6111 ExplodeDelay[x][y] = last_phase;
6116 if (center_element == EL_YAMYAM)
6117 game.yamyam_content_nr =
6118 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
6130 GfxFrame[x][y] = 0; // restart explosion animation
6132 last_phase = ExplodeDelay[x][y];
6134 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
6136 // this can happen if the player leaves an explosion just in time
6137 if (GfxElement[x][y] == EL_UNDEFINED)
6138 GfxElement[x][y] = EL_EMPTY;
6140 border_element = Store2[x][y];
6141 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6142 border_element = StorePlayer[x][y];
6144 if (phase == element_info[border_element].ignition_delay ||
6145 phase == last_phase)
6147 boolean border_explosion = FALSE;
6149 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6150 !PLAYER_EXPLOSION_PROTECTED(x, y))
6152 KillPlayerUnlessExplosionProtected(x, y);
6153 border_explosion = TRUE;
6155 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6157 Tile[x][y] = Store2[x][y];
6160 border_explosion = TRUE;
6162 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6164 AmoebaToDiamond(x, y);
6166 border_explosion = TRUE;
6169 // if an element just explodes due to another explosion (chain-reaction),
6170 // do not immediately end the new explosion when it was the last frame of
6171 // the explosion (as it would be done in the following "if"-statement!)
6172 if (border_explosion && phase == last_phase)
6176 // this can happen if the player was just killed by an explosion
6177 if (GfxElement[x][y] == EL_UNDEFINED)
6178 GfxElement[x][y] = EL_EMPTY;
6180 if (phase == last_phase)
6184 element = Tile[x][y] = Store[x][y];
6185 Store[x][y] = Store2[x][y] = 0;
6186 GfxElement[x][y] = EL_UNDEFINED;
6188 // player can escape from explosions and might therefore be still alive
6189 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6190 element <= EL_PLAYER_IS_EXPLODING_4)
6192 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6193 int explosion_element = EL_PLAYER_1 + player_nr;
6194 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6195 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6197 if (level.use_explosion_element[player_nr])
6198 explosion_element = level.explosion_element[player_nr];
6200 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6201 element_info[explosion_element].content.e[xx][yy]);
6204 // restore probably existing indestructible background element
6205 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6206 element = Tile[x][y] = Back[x][y];
6209 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6210 GfxDir[x][y] = MV_NONE;
6211 ChangeDelay[x][y] = 0;
6212 ChangePage[x][y] = -1;
6214 CustomValue[x][y] = 0;
6216 InitField_WithBug2(x, y, FALSE);
6218 TEST_DrawLevelField(x, y);
6220 TestIfElementTouchesCustomElement(x, y);
6222 if (GFX_CRUMBLED(element))
6223 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6225 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6226 StorePlayer[x][y] = 0;
6228 if (IS_PLAYER_ELEMENT(element))
6229 RelocatePlayer(x, y, element);
6231 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6233 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6234 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6237 TEST_DrawLevelFieldCrumbled(x, y);
6239 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6241 DrawLevelElement(x, y, Back[x][y]);
6242 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6244 else if (IS_WALKABLE_UNDER(Back[x][y]))
6246 DrawLevelGraphic(x, y, graphic, frame);
6247 DrawLevelElementThruMask(x, y, Back[x][y]);
6249 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6250 DrawLevelGraphic(x, y, graphic, frame);
6254 static void DynaExplode(int ex, int ey)
6257 int dynabomb_element = Tile[ex][ey];
6258 int dynabomb_size = 1;
6259 boolean dynabomb_xl = FALSE;
6260 struct PlayerInfo *player;
6261 struct XY *xy = xy_topdown;
6263 if (IS_ACTIVE_BOMB(dynabomb_element))
6265 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6266 dynabomb_size = player->dynabomb_size;
6267 dynabomb_xl = player->dynabomb_xl;
6268 player->dynabombs_left++;
6271 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6273 for (i = 0; i < NUM_DIRECTIONS; i++)
6275 for (j = 1; j <= dynabomb_size; j++)
6277 int x = ex + j * xy[i].x;
6278 int y = ey + j * xy[i].y;
6281 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6284 element = Tile[x][y];
6286 // do not restart explosions of fields with active bombs
6287 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6290 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6292 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6293 !IS_DIGGABLE(element) && !dynabomb_xl)
6299 void Bang(int x, int y)
6301 int element = MovingOrBlocked2Element(x, y);
6302 int explosion_type = EX_TYPE_NORMAL;
6304 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6306 struct PlayerInfo *player = PLAYERINFO(x, y);
6308 element = Tile[x][y] = player->initial_element;
6310 if (level.use_explosion_element[player->index_nr])
6312 int explosion_element = level.explosion_element[player->index_nr];
6314 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6315 explosion_type = EX_TYPE_CROSS;
6316 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6317 explosion_type = EX_TYPE_CENTER;
6325 case EL_BD_BUTTERFLY:
6328 case EL_DARK_YAMYAM:
6332 RaiseScoreElement(element);
6335 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6336 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6337 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6338 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6339 case EL_DYNABOMB_INCREASE_NUMBER:
6340 case EL_DYNABOMB_INCREASE_SIZE:
6341 case EL_DYNABOMB_INCREASE_POWER:
6342 explosion_type = EX_TYPE_DYNA;
6345 case EL_DC_LANDMINE:
6346 explosion_type = EX_TYPE_CENTER;
6351 case EL_LAMP_ACTIVE:
6352 case EL_AMOEBA_TO_DIAMOND:
6353 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6354 explosion_type = EX_TYPE_CENTER;
6358 if (element_info[element].explosion_type == EXPLODES_CROSS)
6359 explosion_type = EX_TYPE_CROSS;
6360 else if (element_info[element].explosion_type == EXPLODES_1X1)
6361 explosion_type = EX_TYPE_CENTER;
6365 if (explosion_type == EX_TYPE_DYNA)
6368 Explode(x, y, EX_PHASE_START, explosion_type);
6370 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6373 static void SplashAcid(int x, int y)
6375 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6376 (!IN_LEV_FIELD(x - 1, y - 2) ||
6377 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6378 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6380 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6381 (!IN_LEV_FIELD(x + 1, y - 2) ||
6382 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6383 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6385 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6388 static void InitBeltMovement(void)
6390 static int belt_base_element[4] =
6392 EL_CONVEYOR_BELT_1_LEFT,
6393 EL_CONVEYOR_BELT_2_LEFT,
6394 EL_CONVEYOR_BELT_3_LEFT,
6395 EL_CONVEYOR_BELT_4_LEFT
6397 static int belt_base_active_element[4] =
6399 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6400 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6401 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6402 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6407 // set frame order for belt animation graphic according to belt direction
6408 for (i = 0; i < NUM_BELTS; i++)
6412 for (j = 0; j < NUM_BELT_PARTS; j++)
6414 int element = belt_base_active_element[belt_nr] + j;
6415 int graphic_1 = el2img(element);
6416 int graphic_2 = el2panelimg(element);
6418 if (game.belt_dir[i] == MV_LEFT)
6420 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6421 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6425 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6426 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6431 SCAN_PLAYFIELD(x, y)
6433 int element = Tile[x][y];
6435 for (i = 0; i < NUM_BELTS; i++)
6437 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6439 int e_belt_nr = getBeltNrFromBeltElement(element);
6442 if (e_belt_nr == belt_nr)
6444 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6446 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6453 static void ToggleBeltSwitch(int x, int y)
6455 static int belt_base_element[4] =
6457 EL_CONVEYOR_BELT_1_LEFT,
6458 EL_CONVEYOR_BELT_2_LEFT,
6459 EL_CONVEYOR_BELT_3_LEFT,
6460 EL_CONVEYOR_BELT_4_LEFT
6462 static int belt_base_active_element[4] =
6464 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6465 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6466 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6467 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6469 static int belt_base_switch_element[4] =
6471 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6472 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6473 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6474 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6476 static int belt_move_dir[4] =
6484 int element = Tile[x][y];
6485 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6486 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6487 int belt_dir = belt_move_dir[belt_dir_nr];
6490 if (!IS_BELT_SWITCH(element))
6493 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6494 game.belt_dir[belt_nr] = belt_dir;
6496 if (belt_dir_nr == 3)
6499 // set frame order for belt animation graphic according to belt direction
6500 for (i = 0; i < NUM_BELT_PARTS; i++)
6502 int element = belt_base_active_element[belt_nr] + i;
6503 int graphic_1 = el2img(element);
6504 int graphic_2 = el2panelimg(element);
6506 if (belt_dir == MV_LEFT)
6508 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6509 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6513 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6514 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6518 SCAN_PLAYFIELD(xx, yy)
6520 int element = Tile[xx][yy];
6522 if (IS_BELT_SWITCH(element))
6524 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6526 if (e_belt_nr == belt_nr)
6528 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6529 TEST_DrawLevelField(xx, yy);
6532 else if (IS_BELT(element) && belt_dir != MV_NONE)
6534 int e_belt_nr = getBeltNrFromBeltElement(element);
6536 if (e_belt_nr == belt_nr)
6538 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6540 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6541 TEST_DrawLevelField(xx, yy);
6544 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6546 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6548 if (e_belt_nr == belt_nr)
6550 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6552 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6553 TEST_DrawLevelField(xx, yy);
6559 static void ToggleSwitchgateSwitch(void)
6563 game.switchgate_pos = !game.switchgate_pos;
6565 SCAN_PLAYFIELD(xx, yy)
6567 int element = Tile[xx][yy];
6569 if (element == EL_SWITCHGATE_SWITCH_UP)
6571 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6572 TEST_DrawLevelField(xx, yy);
6574 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6576 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6577 TEST_DrawLevelField(xx, yy);
6579 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6581 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6582 TEST_DrawLevelField(xx, yy);
6584 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6586 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6587 TEST_DrawLevelField(xx, yy);
6589 else if (element == EL_SWITCHGATE_OPEN ||
6590 element == EL_SWITCHGATE_OPENING)
6592 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6594 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6596 else if (element == EL_SWITCHGATE_CLOSED ||
6597 element == EL_SWITCHGATE_CLOSING)
6599 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6601 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6606 static int getInvisibleActiveFromInvisibleElement(int element)
6608 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6609 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6610 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6614 static int getInvisibleFromInvisibleActiveElement(int element)
6616 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6617 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6618 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6622 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6626 SCAN_PLAYFIELD(x, y)
6628 int element = Tile[x][y];
6630 if (element == EL_LIGHT_SWITCH &&
6631 game.light_time_left > 0)
6633 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6634 TEST_DrawLevelField(x, y);
6636 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6637 game.light_time_left == 0)
6639 Tile[x][y] = EL_LIGHT_SWITCH;
6640 TEST_DrawLevelField(x, y);
6642 else if (element == EL_EMC_DRIPPER &&
6643 game.light_time_left > 0)
6645 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6646 TEST_DrawLevelField(x, y);
6648 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6649 game.light_time_left == 0)
6651 Tile[x][y] = EL_EMC_DRIPPER;
6652 TEST_DrawLevelField(x, y);
6654 else if (element == EL_INVISIBLE_STEELWALL ||
6655 element == EL_INVISIBLE_WALL ||
6656 element == EL_INVISIBLE_SAND)
6658 if (game.light_time_left > 0)
6659 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6661 TEST_DrawLevelField(x, y);
6663 // uncrumble neighbour fields, if needed
6664 if (element == EL_INVISIBLE_SAND)
6665 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6667 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6668 element == EL_INVISIBLE_WALL_ACTIVE ||
6669 element == EL_INVISIBLE_SAND_ACTIVE)
6671 if (game.light_time_left == 0)
6672 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6674 TEST_DrawLevelField(x, y);
6676 // re-crumble neighbour fields, if needed
6677 if (element == EL_INVISIBLE_SAND)
6678 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6683 static void RedrawAllInvisibleElementsForLenses(void)
6687 SCAN_PLAYFIELD(x, y)
6689 int element = Tile[x][y];
6691 if (element == EL_EMC_DRIPPER &&
6692 game.lenses_time_left > 0)
6694 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6695 TEST_DrawLevelField(x, y);
6697 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6698 game.lenses_time_left == 0)
6700 Tile[x][y] = EL_EMC_DRIPPER;
6701 TEST_DrawLevelField(x, y);
6703 else if (element == EL_INVISIBLE_STEELWALL ||
6704 element == EL_INVISIBLE_WALL ||
6705 element == EL_INVISIBLE_SAND)
6707 if (game.lenses_time_left > 0)
6708 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6710 TEST_DrawLevelField(x, y);
6712 // uncrumble neighbour fields, if needed
6713 if (element == EL_INVISIBLE_SAND)
6714 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6716 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6717 element == EL_INVISIBLE_WALL_ACTIVE ||
6718 element == EL_INVISIBLE_SAND_ACTIVE)
6720 if (game.lenses_time_left == 0)
6721 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6723 TEST_DrawLevelField(x, y);
6725 // re-crumble neighbour fields, if needed
6726 if (element == EL_INVISIBLE_SAND)
6727 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6732 static void RedrawAllInvisibleElementsForMagnifier(void)
6736 SCAN_PLAYFIELD(x, y)
6738 int element = Tile[x][y];
6740 if (element == EL_EMC_FAKE_GRASS &&
6741 game.magnify_time_left > 0)
6743 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6744 TEST_DrawLevelField(x, y);
6746 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6747 game.magnify_time_left == 0)
6749 Tile[x][y] = EL_EMC_FAKE_GRASS;
6750 TEST_DrawLevelField(x, y);
6752 else if (IS_GATE_GRAY(element) &&
6753 game.magnify_time_left > 0)
6755 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6756 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6757 IS_EM_GATE_GRAY(element) ?
6758 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6759 IS_EMC_GATE_GRAY(element) ?
6760 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6761 IS_DC_GATE_GRAY(element) ?
6762 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6764 TEST_DrawLevelField(x, y);
6766 else if (IS_GATE_GRAY_ACTIVE(element) &&
6767 game.magnify_time_left == 0)
6769 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6770 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6771 IS_EM_GATE_GRAY_ACTIVE(element) ?
6772 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6773 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6774 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6775 IS_DC_GATE_GRAY_ACTIVE(element) ?
6776 EL_DC_GATE_WHITE_GRAY :
6778 TEST_DrawLevelField(x, y);
6783 static void ToggleLightSwitch(int x, int y)
6785 int element = Tile[x][y];
6787 game.light_time_left =
6788 (element == EL_LIGHT_SWITCH ?
6789 level.time_light * FRAMES_PER_SECOND : 0);
6791 RedrawAllLightSwitchesAndInvisibleElements();
6794 static void ActivateTimegateSwitch(int x, int y)
6798 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6800 SCAN_PLAYFIELD(xx, yy)
6802 int element = Tile[xx][yy];
6804 if (element == EL_TIMEGATE_CLOSED ||
6805 element == EL_TIMEGATE_CLOSING)
6807 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6808 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6812 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6814 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6815 TEST_DrawLevelField(xx, yy);
6821 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6822 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6825 static void Impact(int x, int y)
6827 boolean last_line = (y == lev_fieldy - 1);
6828 boolean object_hit = FALSE;
6829 boolean impact = (last_line || object_hit);
6830 int element = Tile[x][y];
6831 int smashed = EL_STEELWALL;
6833 if (!last_line) // check if element below was hit
6835 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6838 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6839 MovDir[x][y + 1] != MV_DOWN ||
6840 MovPos[x][y + 1] <= TILEY / 2));
6842 // do not smash moving elements that left the smashed field in time
6843 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6844 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6847 #if USE_QUICKSAND_IMPACT_BUGFIX
6848 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6850 RemoveMovingField(x, y + 1);
6851 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6852 Tile[x][y + 2] = EL_ROCK;
6853 TEST_DrawLevelField(x, y + 2);
6858 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6860 RemoveMovingField(x, y + 1);
6861 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6862 Tile[x][y + 2] = EL_ROCK;
6863 TEST_DrawLevelField(x, y + 2);
6870 smashed = MovingOrBlocked2Element(x, y + 1);
6872 impact = (last_line || object_hit);
6875 if (!last_line && smashed == EL_ACID) // element falls into acid
6877 SplashAcid(x, y + 1);
6881 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6882 // only reset graphic animation if graphic really changes after impact
6884 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6886 ResetGfxAnimation(x, y);
6887 TEST_DrawLevelField(x, y);
6890 if (impact && CAN_EXPLODE_IMPACT(element))
6895 else if (impact && element == EL_PEARL &&
6896 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6898 ResetGfxAnimation(x, y);
6900 Tile[x][y] = EL_PEARL_BREAKING;
6901 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6904 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6906 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6911 if (impact && element == EL_AMOEBA_DROP)
6913 if (object_hit && IS_PLAYER(x, y + 1))
6914 KillPlayerUnlessEnemyProtected(x, y + 1);
6915 else if (object_hit && smashed == EL_PENGUIN)
6919 Tile[x][y] = EL_AMOEBA_GROWING;
6920 Store[x][y] = EL_AMOEBA_WET;
6922 ResetRandomAnimationValue(x, y);
6927 if (object_hit) // check which object was hit
6929 if ((CAN_PASS_MAGIC_WALL(element) &&
6930 (smashed == EL_MAGIC_WALL ||
6931 smashed == EL_BD_MAGIC_WALL)) ||
6932 (CAN_PASS_DC_MAGIC_WALL(element) &&
6933 smashed == EL_DC_MAGIC_WALL))
6936 int activated_magic_wall =
6937 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6938 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6939 EL_DC_MAGIC_WALL_ACTIVE);
6941 // activate magic wall / mill
6942 SCAN_PLAYFIELD(xx, yy)
6944 if (Tile[xx][yy] == smashed)
6945 Tile[xx][yy] = activated_magic_wall;
6948 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6949 game.magic_wall_active = TRUE;
6951 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6952 SND_MAGIC_WALL_ACTIVATING :
6953 smashed == EL_BD_MAGIC_WALL ?
6954 SND_BD_MAGIC_WALL_ACTIVATING :
6955 SND_DC_MAGIC_WALL_ACTIVATING));
6958 if (IS_PLAYER(x, y + 1))
6960 if (CAN_SMASH_PLAYER(element))
6962 KillPlayerUnlessEnemyProtected(x, y + 1);
6966 else if (smashed == EL_PENGUIN)
6968 if (CAN_SMASH_PLAYER(element))
6974 else if (element == EL_BD_DIAMOND)
6976 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6982 else if (((element == EL_SP_INFOTRON ||
6983 element == EL_SP_ZONK) &&
6984 (smashed == EL_SP_SNIKSNAK ||
6985 smashed == EL_SP_ELECTRON ||
6986 smashed == EL_SP_DISK_ORANGE)) ||
6987 (element == EL_SP_INFOTRON &&
6988 smashed == EL_SP_DISK_YELLOW))
6993 else if (CAN_SMASH_EVERYTHING(element))
6995 if (IS_CLASSIC_ENEMY(smashed) ||
6996 CAN_EXPLODE_SMASHED(smashed))
7001 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
7003 if (smashed == EL_LAMP ||
7004 smashed == EL_LAMP_ACTIVE)
7009 else if (smashed == EL_NUT)
7011 Tile[x][y + 1] = EL_NUT_BREAKING;
7012 PlayLevelSound(x, y, SND_NUT_BREAKING);
7013 RaiseScoreElement(EL_NUT);
7016 else if (smashed == EL_PEARL)
7018 ResetGfxAnimation(x, y);
7020 Tile[x][y + 1] = EL_PEARL_BREAKING;
7021 PlayLevelSound(x, y, SND_PEARL_BREAKING);
7024 else if (smashed == EL_DIAMOND)
7026 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
7027 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
7030 else if (IS_BELT_SWITCH(smashed))
7032 ToggleBeltSwitch(x, y + 1);
7034 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
7035 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
7036 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
7037 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
7039 ToggleSwitchgateSwitch();
7041 else if (smashed == EL_LIGHT_SWITCH ||
7042 smashed == EL_LIGHT_SWITCH_ACTIVE)
7044 ToggleLightSwitch(x, y + 1);
7048 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7050 CheckElementChangeBySide(x, y + 1, smashed, element,
7051 CE_SWITCHED, CH_SIDE_TOP);
7052 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
7058 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7063 // play sound of magic wall / mill
7065 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7066 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
7067 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
7069 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7070 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
7071 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7072 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
7073 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7074 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
7079 // play sound of object that hits the ground
7080 if (last_line || object_hit)
7081 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
7084 static void TurnRoundExt(int x, int y)
7096 { 0, 0 }, { 0, 0 }, { 0, 0 },
7101 int left, right, back;
7105 { MV_DOWN, MV_UP, MV_RIGHT },
7106 { MV_UP, MV_DOWN, MV_LEFT },
7108 { MV_LEFT, MV_RIGHT, MV_DOWN },
7112 { MV_RIGHT, MV_LEFT, MV_UP }
7115 int element = Tile[x][y];
7116 int move_pattern = element_info[element].move_pattern;
7118 int old_move_dir = MovDir[x][y];
7119 int left_dir = turn[old_move_dir].left;
7120 int right_dir = turn[old_move_dir].right;
7121 int back_dir = turn[old_move_dir].back;
7123 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
7124 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
7125 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
7126 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
7128 int left_x = x + left_dx, left_y = y + left_dy;
7129 int right_x = x + right_dx, right_y = y + right_dy;
7130 int move_x = x + move_dx, move_y = y + move_dy;
7134 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
7136 TestIfBadThingTouchesOtherBadThing(x, y);
7138 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
7139 MovDir[x][y] = right_dir;
7140 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7141 MovDir[x][y] = left_dir;
7143 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7145 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7148 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7150 TestIfBadThingTouchesOtherBadThing(x, y);
7152 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7153 MovDir[x][y] = left_dir;
7154 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7155 MovDir[x][y] = right_dir;
7157 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7159 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7162 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7164 TestIfBadThingTouchesOtherBadThing(x, y);
7166 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7167 MovDir[x][y] = left_dir;
7168 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7169 MovDir[x][y] = right_dir;
7171 if (MovDir[x][y] != old_move_dir)
7174 else if (element == EL_YAMYAM)
7176 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7177 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7179 if (can_turn_left && can_turn_right)
7180 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7181 else if (can_turn_left)
7182 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7183 else if (can_turn_right)
7184 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7186 MovDir[x][y] = back_dir;
7188 MovDelay[x][y] = 16 + 16 * RND(3);
7190 else if (element == EL_DARK_YAMYAM)
7192 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7194 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7197 if (can_turn_left && can_turn_right)
7198 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7199 else if (can_turn_left)
7200 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7201 else if (can_turn_right)
7202 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7204 MovDir[x][y] = back_dir;
7206 MovDelay[x][y] = 16 + 16 * RND(3);
7208 else if (element == EL_PACMAN)
7210 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7211 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7213 if (can_turn_left && can_turn_right)
7214 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7215 else if (can_turn_left)
7216 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7217 else if (can_turn_right)
7218 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7220 MovDir[x][y] = back_dir;
7222 MovDelay[x][y] = 6 + RND(40);
7224 else if (element == EL_PIG)
7226 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7227 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7228 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7229 boolean should_turn_left, should_turn_right, should_move_on;
7231 int rnd = RND(rnd_value);
7233 should_turn_left = (can_turn_left &&
7235 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7236 y + back_dy + left_dy)));
7237 should_turn_right = (can_turn_right &&
7239 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7240 y + back_dy + right_dy)));
7241 should_move_on = (can_move_on &&
7244 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7245 y + move_dy + left_dy) ||
7246 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7247 y + move_dy + right_dy)));
7249 if (should_turn_left || should_turn_right || should_move_on)
7251 if (should_turn_left && should_turn_right && should_move_on)
7252 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7253 rnd < 2 * rnd_value / 3 ? right_dir :
7255 else if (should_turn_left && should_turn_right)
7256 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7257 else if (should_turn_left && should_move_on)
7258 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7259 else if (should_turn_right && should_move_on)
7260 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7261 else if (should_turn_left)
7262 MovDir[x][y] = left_dir;
7263 else if (should_turn_right)
7264 MovDir[x][y] = right_dir;
7265 else if (should_move_on)
7266 MovDir[x][y] = old_move_dir;
7268 else if (can_move_on && rnd > rnd_value / 8)
7269 MovDir[x][y] = old_move_dir;
7270 else if (can_turn_left && can_turn_right)
7271 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7272 else if (can_turn_left && rnd > rnd_value / 8)
7273 MovDir[x][y] = left_dir;
7274 else if (can_turn_right && rnd > rnd_value/8)
7275 MovDir[x][y] = right_dir;
7277 MovDir[x][y] = back_dir;
7279 xx = x + move_xy[MovDir[x][y]].dx;
7280 yy = y + move_xy[MovDir[x][y]].dy;
7282 if (!IN_LEV_FIELD(xx, yy) ||
7283 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7284 MovDir[x][y] = old_move_dir;
7288 else if (element == EL_DRAGON)
7290 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7291 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7292 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7294 int rnd = RND(rnd_value);
7296 if (can_move_on && rnd > rnd_value / 8)
7297 MovDir[x][y] = old_move_dir;
7298 else if (can_turn_left && can_turn_right)
7299 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7300 else if (can_turn_left && rnd > rnd_value / 8)
7301 MovDir[x][y] = left_dir;
7302 else if (can_turn_right && rnd > rnd_value / 8)
7303 MovDir[x][y] = right_dir;
7305 MovDir[x][y] = back_dir;
7307 xx = x + move_xy[MovDir[x][y]].dx;
7308 yy = y + move_xy[MovDir[x][y]].dy;
7310 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7311 MovDir[x][y] = old_move_dir;
7315 else if (element == EL_MOLE)
7317 boolean can_move_on =
7318 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7319 IS_AMOEBOID(Tile[move_x][move_y]) ||
7320 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7323 boolean can_turn_left =
7324 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7325 IS_AMOEBOID(Tile[left_x][left_y])));
7327 boolean can_turn_right =
7328 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7329 IS_AMOEBOID(Tile[right_x][right_y])));
7331 if (can_turn_left && can_turn_right)
7332 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7333 else if (can_turn_left)
7334 MovDir[x][y] = left_dir;
7336 MovDir[x][y] = right_dir;
7339 if (MovDir[x][y] != old_move_dir)
7342 else if (element == EL_BALLOON)
7344 MovDir[x][y] = game.wind_direction;
7347 else if (element == EL_SPRING)
7349 if (MovDir[x][y] & MV_HORIZONTAL)
7351 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7352 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7354 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7355 ResetGfxAnimation(move_x, move_y);
7356 TEST_DrawLevelField(move_x, move_y);
7358 MovDir[x][y] = back_dir;
7360 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7361 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7362 MovDir[x][y] = MV_NONE;
7367 else if (element == EL_ROBOT ||
7368 element == EL_SATELLITE ||
7369 element == EL_PENGUIN ||
7370 element == EL_EMC_ANDROID)
7372 int attr_x = -1, attr_y = -1;
7374 if (game.all_players_gone)
7376 attr_x = game.exit_x;
7377 attr_y = game.exit_y;
7383 for (i = 0; i < MAX_PLAYERS; i++)
7385 struct PlayerInfo *player = &stored_player[i];
7386 int jx = player->jx, jy = player->jy;
7388 if (!player->active)
7392 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7400 if (element == EL_ROBOT &&
7401 game.robot_wheel_x >= 0 &&
7402 game.robot_wheel_y >= 0 &&
7403 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7404 game.engine_version < VERSION_IDENT(3,1,0,0)))
7406 attr_x = game.robot_wheel_x;
7407 attr_y = game.robot_wheel_y;
7410 if (element == EL_PENGUIN)
7413 struct XY *xy = xy_topdown;
7415 for (i = 0; i < NUM_DIRECTIONS; i++)
7417 int ex = x + xy[i].x;
7418 int ey = y + xy[i].y;
7420 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7421 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7422 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7423 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7432 MovDir[x][y] = MV_NONE;
7434 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7435 else if (attr_x > x)
7436 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7438 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7439 else if (attr_y > y)
7440 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7442 if (element == EL_ROBOT)
7446 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7447 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7448 Moving2Blocked(x, y, &newx, &newy);
7450 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7451 MovDelay[x][y] = 8 + 8 * !RND(3);
7453 MovDelay[x][y] = 16;
7455 else if (element == EL_PENGUIN)
7461 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7463 boolean first_horiz = RND(2);
7464 int new_move_dir = MovDir[x][y];
7467 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7468 Moving2Blocked(x, y, &newx, &newy);
7470 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7474 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7475 Moving2Blocked(x, y, &newx, &newy);
7477 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7480 MovDir[x][y] = old_move_dir;
7484 else if (element == EL_SATELLITE)
7490 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7492 boolean first_horiz = RND(2);
7493 int new_move_dir = MovDir[x][y];
7496 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7497 Moving2Blocked(x, y, &newx, &newy);
7499 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7503 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7504 Moving2Blocked(x, y, &newx, &newy);
7506 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7509 MovDir[x][y] = old_move_dir;
7513 else if (element == EL_EMC_ANDROID)
7515 static int check_pos[16] =
7517 -1, // 0 => (invalid)
7520 -1, // 3 => (invalid)
7522 0, // 5 => MV_LEFT | MV_UP
7523 2, // 6 => MV_RIGHT | MV_UP
7524 -1, // 7 => (invalid)
7526 6, // 9 => MV_LEFT | MV_DOWN
7527 4, // 10 => MV_RIGHT | MV_DOWN
7528 -1, // 11 => (invalid)
7529 -1, // 12 => (invalid)
7530 -1, // 13 => (invalid)
7531 -1, // 14 => (invalid)
7532 -1, // 15 => (invalid)
7540 { -1, -1, MV_LEFT | MV_UP },
7542 { +1, -1, MV_RIGHT | MV_UP },
7543 { +1, 0, MV_RIGHT },
7544 { +1, +1, MV_RIGHT | MV_DOWN },
7546 { -1, +1, MV_LEFT | MV_DOWN },
7549 int start_pos, check_order;
7550 boolean can_clone = FALSE;
7553 // check if there is any free field around current position
7554 for (i = 0; i < 8; i++)
7556 int newx = x + check_xy[i].dx;
7557 int newy = y + check_xy[i].dy;
7559 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7567 if (can_clone) // randomly find an element to clone
7571 start_pos = check_pos[RND(8)];
7572 check_order = (RND(2) ? -1 : +1);
7574 for (i = 0; i < 8; i++)
7576 int pos_raw = start_pos + i * check_order;
7577 int pos = (pos_raw + 8) % 8;
7578 int newx = x + check_xy[pos].dx;
7579 int newy = y + check_xy[pos].dy;
7581 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7583 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7584 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7586 Store[x][y] = Tile[newx][newy];
7595 if (can_clone) // randomly find a direction to move
7599 start_pos = check_pos[RND(8)];
7600 check_order = (RND(2) ? -1 : +1);
7602 for (i = 0; i < 8; i++)
7604 int pos_raw = start_pos + i * check_order;
7605 int pos = (pos_raw + 8) % 8;
7606 int newx = x + check_xy[pos].dx;
7607 int newy = y + check_xy[pos].dy;
7608 int new_move_dir = check_xy[pos].dir;
7610 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7612 MovDir[x][y] = new_move_dir;
7613 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7622 if (can_clone) // cloning and moving successful
7625 // cannot clone -- try to move towards player
7627 start_pos = check_pos[MovDir[x][y] & 0x0f];
7628 check_order = (RND(2) ? -1 : +1);
7630 for (i = 0; i < 3; i++)
7632 // first check start_pos, then previous/next or (next/previous) pos
7633 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7634 int pos = (pos_raw + 8) % 8;
7635 int newx = x + check_xy[pos].dx;
7636 int newy = y + check_xy[pos].dy;
7637 int new_move_dir = check_xy[pos].dir;
7639 if (IS_PLAYER(newx, newy))
7642 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7644 MovDir[x][y] = new_move_dir;
7645 MovDelay[x][y] = level.android_move_time * 8 + 1;
7652 else if (move_pattern == MV_TURNING_LEFT ||
7653 move_pattern == MV_TURNING_RIGHT ||
7654 move_pattern == MV_TURNING_LEFT_RIGHT ||
7655 move_pattern == MV_TURNING_RIGHT_LEFT ||
7656 move_pattern == MV_TURNING_RANDOM ||
7657 move_pattern == MV_ALL_DIRECTIONS)
7659 boolean can_turn_left =
7660 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7661 boolean can_turn_right =
7662 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y);
7664 if (element_info[element].move_stepsize == 0) // "not moving"
7667 if (move_pattern == MV_TURNING_LEFT)
7668 MovDir[x][y] = left_dir;
7669 else if (move_pattern == MV_TURNING_RIGHT)
7670 MovDir[x][y] = right_dir;
7671 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7672 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7673 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7674 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7675 else if (move_pattern == MV_TURNING_RANDOM)
7676 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7677 can_turn_right && !can_turn_left ? right_dir :
7678 RND(2) ? left_dir : right_dir);
7679 else if (can_turn_left && can_turn_right)
7680 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7681 else if (can_turn_left)
7682 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7683 else if (can_turn_right)
7684 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7686 MovDir[x][y] = back_dir;
7688 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7690 else if (move_pattern == MV_HORIZONTAL ||
7691 move_pattern == MV_VERTICAL)
7693 if (move_pattern & old_move_dir)
7694 MovDir[x][y] = back_dir;
7695 else if (move_pattern == MV_HORIZONTAL)
7696 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7697 else if (move_pattern == MV_VERTICAL)
7698 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7700 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7702 else if (move_pattern & MV_ANY_DIRECTION)
7704 MovDir[x][y] = move_pattern;
7705 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7707 else if (move_pattern & MV_WIND_DIRECTION)
7709 MovDir[x][y] = game.wind_direction;
7710 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7712 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7714 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7715 MovDir[x][y] = left_dir;
7716 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7717 MovDir[x][y] = right_dir;
7719 if (MovDir[x][y] != old_move_dir)
7720 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7722 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7724 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7725 MovDir[x][y] = right_dir;
7726 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7727 MovDir[x][y] = left_dir;
7729 if (MovDir[x][y] != old_move_dir)
7730 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7732 else if (move_pattern == MV_TOWARDS_PLAYER ||
7733 move_pattern == MV_AWAY_FROM_PLAYER)
7735 int attr_x = -1, attr_y = -1;
7737 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7739 if (game.all_players_gone)
7741 attr_x = game.exit_x;
7742 attr_y = game.exit_y;
7748 for (i = 0; i < MAX_PLAYERS; i++)
7750 struct PlayerInfo *player = &stored_player[i];
7751 int jx = player->jx, jy = player->jy;
7753 if (!player->active)
7757 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7765 MovDir[x][y] = MV_NONE;
7767 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7768 else if (attr_x > x)
7769 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7771 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7772 else if (attr_y > y)
7773 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7775 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7777 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7779 boolean first_horiz = RND(2);
7780 int new_move_dir = MovDir[x][y];
7782 if (element_info[element].move_stepsize == 0) // "not moving"
7784 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7785 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7791 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7792 Moving2Blocked(x, y, &newx, &newy);
7794 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7798 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7799 Moving2Blocked(x, y, &newx, &newy);
7801 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7804 MovDir[x][y] = old_move_dir;
7807 else if (move_pattern == MV_WHEN_PUSHED ||
7808 move_pattern == MV_WHEN_DROPPED)
7810 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7811 MovDir[x][y] = MV_NONE;
7815 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7817 struct XY *test_xy = xy_topdown;
7818 static int test_dir[4] =
7825 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7826 int move_preference = -1000000; // start with very low preference
7827 int new_move_dir = MV_NONE;
7828 int start_test = RND(4);
7831 for (i = 0; i < NUM_DIRECTIONS; i++)
7833 int j = (start_test + i) % 4;
7834 int move_dir = test_dir[j];
7835 int move_dir_preference;
7837 xx = x + test_xy[j].x;
7838 yy = y + test_xy[j].y;
7840 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7841 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7843 new_move_dir = move_dir;
7848 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7851 move_dir_preference = -1 * RunnerVisit[xx][yy];
7852 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7853 move_dir_preference = PlayerVisit[xx][yy];
7855 if (move_dir_preference > move_preference)
7857 // prefer field that has not been visited for the longest time
7858 move_preference = move_dir_preference;
7859 new_move_dir = move_dir;
7861 else if (move_dir_preference == move_preference &&
7862 move_dir == old_move_dir)
7864 // prefer last direction when all directions are preferred equally
7865 move_preference = move_dir_preference;
7866 new_move_dir = move_dir;
7870 MovDir[x][y] = new_move_dir;
7871 if (old_move_dir != new_move_dir)
7872 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7876 static void TurnRound(int x, int y)
7878 int direction = MovDir[x][y];
7882 GfxDir[x][y] = MovDir[x][y];
7884 if (direction != MovDir[x][y])
7888 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7890 ResetGfxFrame(x, y);
7893 static boolean JustBeingPushed(int x, int y)
7897 for (i = 0; i < MAX_PLAYERS; i++)
7899 struct PlayerInfo *player = &stored_player[i];
7901 if (player->active && player->is_pushing && player->MovPos)
7903 int next_jx = player->jx + (player->jx - player->last_jx);
7904 int next_jy = player->jy + (player->jy - player->last_jy);
7906 if (x == next_jx && y == next_jy)
7914 static void StartMoving(int x, int y)
7916 boolean started_moving = FALSE; // some elements can fall _and_ move
7917 int element = Tile[x][y];
7922 if (MovDelay[x][y] == 0)
7923 GfxAction[x][y] = ACTION_DEFAULT;
7925 if (CAN_FALL(element) && y < lev_fieldy - 1)
7927 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7928 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7929 if (JustBeingPushed(x, y))
7932 if (element == EL_QUICKSAND_FULL)
7934 if (IS_FREE(x, y + 1))
7936 InitMovingField(x, y, MV_DOWN);
7937 started_moving = TRUE;
7939 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7940 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7941 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7942 Store[x][y] = EL_ROCK;
7944 Store[x][y] = EL_ROCK;
7947 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7949 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7951 if (!MovDelay[x][y])
7953 MovDelay[x][y] = TILEY + 1;
7955 ResetGfxAnimation(x, y);
7956 ResetGfxAnimation(x, y + 1);
7961 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7962 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7969 Tile[x][y] = EL_QUICKSAND_EMPTY;
7970 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7971 Store[x][y + 1] = Store[x][y];
7974 PlayLevelSoundAction(x, y, ACTION_FILLING);
7976 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7978 if (!MovDelay[x][y])
7980 MovDelay[x][y] = TILEY + 1;
7982 ResetGfxAnimation(x, y);
7983 ResetGfxAnimation(x, y + 1);
7988 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7989 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7996 Tile[x][y] = EL_QUICKSAND_EMPTY;
7997 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7998 Store[x][y + 1] = Store[x][y];
8001 PlayLevelSoundAction(x, y, ACTION_FILLING);
8004 else if (element == EL_QUICKSAND_FAST_FULL)
8006 if (IS_FREE(x, y + 1))
8008 InitMovingField(x, y, MV_DOWN);
8009 started_moving = TRUE;
8011 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
8012 #if USE_QUICKSAND_BD_ROCK_BUGFIX
8013 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
8014 Store[x][y] = EL_ROCK;
8016 Store[x][y] = EL_ROCK;
8019 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
8021 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8023 if (!MovDelay[x][y])
8025 MovDelay[x][y] = TILEY + 1;
8027 ResetGfxAnimation(x, y);
8028 ResetGfxAnimation(x, y + 1);
8033 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8034 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8041 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8042 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8043 Store[x][y + 1] = Store[x][y];
8046 PlayLevelSoundAction(x, y, ACTION_FILLING);
8048 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8050 if (!MovDelay[x][y])
8052 MovDelay[x][y] = TILEY + 1;
8054 ResetGfxAnimation(x, y);
8055 ResetGfxAnimation(x, y + 1);
8060 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8061 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8068 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8069 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8070 Store[x][y + 1] = Store[x][y];
8073 PlayLevelSoundAction(x, y, ACTION_FILLING);
8076 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8077 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8079 InitMovingField(x, y, MV_DOWN);
8080 started_moving = TRUE;
8082 Tile[x][y] = EL_QUICKSAND_FILLING;
8083 Store[x][y] = element;
8085 PlayLevelSoundAction(x, y, ACTION_FILLING);
8087 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8088 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8090 InitMovingField(x, y, MV_DOWN);
8091 started_moving = TRUE;
8093 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
8094 Store[x][y] = element;
8096 PlayLevelSoundAction(x, y, ACTION_FILLING);
8098 else if (element == EL_MAGIC_WALL_FULL)
8100 if (IS_FREE(x, y + 1))
8102 InitMovingField(x, y, MV_DOWN);
8103 started_moving = TRUE;
8105 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
8106 Store[x][y] = EL_CHANGED(Store[x][y]);
8108 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
8110 if (!MovDelay[x][y])
8111 MovDelay[x][y] = TILEY / 4 + 1;
8120 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
8121 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
8122 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8126 else if (element == EL_BD_MAGIC_WALL_FULL)
8128 if (IS_FREE(x, y + 1))
8130 InitMovingField(x, y, MV_DOWN);
8131 started_moving = TRUE;
8133 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8134 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8136 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8138 if (!MovDelay[x][y])
8139 MovDelay[x][y] = TILEY / 4 + 1;
8148 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8149 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8150 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8154 else if (element == EL_DC_MAGIC_WALL_FULL)
8156 if (IS_FREE(x, y + 1))
8158 InitMovingField(x, y, MV_DOWN);
8159 started_moving = TRUE;
8161 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8162 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8164 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8166 if (!MovDelay[x][y])
8167 MovDelay[x][y] = TILEY / 4 + 1;
8176 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8177 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8178 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8182 else if ((CAN_PASS_MAGIC_WALL(element) &&
8183 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8184 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8185 (CAN_PASS_DC_MAGIC_WALL(element) &&
8186 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8189 InitMovingField(x, y, MV_DOWN);
8190 started_moving = TRUE;
8193 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8194 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8195 EL_DC_MAGIC_WALL_FILLING);
8196 Store[x][y] = element;
8198 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8200 SplashAcid(x, y + 1);
8202 InitMovingField(x, y, MV_DOWN);
8203 started_moving = TRUE;
8205 Store[x][y] = EL_ACID;
8208 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8209 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8210 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8211 CAN_FALL(element) && WasJustFalling[x][y] &&
8212 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8214 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8215 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8216 (Tile[x][y + 1] == EL_BLOCKED)))
8218 /* this is needed for a special case not covered by calling "Impact()"
8219 from "ContinueMoving()": if an element moves to a tile directly below
8220 another element which was just falling on that tile (which was empty
8221 in the previous frame), the falling element above would just stop
8222 instead of smashing the element below (in previous version, the above
8223 element was just checked for "moving" instead of "falling", resulting
8224 in incorrect smashes caused by horizontal movement of the above
8225 element; also, the case of the player being the element to smash was
8226 simply not covered here... :-/ ) */
8228 CheckCollision[x][y] = 0;
8229 CheckImpact[x][y] = 0;
8233 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8235 if (MovDir[x][y] == MV_NONE)
8237 InitMovingField(x, y, MV_DOWN);
8238 started_moving = TRUE;
8241 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8243 if (WasJustFalling[x][y]) // prevent animation from being restarted
8244 MovDir[x][y] = MV_DOWN;
8246 InitMovingField(x, y, MV_DOWN);
8247 started_moving = TRUE;
8249 else if (element == EL_AMOEBA_DROP)
8251 Tile[x][y] = EL_AMOEBA_GROWING;
8252 Store[x][y] = EL_AMOEBA_WET;
8254 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8255 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8256 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8257 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8259 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8260 (IS_FREE(x - 1, y + 1) ||
8261 Tile[x - 1][y + 1] == EL_ACID));
8262 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8263 (IS_FREE(x + 1, y + 1) ||
8264 Tile[x + 1][y + 1] == EL_ACID));
8265 boolean can_fall_any = (can_fall_left || can_fall_right);
8266 boolean can_fall_both = (can_fall_left && can_fall_right);
8267 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8269 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8271 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8272 can_fall_right = FALSE;
8273 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8274 can_fall_left = FALSE;
8275 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8276 can_fall_right = FALSE;
8277 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8278 can_fall_left = FALSE;
8280 can_fall_any = (can_fall_left || can_fall_right);
8281 can_fall_both = FALSE;
8286 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8287 can_fall_right = FALSE; // slip down on left side
8289 can_fall_left = !(can_fall_right = RND(2));
8291 can_fall_both = FALSE;
8296 // if not determined otherwise, prefer left side for slipping down
8297 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8298 started_moving = TRUE;
8301 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8303 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8304 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8305 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8306 int belt_dir = game.belt_dir[belt_nr];
8308 if ((belt_dir == MV_LEFT && left_is_free) ||
8309 (belt_dir == MV_RIGHT && right_is_free))
8311 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8313 InitMovingField(x, y, belt_dir);
8314 started_moving = TRUE;
8316 Pushed[x][y] = TRUE;
8317 Pushed[nextx][y] = TRUE;
8319 GfxAction[x][y] = ACTION_DEFAULT;
8323 MovDir[x][y] = 0; // if element was moving, stop it
8328 // not "else if" because of elements that can fall and move (EL_SPRING)
8329 if (CAN_MOVE(element) && !started_moving)
8331 int move_pattern = element_info[element].move_pattern;
8334 Moving2Blocked(x, y, &newx, &newy);
8336 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8339 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8340 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8342 WasJustMoving[x][y] = 0;
8343 CheckCollision[x][y] = 0;
8345 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8347 if (Tile[x][y] != element) // element has changed
8351 if (!MovDelay[x][y]) // start new movement phase
8353 // all objects that can change their move direction after each step
8354 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8356 if (element != EL_YAMYAM &&
8357 element != EL_DARK_YAMYAM &&
8358 element != EL_PACMAN &&
8359 !(move_pattern & MV_ANY_DIRECTION) &&
8360 move_pattern != MV_TURNING_LEFT &&
8361 move_pattern != MV_TURNING_RIGHT &&
8362 move_pattern != MV_TURNING_LEFT_RIGHT &&
8363 move_pattern != MV_TURNING_RIGHT_LEFT &&
8364 move_pattern != MV_TURNING_RANDOM)
8368 if (MovDelay[x][y] && (element == EL_BUG ||
8369 element == EL_SPACESHIP ||
8370 element == EL_SP_SNIKSNAK ||
8371 element == EL_SP_ELECTRON ||
8372 element == EL_MOLE))
8373 TEST_DrawLevelField(x, y);
8377 if (MovDelay[x][y]) // wait some time before next movement
8381 if (element == EL_ROBOT ||
8382 element == EL_YAMYAM ||
8383 element == EL_DARK_YAMYAM)
8385 DrawLevelElementAnimationIfNeeded(x, y, element);
8386 PlayLevelSoundAction(x, y, ACTION_WAITING);
8388 else if (element == EL_SP_ELECTRON)
8389 DrawLevelElementAnimationIfNeeded(x, y, element);
8390 else if (element == EL_DRAGON)
8393 int dir = MovDir[x][y];
8394 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8395 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8396 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8397 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8398 dir == MV_UP ? IMG_FLAMES_1_UP :
8399 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8400 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8402 GfxAction[x][y] = ACTION_ATTACKING;
8404 if (IS_PLAYER(x, y))
8405 DrawPlayerField(x, y);
8407 TEST_DrawLevelField(x, y);
8409 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8411 for (i = 1; i <= 3; i++)
8413 int xx = x + i * dx;
8414 int yy = y + i * dy;
8415 int sx = SCREENX(xx);
8416 int sy = SCREENY(yy);
8417 int flame_graphic = graphic + (i - 1);
8419 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8424 int flamed = MovingOrBlocked2Element(xx, yy);
8426 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8429 RemoveMovingField(xx, yy);
8431 ChangeDelay[xx][yy] = 0;
8433 Tile[xx][yy] = EL_FLAMES;
8435 if (IN_SCR_FIELD(sx, sy))
8437 TEST_DrawLevelFieldCrumbled(xx, yy);
8438 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8443 if (Tile[xx][yy] == EL_FLAMES)
8444 Tile[xx][yy] = EL_EMPTY;
8445 TEST_DrawLevelField(xx, yy);
8450 if (MovDelay[x][y]) // element still has to wait some time
8452 PlayLevelSoundAction(x, y, ACTION_WAITING);
8458 // now make next step
8460 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8462 if (DONT_COLLIDE_WITH(element) &&
8463 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8464 !PLAYER_ENEMY_PROTECTED(newx, newy))
8466 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8471 else if (CAN_MOVE_INTO_ACID(element) &&
8472 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8473 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8474 (MovDir[x][y] == MV_DOWN ||
8475 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8477 SplashAcid(newx, newy);
8478 Store[x][y] = EL_ACID;
8480 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8482 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8483 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8484 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8485 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8488 TEST_DrawLevelField(x, y);
8490 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8491 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8492 DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0);
8494 game.friends_still_needed--;
8495 if (!game.friends_still_needed &&
8497 game.all_players_gone)
8502 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8504 if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING)
8505 TEST_DrawLevelField(newx, newy);
8507 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8509 else if (!IS_FREE(newx, newy))
8511 GfxAction[x][y] = ACTION_WAITING;
8513 if (IS_PLAYER(x, y))
8514 DrawPlayerField(x, y);
8516 TEST_DrawLevelField(x, y);
8521 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8523 if (IS_FOOD_PIG(Tile[newx][newy]))
8525 if (IS_MOVING(newx, newy))
8526 RemoveMovingField(newx, newy);
8529 Tile[newx][newy] = EL_EMPTY;
8530 TEST_DrawLevelField(newx, newy);
8533 PlayLevelSound(x, y, SND_PIG_DIGGING);
8535 else if (!IS_FREE(newx, newy))
8537 if (IS_PLAYER(x, y))
8538 DrawPlayerField(x, y);
8540 TEST_DrawLevelField(x, y);
8545 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8547 if (Store[x][y] != EL_EMPTY)
8549 boolean can_clone = FALSE;
8552 // check if element to clone is still there
8553 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8555 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8563 // cannot clone or target field not free anymore -- do not clone
8564 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8565 Store[x][y] = EL_EMPTY;
8568 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8570 if (IS_MV_DIAGONAL(MovDir[x][y]))
8572 int diagonal_move_dir = MovDir[x][y];
8573 int stored = Store[x][y];
8574 int change_delay = 8;
8577 // android is moving diagonally
8579 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8581 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8582 GfxElement[x][y] = EL_EMC_ANDROID;
8583 GfxAction[x][y] = ACTION_SHRINKING;
8584 GfxDir[x][y] = diagonal_move_dir;
8585 ChangeDelay[x][y] = change_delay;
8587 if (Store[x][y] == EL_EMPTY)
8588 Store[x][y] = GfxElementEmpty[x][y];
8590 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8593 DrawLevelGraphicAnimation(x, y, graphic);
8594 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8596 if (Tile[newx][newy] == EL_ACID)
8598 SplashAcid(newx, newy);
8603 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8605 Store[newx][newy] = EL_EMC_ANDROID;
8606 GfxElement[newx][newy] = EL_EMC_ANDROID;
8607 GfxAction[newx][newy] = ACTION_GROWING;
8608 GfxDir[newx][newy] = diagonal_move_dir;
8609 ChangeDelay[newx][newy] = change_delay;
8611 graphic = el_act_dir2img(GfxElement[newx][newy],
8612 GfxAction[newx][newy], GfxDir[newx][newy]);
8614 DrawLevelGraphicAnimation(newx, newy, graphic);
8615 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8621 Tile[newx][newy] = EL_EMPTY;
8622 TEST_DrawLevelField(newx, newy);
8624 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8627 else if (!IS_FREE(newx, newy))
8632 else if (IS_CUSTOM_ELEMENT(element) &&
8633 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8635 if (!DigFieldByCE(newx, newy, element))
8638 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8640 RunnerVisit[x][y] = FrameCounter;
8641 PlayerVisit[x][y] /= 8; // expire player visit path
8644 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8646 if (!IS_FREE(newx, newy))
8648 if (IS_PLAYER(x, y))
8649 DrawPlayerField(x, y);
8651 TEST_DrawLevelField(x, y);
8657 boolean wanna_flame = !RND(10);
8658 int dx = newx - x, dy = newy - y;
8659 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8660 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8661 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8662 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8663 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8664 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8667 IS_CLASSIC_ENEMY(element1) ||
8668 IS_CLASSIC_ENEMY(element2)) &&
8669 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8670 element1 != EL_FLAMES && element2 != EL_FLAMES)
8672 ResetGfxAnimation(x, y);
8673 GfxAction[x][y] = ACTION_ATTACKING;
8675 if (IS_PLAYER(x, y))
8676 DrawPlayerField(x, y);
8678 TEST_DrawLevelField(x, y);
8680 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8682 MovDelay[x][y] = 50;
8684 Tile[newx][newy] = EL_FLAMES;
8685 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8686 Tile[newx1][newy1] = EL_FLAMES;
8687 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8688 Tile[newx2][newy2] = EL_FLAMES;
8694 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8695 Tile[newx][newy] == EL_DIAMOND)
8697 if (IS_MOVING(newx, newy))
8698 RemoveMovingField(newx, newy);
8701 Tile[newx][newy] = EL_EMPTY;
8702 TEST_DrawLevelField(newx, newy);
8705 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8707 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8708 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8710 if (AmoebaNr[newx][newy])
8712 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8713 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8714 Tile[newx][newy] == EL_BD_AMOEBA)
8715 AmoebaCnt[AmoebaNr[newx][newy]]--;
8718 if (IS_MOVING(newx, newy))
8720 RemoveMovingField(newx, newy);
8724 Tile[newx][newy] = EL_EMPTY;
8725 TEST_DrawLevelField(newx, newy);
8728 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8730 else if ((element == EL_PACMAN || element == EL_MOLE)
8731 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8733 if (AmoebaNr[newx][newy])
8735 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8736 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8737 Tile[newx][newy] == EL_BD_AMOEBA)
8738 AmoebaCnt[AmoebaNr[newx][newy]]--;
8741 if (element == EL_MOLE)
8743 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8744 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8746 ResetGfxAnimation(x, y);
8747 GfxAction[x][y] = ACTION_DIGGING;
8748 TEST_DrawLevelField(x, y);
8750 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8752 return; // wait for shrinking amoeba
8754 else // element == EL_PACMAN
8756 Tile[newx][newy] = EL_EMPTY;
8757 TEST_DrawLevelField(newx, newy);
8758 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8761 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8762 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8763 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8765 // wait for shrinking amoeba to completely disappear
8768 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8770 // object was running against a wall
8774 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8775 DrawLevelElementAnimation(x, y, element);
8777 if (DONT_TOUCH(element))
8778 TestIfBadThingTouchesPlayer(x, y);
8783 InitMovingField(x, y, MovDir[x][y]);
8785 PlayLevelSoundAction(x, y, ACTION_MOVING);
8789 ContinueMoving(x, y);
8792 void ContinueMoving(int x, int y)
8794 int element = Tile[x][y];
8795 struct ElementInfo *ei = &element_info[element];
8796 int direction = MovDir[x][y];
8797 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8798 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8799 int newx = x + dx, newy = y + dy;
8800 int stored = Store[x][y];
8801 int stored_new = Store[newx][newy];
8802 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8803 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8804 boolean last_line = (newy == lev_fieldy - 1);
8805 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8807 if (pushed_by_player) // special case: moving object pushed by player
8809 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos));
8811 else if (use_step_delay) // special case: moving object has step delay
8813 if (!MovDelay[x][y])
8814 MovPos[x][y] += getElementMoveStepsize(x, y);
8819 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8823 TEST_DrawLevelField(x, y);
8825 return; // element is still waiting
8828 else // normal case: generically moving object
8830 MovPos[x][y] += getElementMoveStepsize(x, y);
8833 if (ABS(MovPos[x][y]) < TILEX)
8835 TEST_DrawLevelField(x, y);
8837 return; // element is still moving
8840 // element reached destination field
8842 Tile[x][y] = EL_EMPTY;
8843 Tile[newx][newy] = element;
8844 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8846 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8848 element = Tile[newx][newy] = EL_ACID;
8850 else if (element == EL_MOLE)
8852 Tile[x][y] = EL_SAND;
8854 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8856 else if (element == EL_QUICKSAND_FILLING)
8858 element = Tile[newx][newy] = get_next_element(element);
8859 Store[newx][newy] = Store[x][y];
8861 else if (element == EL_QUICKSAND_EMPTYING)
8863 Tile[x][y] = get_next_element(element);
8864 element = Tile[newx][newy] = Store[x][y];
8866 else if (element == EL_QUICKSAND_FAST_FILLING)
8868 element = Tile[newx][newy] = get_next_element(element);
8869 Store[newx][newy] = Store[x][y];
8871 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8873 Tile[x][y] = get_next_element(element);
8874 element = Tile[newx][newy] = Store[x][y];
8876 else if (element == EL_MAGIC_WALL_FILLING)
8878 element = Tile[newx][newy] = get_next_element(element);
8879 if (!game.magic_wall_active)
8880 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8881 Store[newx][newy] = Store[x][y];
8883 else if (element == EL_MAGIC_WALL_EMPTYING)
8885 Tile[x][y] = get_next_element(element);
8886 if (!game.magic_wall_active)
8887 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8888 element = Tile[newx][newy] = Store[x][y];
8890 InitField(newx, newy, FALSE);
8892 else if (element == EL_BD_MAGIC_WALL_FILLING)
8894 element = Tile[newx][newy] = get_next_element(element);
8895 if (!game.magic_wall_active)
8896 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8897 Store[newx][newy] = Store[x][y];
8899 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8901 Tile[x][y] = get_next_element(element);
8902 if (!game.magic_wall_active)
8903 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8904 element = Tile[newx][newy] = Store[x][y];
8906 InitField(newx, newy, FALSE);
8908 else if (element == EL_DC_MAGIC_WALL_FILLING)
8910 element = Tile[newx][newy] = get_next_element(element);
8911 if (!game.magic_wall_active)
8912 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8913 Store[newx][newy] = Store[x][y];
8915 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8917 Tile[x][y] = get_next_element(element);
8918 if (!game.magic_wall_active)
8919 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8920 element = Tile[newx][newy] = Store[x][y];
8922 InitField(newx, newy, FALSE);
8924 else if (element == EL_AMOEBA_DROPPING)
8926 Tile[x][y] = get_next_element(element);
8927 element = Tile[newx][newy] = Store[x][y];
8929 else if (element == EL_SOKOBAN_OBJECT)
8932 Tile[x][y] = Back[x][y];
8934 if (Back[newx][newy])
8935 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8937 Back[x][y] = Back[newx][newy] = 0;
8940 Store[x][y] = EL_EMPTY;
8945 MovDelay[newx][newy] = 0;
8947 if (CAN_CHANGE_OR_HAS_ACTION(element))
8949 // copy element change control values to new field
8950 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8951 ChangePage[newx][newy] = ChangePage[x][y];
8952 ChangeCount[newx][newy] = ChangeCount[x][y];
8953 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8956 CustomValue[newx][newy] = CustomValue[x][y];
8958 ChangeDelay[x][y] = 0;
8959 ChangePage[x][y] = -1;
8960 ChangeCount[x][y] = 0;
8961 ChangeEvent[x][y] = -1;
8963 CustomValue[x][y] = 0;
8965 // copy animation control values to new field
8966 GfxFrame[newx][newy] = GfxFrame[x][y];
8967 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8968 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8969 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8971 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8973 // some elements can leave other elements behind after moving
8974 if (ei->move_leave_element != EL_EMPTY &&
8975 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8976 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8978 int move_leave_element = ei->move_leave_element;
8980 // this makes it possible to leave the removed element again
8981 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8982 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8984 Tile[x][y] = move_leave_element;
8986 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8987 MovDir[x][y] = direction;
8989 InitField(x, y, FALSE);
8991 if (GFX_CRUMBLED(Tile[x][y]))
8992 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8994 if (IS_PLAYER_ELEMENT(move_leave_element))
8995 RelocatePlayer(x, y, move_leave_element);
8998 // do this after checking for left-behind element
8999 ResetGfxAnimation(x, y); // reset animation values for old field
9001 if (!CAN_MOVE(element) ||
9002 (CAN_FALL(element) && direction == MV_DOWN &&
9003 (element == EL_SPRING ||
9004 element_info[element].move_pattern == MV_WHEN_PUSHED ||
9005 element_info[element].move_pattern == MV_WHEN_DROPPED)))
9006 GfxDir[x][y] = MovDir[newx][newy] = 0;
9008 TEST_DrawLevelField(x, y);
9009 TEST_DrawLevelField(newx, newy);
9011 Stop[newx][newy] = TRUE; // ignore this element until the next frame
9013 // prevent pushed element from moving on in pushed direction
9014 if (pushed_by_player && CAN_MOVE(element) &&
9015 element_info[element].move_pattern & MV_ANY_DIRECTION &&
9016 !(element_info[element].move_pattern & direction))
9017 TurnRound(newx, newy);
9019 // prevent elements on conveyor belt from moving on in last direction
9020 if (pushed_by_conveyor && CAN_FALL(element) &&
9021 direction & MV_HORIZONTAL)
9022 MovDir[newx][newy] = 0;
9024 if (!pushed_by_player)
9026 int nextx = newx + dx, nexty = newy + dy;
9027 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
9029 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
9031 if (CAN_FALL(element) && direction == MV_DOWN)
9032 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
9034 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
9035 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
9037 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
9038 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
9041 if (DONT_TOUCH(element)) // object may be nasty to player or others
9043 TestIfBadThingTouchesPlayer(newx, newy);
9044 TestIfBadThingTouchesFriend(newx, newy);
9046 if (!IS_CUSTOM_ELEMENT(element))
9047 TestIfBadThingTouchesOtherBadThing(newx, newy);
9049 else if (element == EL_PENGUIN)
9050 TestIfFriendTouchesBadThing(newx, newy);
9052 if (DONT_GET_HIT_BY(element))
9054 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
9057 // give the player one last chance (one more frame) to move away
9058 if (CAN_FALL(element) && direction == MV_DOWN &&
9059 (last_line || (!IS_FREE(x, newy + 1) &&
9060 (!IS_PLAYER(x, newy + 1) ||
9061 game.engine_version < VERSION_IDENT(3,1,1,0)))))
9064 if (pushed_by_player && !game.use_change_when_pushing_bug)
9066 int push_side = MV_DIR_OPPOSITE(direction);
9067 struct PlayerInfo *player = PLAYERINFO(x, y);
9069 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
9070 player->index_bit, push_side);
9071 CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X,
9072 player->index_bit, push_side);
9075 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
9076 MovDelay[newx][newy] = 1;
9078 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
9080 TestIfElementTouchesCustomElement(x, y); // empty or new element
9081 TestIfElementHitsCustomElement(newx, newy, direction);
9082 TestIfPlayerTouchesCustomElement(newx, newy);
9083 TestIfElementTouchesCustomElement(newx, newy);
9085 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
9086 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
9087 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
9088 MV_DIR_OPPOSITE(direction));
9091 int AmoebaNeighbourNr(int ax, int ay)
9094 int element = Tile[ax][ay];
9096 struct XY *xy = xy_topdown;
9098 for (i = 0; i < NUM_DIRECTIONS; i++)
9100 int x = ax + xy[i].x;
9101 int y = ay + xy[i].y;
9103 if (!IN_LEV_FIELD(x, y))
9106 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
9107 group_nr = AmoebaNr[x][y];
9113 static void AmoebaMerge(int ax, int ay)
9115 int i, x, y, xx, yy;
9116 int new_group_nr = AmoebaNr[ax][ay];
9117 struct XY *xy = xy_topdown;
9119 if (new_group_nr == 0)
9122 for (i = 0; i < NUM_DIRECTIONS; i++)
9127 if (!IN_LEV_FIELD(x, y))
9130 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9131 Tile[x][y] == EL_BD_AMOEBA ||
9132 Tile[x][y] == EL_AMOEBA_DEAD) &&
9133 AmoebaNr[x][y] != new_group_nr)
9135 int old_group_nr = AmoebaNr[x][y];
9137 if (old_group_nr == 0)
9140 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9141 AmoebaCnt[old_group_nr] = 0;
9142 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9143 AmoebaCnt2[old_group_nr] = 0;
9145 SCAN_PLAYFIELD(xx, yy)
9147 if (AmoebaNr[xx][yy] == old_group_nr)
9148 AmoebaNr[xx][yy] = new_group_nr;
9154 void AmoebaToDiamond(int ax, int ay)
9158 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9160 int group_nr = AmoebaNr[ax][ay];
9165 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9166 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9172 SCAN_PLAYFIELD(x, y)
9174 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9177 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9181 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9182 SND_AMOEBA_TURNING_TO_GEM :
9183 SND_AMOEBA_TURNING_TO_ROCK));
9188 struct XY *xy = xy_topdown;
9190 for (i = 0; i < NUM_DIRECTIONS; i++)
9195 if (!IN_LEV_FIELD(x, y))
9198 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9200 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9201 SND_AMOEBA_TURNING_TO_GEM :
9202 SND_AMOEBA_TURNING_TO_ROCK));
9209 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9212 int group_nr = AmoebaNr[ax][ay];
9213 boolean done = FALSE;
9218 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9219 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9225 SCAN_PLAYFIELD(x, y)
9227 if (AmoebaNr[x][y] == group_nr &&
9228 (Tile[x][y] == EL_AMOEBA_DEAD ||
9229 Tile[x][y] == EL_BD_AMOEBA ||
9230 Tile[x][y] == EL_AMOEBA_GROWING))
9233 Tile[x][y] = new_element;
9234 InitField(x, y, FALSE);
9235 TEST_DrawLevelField(x, y);
9241 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9242 SND_BD_AMOEBA_TURNING_TO_ROCK :
9243 SND_BD_AMOEBA_TURNING_TO_GEM));
9246 static void AmoebaGrowing(int x, int y)
9248 static DelayCounter sound_delay = { 0 };
9250 if (!MovDelay[x][y]) // start new growing cycle
9254 if (DelayReached(&sound_delay))
9256 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9257 sound_delay.value = 30;
9261 if (MovDelay[x][y]) // wait some time before growing bigger
9264 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9266 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9267 6 - MovDelay[x][y]);
9269 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9272 if (!MovDelay[x][y])
9274 Tile[x][y] = Store[x][y];
9276 TEST_DrawLevelField(x, y);
9281 static void AmoebaShrinking(int x, int y)
9283 static DelayCounter sound_delay = { 0 };
9285 if (!MovDelay[x][y]) // start new shrinking cycle
9289 if (DelayReached(&sound_delay))
9290 sound_delay.value = 30;
9293 if (MovDelay[x][y]) // wait some time before shrinking
9296 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9298 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9299 6 - MovDelay[x][y]);
9301 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9304 if (!MovDelay[x][y])
9306 Tile[x][y] = EL_EMPTY;
9307 TEST_DrawLevelField(x, y);
9309 // don't let mole enter this field in this cycle;
9310 // (give priority to objects falling to this field from above)
9316 static void AmoebaReproduce(int ax, int ay)
9319 int element = Tile[ax][ay];
9320 int graphic = el2img(element);
9321 int newax = ax, neway = ay;
9322 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9323 struct XY *xy = xy_topdown;
9325 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9327 Tile[ax][ay] = EL_AMOEBA_DEAD;
9328 TEST_DrawLevelField(ax, ay);
9332 if (IS_ANIMATED(graphic))
9333 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9335 if (!MovDelay[ax][ay]) // start making new amoeba field
9336 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9338 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9341 if (MovDelay[ax][ay])
9345 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9348 int x = ax + xy[start].x;
9349 int y = ay + xy[start].y;
9351 if (!IN_LEV_FIELD(x, y))
9354 if (IS_FREE(x, y) ||
9355 CAN_GROW_INTO(Tile[x][y]) ||
9356 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9357 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9363 if (newax == ax && neway == ay)
9366 else // normal or "filled" (BD style) amoeba
9369 boolean waiting_for_player = FALSE;
9371 for (i = 0; i < NUM_DIRECTIONS; i++)
9373 int j = (start + i) % 4;
9374 int x = ax + xy[j].x;
9375 int y = ay + xy[j].y;
9377 if (!IN_LEV_FIELD(x, y))
9380 if (IS_FREE(x, y) ||
9381 CAN_GROW_INTO(Tile[x][y]) ||
9382 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9383 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9389 else if (IS_PLAYER(x, y))
9390 waiting_for_player = TRUE;
9393 if (newax == ax && neway == ay) // amoeba cannot grow
9395 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9397 Tile[ax][ay] = EL_AMOEBA_DEAD;
9398 TEST_DrawLevelField(ax, ay);
9399 AmoebaCnt[AmoebaNr[ax][ay]]--;
9401 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9403 if (element == EL_AMOEBA_FULL)
9404 AmoebaToDiamond(ax, ay);
9405 else if (element == EL_BD_AMOEBA)
9406 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9411 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9413 // amoeba gets larger by growing in some direction
9415 int new_group_nr = AmoebaNr[ax][ay];
9418 if (new_group_nr == 0)
9420 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9422 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9428 AmoebaNr[newax][neway] = new_group_nr;
9429 AmoebaCnt[new_group_nr]++;
9430 AmoebaCnt2[new_group_nr]++;
9432 // if amoeba touches other amoeba(s) after growing, unify them
9433 AmoebaMerge(newax, neway);
9435 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9437 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9443 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9444 (neway == lev_fieldy - 1 && newax != ax))
9446 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9447 Store[newax][neway] = element;
9449 else if (neway == ay || element == EL_EMC_DRIPPER)
9451 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9453 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9457 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9458 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9459 Store[ax][ay] = EL_AMOEBA_DROP;
9460 ContinueMoving(ax, ay);
9464 TEST_DrawLevelField(newax, neway);
9467 static void Life(int ax, int ay)
9471 int element = Tile[ax][ay];
9472 int graphic = el2img(element);
9473 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9475 boolean changed = FALSE;
9477 if (IS_ANIMATED(graphic))
9478 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9483 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9484 MovDelay[ax][ay] = life_time;
9486 if (MovDelay[ax][ay]) // wait some time before next cycle
9489 if (MovDelay[ax][ay])
9493 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9495 int xx = ax + x1, yy = ay + y1;
9496 int old_element = Tile[xx][yy];
9497 int num_neighbours = 0;
9499 if (!IN_LEV_FIELD(xx, yy))
9502 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9504 int x = xx + x2, y = yy + y2;
9506 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9509 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9510 boolean is_neighbour = FALSE;
9512 if (level.use_life_bugs)
9514 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9515 (IS_FREE(x, y) && Stop[x][y]));
9518 (Last[x][y] == element || is_player_cell);
9524 boolean is_free = FALSE;
9526 if (level.use_life_bugs)
9527 is_free = (IS_FREE(xx, yy));
9529 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9531 if (xx == ax && yy == ay) // field in the middle
9533 if (num_neighbours < life_parameter[0] ||
9534 num_neighbours > life_parameter[1])
9536 Tile[xx][yy] = EL_EMPTY;
9537 if (Tile[xx][yy] != old_element)
9538 TEST_DrawLevelField(xx, yy);
9539 Stop[xx][yy] = TRUE;
9543 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9544 { // free border field
9545 if (num_neighbours >= life_parameter[2] &&
9546 num_neighbours <= life_parameter[3])
9548 Tile[xx][yy] = element;
9549 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9550 if (Tile[xx][yy] != old_element)
9551 TEST_DrawLevelField(xx, yy);
9552 Stop[xx][yy] = TRUE;
9559 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9560 SND_GAME_OF_LIFE_GROWING);
9563 static void InitRobotWheel(int x, int y)
9565 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9568 static void RunRobotWheel(int x, int y)
9570 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9573 static void StopRobotWheel(int x, int y)
9575 if (game.robot_wheel_x == x &&
9576 game.robot_wheel_y == y)
9578 game.robot_wheel_x = -1;
9579 game.robot_wheel_y = -1;
9580 game.robot_wheel_active = FALSE;
9584 static void InitTimegateWheel(int x, int y)
9586 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9589 static void RunTimegateWheel(int x, int y)
9591 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9594 static void InitMagicBallDelay(int x, int y)
9596 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9599 static void ActivateMagicBall(int bx, int by)
9603 if (level.ball_random)
9605 int pos_border = RND(8); // select one of the eight border elements
9606 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9607 int xx = pos_content % 3;
9608 int yy = pos_content / 3;
9613 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9614 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9618 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9620 int xx = x - bx + 1;
9621 int yy = y - by + 1;
9623 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9624 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9628 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9631 static void CheckExit(int x, int y)
9633 if (game.gems_still_needed > 0 ||
9634 game.sokoban_fields_still_needed > 0 ||
9635 game.sokoban_objects_still_needed > 0 ||
9636 game.lights_still_needed > 0)
9638 int element = Tile[x][y];
9639 int graphic = el2img(element);
9641 if (IS_ANIMATED(graphic))
9642 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9647 // do not re-open exit door closed after last player
9648 if (game.all_players_gone)
9651 Tile[x][y] = EL_EXIT_OPENING;
9653 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9656 static void CheckExitEM(int x, int y)
9658 if (game.gems_still_needed > 0 ||
9659 game.sokoban_fields_still_needed > 0 ||
9660 game.sokoban_objects_still_needed > 0 ||
9661 game.lights_still_needed > 0)
9663 int element = Tile[x][y];
9664 int graphic = el2img(element);
9666 if (IS_ANIMATED(graphic))
9667 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9672 // do not re-open exit door closed after last player
9673 if (game.all_players_gone)
9676 Tile[x][y] = EL_EM_EXIT_OPENING;
9678 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9681 static void CheckExitSteel(int x, int y)
9683 if (game.gems_still_needed > 0 ||
9684 game.sokoban_fields_still_needed > 0 ||
9685 game.sokoban_objects_still_needed > 0 ||
9686 game.lights_still_needed > 0)
9688 int element = Tile[x][y];
9689 int graphic = el2img(element);
9691 if (IS_ANIMATED(graphic))
9692 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9697 // do not re-open exit door closed after last player
9698 if (game.all_players_gone)
9701 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9703 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9706 static void CheckExitSteelEM(int x, int y)
9708 if (game.gems_still_needed > 0 ||
9709 game.sokoban_fields_still_needed > 0 ||
9710 game.sokoban_objects_still_needed > 0 ||
9711 game.lights_still_needed > 0)
9713 int element = Tile[x][y];
9714 int graphic = el2img(element);
9716 if (IS_ANIMATED(graphic))
9717 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9722 // do not re-open exit door closed after last player
9723 if (game.all_players_gone)
9726 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9728 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9731 static void CheckExitSP(int x, int y)
9733 if (game.gems_still_needed > 0)
9735 int element = Tile[x][y];
9736 int graphic = el2img(element);
9738 if (IS_ANIMATED(graphic))
9739 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9744 // do not re-open exit door closed after last player
9745 if (game.all_players_gone)
9748 Tile[x][y] = EL_SP_EXIT_OPENING;
9750 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9753 static void CloseAllOpenTimegates(void)
9757 SCAN_PLAYFIELD(x, y)
9759 int element = Tile[x][y];
9761 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9763 Tile[x][y] = EL_TIMEGATE_CLOSING;
9765 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9770 static void DrawTwinkleOnField(int x, int y)
9772 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9775 if (Tile[x][y] == EL_BD_DIAMOND)
9778 if (MovDelay[x][y] == 0) // next animation frame
9779 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9781 if (MovDelay[x][y] != 0) // wait some time before next frame
9785 DrawLevelElementAnimation(x, y, Tile[x][y]);
9787 if (MovDelay[x][y] != 0)
9789 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9790 10 - MovDelay[x][y]);
9792 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9797 static void WallGrowing(int x, int y)
9801 if (!MovDelay[x][y]) // next animation frame
9802 MovDelay[x][y] = 3 * delay;
9804 if (MovDelay[x][y]) // wait some time before next frame
9808 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9810 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9811 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9813 DrawLevelGraphic(x, y, graphic, frame);
9816 if (!MovDelay[x][y])
9818 if (MovDir[x][y] == MV_LEFT)
9820 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9821 TEST_DrawLevelField(x - 1, y);
9823 else if (MovDir[x][y] == MV_RIGHT)
9825 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9826 TEST_DrawLevelField(x + 1, y);
9828 else if (MovDir[x][y] == MV_UP)
9830 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9831 TEST_DrawLevelField(x, y - 1);
9835 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9836 TEST_DrawLevelField(x, y + 1);
9839 Tile[x][y] = Store[x][y];
9841 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9842 TEST_DrawLevelField(x, y);
9847 static void CheckWallGrowing(int ax, int ay)
9849 int element = Tile[ax][ay];
9850 int graphic = el2img(element);
9851 boolean free_top = FALSE;
9852 boolean free_bottom = FALSE;
9853 boolean free_left = FALSE;
9854 boolean free_right = FALSE;
9855 boolean stop_top = FALSE;
9856 boolean stop_bottom = FALSE;
9857 boolean stop_left = FALSE;
9858 boolean stop_right = FALSE;
9859 boolean new_wall = FALSE;
9861 boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9862 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9863 element == EL_EXPANDABLE_STEELWALL_ANY);
9865 boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9866 element == EL_EXPANDABLE_WALL_ANY ||
9867 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9868 element == EL_EXPANDABLE_STEELWALL_ANY);
9870 boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9871 element == EL_EXPANDABLE_WALL_ANY ||
9872 element == EL_EXPANDABLE_WALL ||
9873 element == EL_BD_EXPANDABLE_WALL ||
9874 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9875 element == EL_EXPANDABLE_STEELWALL_ANY);
9877 boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9878 element == EL_EXPANDABLE_STEELWALL_VERTICAL);
9880 boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9881 element == EL_EXPANDABLE_WALL ||
9882 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL);
9884 int wall_growing = (is_steelwall ?
9885 EL_EXPANDABLE_STEELWALL_GROWING :
9886 EL_EXPANDABLE_WALL_GROWING);
9888 int gfx_wall_growing_up = (is_steelwall ?
9889 IMG_EXPANDABLE_STEELWALL_GROWING_UP :
9890 IMG_EXPANDABLE_WALL_GROWING_UP);
9891 int gfx_wall_growing_down = (is_steelwall ?
9892 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN :
9893 IMG_EXPANDABLE_WALL_GROWING_DOWN);
9894 int gfx_wall_growing_left = (is_steelwall ?
9895 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT :
9896 IMG_EXPANDABLE_WALL_GROWING_LEFT);
9897 int gfx_wall_growing_right = (is_steelwall ?
9898 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT :
9899 IMG_EXPANDABLE_WALL_GROWING_RIGHT);
9901 if (IS_ANIMATED(graphic))
9902 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9904 if (!MovDelay[ax][ay]) // start building new wall
9905 MovDelay[ax][ay] = 6;
9907 if (MovDelay[ax][ay]) // wait some time before building new wall
9910 if (MovDelay[ax][ay])
9914 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9916 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9918 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9920 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9927 Tile[ax][ay - 1] = wall_growing;
9928 Store[ax][ay - 1] = element;
9929 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9931 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9932 DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0);
9939 Tile[ax][ay + 1] = wall_growing;
9940 Store[ax][ay + 1] = element;
9941 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9943 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9944 DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0);
9950 if (grow_horizontal)
9954 Tile[ax - 1][ay] = wall_growing;
9955 Store[ax - 1][ay] = element;
9956 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9958 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9959 DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0);
9966 Tile[ax + 1][ay] = wall_growing;
9967 Store[ax + 1][ay] = element;
9968 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
9970 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
9971 DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0);
9977 if (element == EL_EXPANDABLE_WALL && (free_left || free_right))
9978 TEST_DrawLevelField(ax, ay);
9980 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
9982 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
9984 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
9986 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
9989 if (((stop_top && stop_bottom) || stop_horizontal) &&
9990 ((stop_left && stop_right) || stop_vertical))
9991 Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL);
9994 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9997 static void CheckForDragon(int x, int y)
10000 boolean dragon_found = FALSE;
10001 struct XY *xy = xy_topdown;
10003 for (i = 0; i < NUM_DIRECTIONS; i++)
10005 for (j = 0; j < 4; j++)
10007 int xx = x + j * xy[i].x;
10008 int yy = y + j * xy[i].y;
10010 if (IN_LEV_FIELD(xx, yy) &&
10011 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
10013 if (Tile[xx][yy] == EL_DRAGON)
10014 dragon_found = TRUE;
10023 for (i = 0; i < NUM_DIRECTIONS; i++)
10025 for (j = 0; j < 3; j++)
10027 int xx = x + j * xy[i].x;
10028 int yy = y + j * xy[i].y;
10030 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
10032 Tile[xx][yy] = EL_EMPTY;
10033 TEST_DrawLevelField(xx, yy);
10042 static void InitBuggyBase(int x, int y)
10044 int element = Tile[x][y];
10045 int activating_delay = FRAMES_PER_SECOND / 4;
10047 ChangeDelay[x][y] =
10048 (element == EL_SP_BUGGY_BASE ?
10049 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10050 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10052 element == EL_SP_BUGGY_BASE_ACTIVE ?
10053 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10056 static void WarnBuggyBase(int x, int y)
10059 struct XY *xy = xy_topdown;
10061 for (i = 0; i < NUM_DIRECTIONS; i++)
10063 int xx = x + xy[i].x;
10064 int yy = y + xy[i].y;
10066 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10068 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10075 static void InitTrap(int x, int y)
10077 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10080 static void ActivateTrap(int x, int y)
10082 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10085 static void ChangeActiveTrap(int x, int y)
10087 int graphic = IMG_TRAP_ACTIVE;
10089 // if new animation frame was drawn, correct crumbled sand border
10090 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10091 TEST_DrawLevelFieldCrumbled(x, y);
10094 static int getSpecialActionElement(int element, int number, int base_element)
10096 return (element != EL_EMPTY ? element :
10097 number != -1 ? base_element + number - 1 :
10101 static int getModifiedActionNumber(int value_old, int operator, int operand,
10102 int value_min, int value_max)
10104 int value_new = (operator == CA_MODE_SET ? operand :
10105 operator == CA_MODE_ADD ? value_old + operand :
10106 operator == CA_MODE_SUBTRACT ? value_old - operand :
10107 operator == CA_MODE_MULTIPLY ? value_old * operand :
10108 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10109 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10112 return (value_new < value_min ? value_min :
10113 value_new > value_max ? value_max :
10117 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10119 struct ElementInfo *ei = &element_info[element];
10120 struct ElementChangeInfo *change = &ei->change_page[page];
10121 int target_element = change->target_element;
10122 int action_type = change->action_type;
10123 int action_mode = change->action_mode;
10124 int action_arg = change->action_arg;
10125 int action_element = change->action_element;
10128 if (!change->has_action)
10131 // ---------- determine action paramater values -----------------------------
10133 int level_time_value =
10134 (level.time > 0 ? TimeLeft :
10137 int action_arg_element_raw =
10138 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10139 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10140 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10141 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10142 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10143 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10144 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10146 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10148 int action_arg_direction =
10149 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10150 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10151 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10152 change->actual_trigger_side :
10153 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10154 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10157 int action_arg_number_min =
10158 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10161 int action_arg_number_max =
10162 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10163 action_type == CA_SET_LEVEL_GEMS ? 999 :
10164 action_type == CA_SET_LEVEL_TIME ? 9999 :
10165 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10166 action_type == CA_SET_CE_VALUE ? 9999 :
10167 action_type == CA_SET_CE_SCORE ? 9999 :
10170 int action_arg_number_reset =
10171 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10172 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10173 action_type == CA_SET_LEVEL_TIME ? level.time :
10174 action_type == CA_SET_LEVEL_SCORE ? 0 :
10175 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10176 action_type == CA_SET_CE_SCORE ? 0 :
10179 int action_arg_number =
10180 (action_arg <= CA_ARG_MAX ? action_arg :
10181 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10182 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10183 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10184 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10185 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10186 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10187 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10188 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10189 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10190 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10191 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10192 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10193 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10194 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10195 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10196 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10197 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10198 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10199 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10200 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10201 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10204 int action_arg_number_old =
10205 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10206 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10207 action_type == CA_SET_LEVEL_SCORE ? game.score :
10208 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10209 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10212 int action_arg_number_new =
10213 getModifiedActionNumber(action_arg_number_old,
10214 action_mode, action_arg_number,
10215 action_arg_number_min, action_arg_number_max);
10217 int trigger_player_bits =
10218 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10219 change->actual_trigger_player_bits : change->trigger_player);
10221 int action_arg_player_bits =
10222 (action_arg >= CA_ARG_PLAYER_1 &&
10223 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10224 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10225 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10228 // ---------- execute action -----------------------------------------------
10230 switch (action_type)
10237 // ---------- level actions ----------------------------------------------
10239 case CA_RESTART_LEVEL:
10241 game.restart_level = TRUE;
10246 case CA_SHOW_ENVELOPE:
10248 int element = getSpecialActionElement(action_arg_element,
10249 action_arg_number, EL_ENVELOPE_1);
10251 if (IS_ENVELOPE(element))
10252 local_player->show_envelope = element;
10257 case CA_SET_LEVEL_TIME:
10259 if (level.time > 0) // only modify limited time value
10261 TimeLeft = action_arg_number_new;
10263 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10265 DisplayGameControlValues();
10267 if (!TimeLeft && game.time_limit)
10268 for (i = 0; i < MAX_PLAYERS; i++)
10269 KillPlayer(&stored_player[i]);
10275 case CA_SET_LEVEL_SCORE:
10277 game.score = action_arg_number_new;
10279 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10281 DisplayGameControlValues();
10286 case CA_SET_LEVEL_GEMS:
10288 game.gems_still_needed = action_arg_number_new;
10290 game.snapshot.collected_item = TRUE;
10292 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10294 DisplayGameControlValues();
10299 case CA_SET_LEVEL_WIND:
10301 game.wind_direction = action_arg_direction;
10306 case CA_SET_LEVEL_RANDOM_SEED:
10308 // ensure that setting a new random seed while playing is predictable
10309 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10314 // ---------- player actions ---------------------------------------------
10316 case CA_MOVE_PLAYER:
10317 case CA_MOVE_PLAYER_NEW:
10319 // automatically move to the next field in specified direction
10320 for (i = 0; i < MAX_PLAYERS; i++)
10321 if (trigger_player_bits & (1 << i))
10322 if (action_type == CA_MOVE_PLAYER ||
10323 stored_player[i].MovPos == 0)
10324 stored_player[i].programmed_action = action_arg_direction;
10329 case CA_EXIT_PLAYER:
10331 for (i = 0; i < MAX_PLAYERS; i++)
10332 if (action_arg_player_bits & (1 << i))
10333 ExitPlayer(&stored_player[i]);
10335 if (game.players_still_needed == 0)
10341 case CA_KILL_PLAYER:
10343 for (i = 0; i < MAX_PLAYERS; i++)
10344 if (action_arg_player_bits & (1 << i))
10345 KillPlayer(&stored_player[i]);
10350 case CA_SET_PLAYER_KEYS:
10352 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10353 int element = getSpecialActionElement(action_arg_element,
10354 action_arg_number, EL_KEY_1);
10356 if (IS_KEY(element))
10358 for (i = 0; i < MAX_PLAYERS; i++)
10360 if (trigger_player_bits & (1 << i))
10362 stored_player[i].key[KEY_NR(element)] = key_state;
10364 DrawGameDoorValues();
10372 case CA_SET_PLAYER_SPEED:
10374 for (i = 0; i < MAX_PLAYERS; i++)
10376 if (trigger_player_bits & (1 << i))
10378 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10380 if (action_arg == CA_ARG_SPEED_FASTER &&
10381 stored_player[i].cannot_move)
10383 action_arg_number = STEPSIZE_VERY_SLOW;
10385 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10386 action_arg == CA_ARG_SPEED_FASTER)
10388 action_arg_number = 2;
10389 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10392 else if (action_arg == CA_ARG_NUMBER_RESET)
10394 action_arg_number = level.initial_player_stepsize[i];
10398 getModifiedActionNumber(move_stepsize,
10401 action_arg_number_min,
10402 action_arg_number_max);
10404 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10411 case CA_SET_PLAYER_SHIELD:
10413 for (i = 0; i < MAX_PLAYERS; i++)
10415 if (trigger_player_bits & (1 << i))
10417 if (action_arg == CA_ARG_SHIELD_OFF)
10419 stored_player[i].shield_normal_time_left = 0;
10420 stored_player[i].shield_deadly_time_left = 0;
10422 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10424 stored_player[i].shield_normal_time_left = 999999;
10426 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10428 stored_player[i].shield_normal_time_left = 999999;
10429 stored_player[i].shield_deadly_time_left = 999999;
10437 case CA_SET_PLAYER_GRAVITY:
10439 for (i = 0; i < MAX_PLAYERS; i++)
10441 if (trigger_player_bits & (1 << i))
10443 stored_player[i].gravity =
10444 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10445 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10446 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10447 stored_player[i].gravity);
10454 case CA_SET_PLAYER_ARTWORK:
10456 for (i = 0; i < MAX_PLAYERS; i++)
10458 if (trigger_player_bits & (1 << i))
10460 int artwork_element = action_arg_element;
10462 if (action_arg == CA_ARG_ELEMENT_RESET)
10464 (level.use_artwork_element[i] ? level.artwork_element[i] :
10465 stored_player[i].element_nr);
10467 if (stored_player[i].artwork_element != artwork_element)
10468 stored_player[i].Frame = 0;
10470 stored_player[i].artwork_element = artwork_element;
10472 SetPlayerWaiting(&stored_player[i], FALSE);
10474 // set number of special actions for bored and sleeping animation
10475 stored_player[i].num_special_action_bored =
10476 get_num_special_action(artwork_element,
10477 ACTION_BORING_1, ACTION_BORING_LAST);
10478 stored_player[i].num_special_action_sleeping =
10479 get_num_special_action(artwork_element,
10480 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10487 case CA_SET_PLAYER_INVENTORY:
10489 for (i = 0; i < MAX_PLAYERS; i++)
10491 struct PlayerInfo *player = &stored_player[i];
10494 if (trigger_player_bits & (1 << i))
10496 int inventory_element = action_arg_element;
10498 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10499 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10500 action_arg == CA_ARG_ELEMENT_ACTION)
10502 int element = inventory_element;
10503 int collect_count = element_info[element].collect_count_initial;
10505 if (!IS_CUSTOM_ELEMENT(element))
10508 if (collect_count == 0)
10509 player->inventory_infinite_element = element;
10511 for (k = 0; k < collect_count; k++)
10512 if (player->inventory_size < MAX_INVENTORY_SIZE)
10513 player->inventory_element[player->inventory_size++] =
10516 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10517 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10518 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10520 if (player->inventory_infinite_element != EL_UNDEFINED &&
10521 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10522 action_arg_element_raw))
10523 player->inventory_infinite_element = EL_UNDEFINED;
10525 for (k = 0, j = 0; j < player->inventory_size; j++)
10527 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10528 action_arg_element_raw))
10529 player->inventory_element[k++] = player->inventory_element[j];
10532 player->inventory_size = k;
10534 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10536 if (player->inventory_size > 0)
10538 for (j = 0; j < player->inventory_size - 1; j++)
10539 player->inventory_element[j] = player->inventory_element[j + 1];
10541 player->inventory_size--;
10544 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10546 if (player->inventory_size > 0)
10547 player->inventory_size--;
10549 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10551 player->inventory_infinite_element = EL_UNDEFINED;
10552 player->inventory_size = 0;
10554 else if (action_arg == CA_ARG_INVENTORY_RESET)
10556 player->inventory_infinite_element = EL_UNDEFINED;
10557 player->inventory_size = 0;
10559 if (level.use_initial_inventory[i])
10561 for (j = 0; j < level.initial_inventory_size[i]; j++)
10563 int element = level.initial_inventory_content[i][j];
10564 int collect_count = element_info[element].collect_count_initial;
10566 if (!IS_CUSTOM_ELEMENT(element))
10569 if (collect_count == 0)
10570 player->inventory_infinite_element = element;
10572 for (k = 0; k < collect_count; k++)
10573 if (player->inventory_size < MAX_INVENTORY_SIZE)
10574 player->inventory_element[player->inventory_size++] =
10585 // ---------- CE actions -------------------------------------------------
10587 case CA_SET_CE_VALUE:
10589 int last_ce_value = CustomValue[x][y];
10591 CustomValue[x][y] = action_arg_number_new;
10593 if (CustomValue[x][y] != last_ce_value)
10595 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10596 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10598 if (CustomValue[x][y] == 0)
10600 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10601 ChangeCount[x][y] = 0; // allow at least one more change
10603 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10604 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10611 case CA_SET_CE_SCORE:
10613 int last_ce_score = ei->collect_score;
10615 ei->collect_score = action_arg_number_new;
10617 if (ei->collect_score != last_ce_score)
10619 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10620 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10622 if (ei->collect_score == 0)
10626 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10627 ChangeCount[x][y] = 0; // allow at least one more change
10629 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10630 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10633 This is a very special case that seems to be a mixture between
10634 CheckElementChange() and CheckTriggeredElementChange(): while
10635 the first one only affects single elements that are triggered
10636 directly, the second one affects multiple elements in the playfield
10637 that are triggered indirectly by another element. This is a third
10638 case: Changing the CE score always affects multiple identical CEs,
10639 so every affected CE must be checked, not only the single CE for
10640 which the CE score was changed in the first place (as every instance
10641 of that CE shares the same CE score, and therefore also can change)!
10643 SCAN_PLAYFIELD(xx, yy)
10645 if (Tile[xx][yy] == element)
10646 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10647 CE_SCORE_GETS_ZERO);
10655 case CA_SET_CE_ARTWORK:
10657 int artwork_element = action_arg_element;
10658 boolean reset_frame = FALSE;
10661 if (action_arg == CA_ARG_ELEMENT_RESET)
10662 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10665 if (ei->gfx_element != artwork_element)
10666 reset_frame = TRUE;
10668 ei->gfx_element = artwork_element;
10670 SCAN_PLAYFIELD(xx, yy)
10672 if (Tile[xx][yy] == element)
10676 ResetGfxAnimation(xx, yy);
10677 ResetRandomAnimationValue(xx, yy);
10680 TEST_DrawLevelField(xx, yy);
10687 // ---------- engine actions ---------------------------------------------
10689 case CA_SET_ENGINE_SCAN_MODE:
10691 InitPlayfieldScanMode(action_arg);
10701 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10703 int old_element = Tile[x][y];
10704 int new_element = GetElementFromGroupElement(element);
10705 int previous_move_direction = MovDir[x][y];
10706 int last_ce_value = CustomValue[x][y];
10707 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10708 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10709 boolean add_player_onto_element = (new_element_is_player &&
10710 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10711 IS_WALKABLE(old_element));
10713 if (!add_player_onto_element)
10715 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10716 RemoveMovingField(x, y);
10720 Tile[x][y] = new_element;
10722 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10723 MovDir[x][y] = previous_move_direction;
10725 if (element_info[new_element].use_last_ce_value)
10726 CustomValue[x][y] = last_ce_value;
10728 InitField_WithBug1(x, y, FALSE);
10730 new_element = Tile[x][y]; // element may have changed
10732 ResetGfxAnimation(x, y);
10733 ResetRandomAnimationValue(x, y);
10735 TEST_DrawLevelField(x, y);
10737 if (GFX_CRUMBLED(new_element))
10738 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10740 if (old_element == EL_EXPLOSION)
10742 Store[x][y] = Store2[x][y] = 0;
10744 // check if new element replaces an exploding player, requiring cleanup
10745 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
10746 StorePlayer[x][y] = 0;
10749 // check if element under the player changes from accessible to unaccessible
10750 // (needed for special case of dropping element which then changes)
10751 // (must be checked after creating new element for walkable group elements)
10752 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10753 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10755 KillPlayer(PLAYERINFO(x, y));
10761 // "ChangeCount" not set yet to allow "entered by player" change one time
10762 if (new_element_is_player)
10763 RelocatePlayer(x, y, new_element);
10766 ChangeCount[x][y]++; // count number of changes in the same frame
10768 TestIfBadThingTouchesPlayer(x, y);
10769 TestIfPlayerTouchesCustomElement(x, y);
10770 TestIfElementTouchesCustomElement(x, y);
10773 static void CreateField(int x, int y, int element)
10775 CreateFieldExt(x, y, element, FALSE);
10778 static void CreateElementFromChange(int x, int y, int element)
10780 element = GET_VALID_RUNTIME_ELEMENT(element);
10782 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10784 int old_element = Tile[x][y];
10786 // prevent changed element from moving in same engine frame
10787 // unless both old and new element can either fall or move
10788 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10789 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10793 CreateFieldExt(x, y, element, TRUE);
10796 static boolean ChangeElement(int x, int y, int element, int page)
10798 struct ElementInfo *ei = &element_info[element];
10799 struct ElementChangeInfo *change = &ei->change_page[page];
10800 int ce_value = CustomValue[x][y];
10801 int ce_score = ei->collect_score;
10802 int target_element;
10803 int old_element = Tile[x][y];
10805 // always use default change event to prevent running into a loop
10806 if (ChangeEvent[x][y] == -1)
10807 ChangeEvent[x][y] = CE_DELAY;
10809 if (ChangeEvent[x][y] == CE_DELAY)
10811 // reset actual trigger element, trigger player and action element
10812 change->actual_trigger_element = EL_EMPTY;
10813 change->actual_trigger_player = EL_EMPTY;
10814 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10815 change->actual_trigger_side = CH_SIDE_NONE;
10816 change->actual_trigger_ce_value = 0;
10817 change->actual_trigger_ce_score = 0;
10818 change->actual_trigger_x = -1;
10819 change->actual_trigger_y = -1;
10822 // do not change elements more than a specified maximum number of changes
10823 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10826 ChangeCount[x][y]++; // count number of changes in the same frame
10828 if (ei->has_anim_event)
10829 HandleGlobalAnimEventByElementChange(element, page, x, y,
10830 change->actual_trigger_x,
10831 change->actual_trigger_y);
10833 if (change->explode)
10840 if (change->use_target_content)
10842 boolean complete_replace = TRUE;
10843 boolean can_replace[3][3];
10846 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10849 boolean is_walkable;
10850 boolean is_diggable;
10851 boolean is_collectible;
10852 boolean is_removable;
10853 boolean is_destructible;
10854 int ex = x + xx - 1;
10855 int ey = y + yy - 1;
10856 int content_element = change->target_content.e[xx][yy];
10859 can_replace[xx][yy] = TRUE;
10861 if (ex == x && ey == y) // do not check changing element itself
10864 if (content_element == EL_EMPTY_SPACE)
10866 can_replace[xx][yy] = FALSE; // do not replace border with space
10871 if (!IN_LEV_FIELD(ex, ey))
10873 can_replace[xx][yy] = FALSE;
10874 complete_replace = FALSE;
10881 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10882 e = MovingOrBlocked2Element(ex, ey);
10884 is_empty = (IS_FREE(ex, ey) ||
10885 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10887 is_walkable = (is_empty || IS_WALKABLE(e));
10888 is_diggable = (is_empty || IS_DIGGABLE(e));
10889 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10890 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10891 is_removable = (is_diggable || is_collectible);
10893 can_replace[xx][yy] =
10894 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10895 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10896 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10897 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10898 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10899 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10900 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10902 if (!can_replace[xx][yy])
10903 complete_replace = FALSE;
10906 if (!change->only_if_complete || complete_replace)
10908 boolean something_has_changed = FALSE;
10910 if (change->only_if_complete && change->use_random_replace &&
10911 RND(100) < change->random_percentage)
10914 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10916 int ex = x + xx - 1;
10917 int ey = y + yy - 1;
10918 int content_element;
10920 if (can_replace[xx][yy] && (!change->use_random_replace ||
10921 RND(100) < change->random_percentage))
10923 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10924 RemoveMovingField(ex, ey);
10926 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10928 content_element = change->target_content.e[xx][yy];
10929 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10930 ce_value, ce_score);
10932 CreateElementFromChange(ex, ey, target_element);
10934 something_has_changed = TRUE;
10936 // for symmetry reasons, freeze newly created border elements
10937 if (ex != x || ey != y)
10938 Stop[ex][ey] = TRUE; // no more moving in this frame
10942 if (something_has_changed)
10944 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10945 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10951 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10952 ce_value, ce_score);
10954 if (element == EL_DIAGONAL_GROWING ||
10955 element == EL_DIAGONAL_SHRINKING)
10957 target_element = Store[x][y];
10959 Store[x][y] = EL_EMPTY;
10962 // special case: element changes to player (and may be kept if walkable)
10963 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10964 CreateElementFromChange(x, y, EL_EMPTY);
10966 CreateElementFromChange(x, y, target_element);
10968 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10969 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10972 // this uses direct change before indirect change
10973 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10978 static void HandleElementChange(int x, int y, int page)
10980 int element = MovingOrBlocked2Element(x, y);
10981 struct ElementInfo *ei = &element_info[element];
10982 struct ElementChangeInfo *change = &ei->change_page[page];
10983 boolean handle_action_before_change = FALSE;
10986 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10987 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10989 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10990 x, y, element, element_info[element].token_name);
10991 Debug("game:playing:HandleElementChange", "This should never happen!");
10995 // this can happen with classic bombs on walkable, changing elements
10996 if (!CAN_CHANGE_OR_HAS_ACTION(element))
11001 if (ChangeDelay[x][y] == 0) // initialize element change
11003 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
11005 if (change->can_change)
11007 // !!! not clear why graphic animation should be reset at all here !!!
11008 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
11009 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
11012 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
11014 When using an animation frame delay of 1 (this only happens with
11015 "sp_zonk.moving.left/right" in the classic graphics), the default
11016 (non-moving) animation shows wrong animation frames (while the
11017 moving animation, like "sp_zonk.moving.left/right", is correct,
11018 so this graphical bug never shows up with the classic graphics).
11019 For an animation with 4 frames, this causes wrong frames 0,0,1,2
11020 be drawn instead of the correct frames 0,1,2,3. This is caused by
11021 "GfxFrame[][]" being reset *twice* (in two successive frames) after
11022 an element change: First when the change delay ("ChangeDelay[][]")
11023 counter has reached zero after decrementing, then a second time in
11024 the next frame (after "GfxFrame[][]" was already incremented) when
11025 "ChangeDelay[][]" is reset to the initial delay value again.
11027 This causes frame 0 to be drawn twice, while the last frame won't
11028 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
11030 As some animations may already be cleverly designed around this bug
11031 (at least the "Snake Bite" snake tail animation does this), it cannot
11032 simply be fixed here without breaking such existing animations.
11033 Unfortunately, it cannot easily be detected if a graphics set was
11034 designed "before" or "after" the bug was fixed. As a workaround,
11035 a new graphics set option "game.graphics_engine_version" was added
11036 to be able to specify the game's major release version for which the
11037 graphics set was designed, which can then be used to decide if the
11038 bugfix should be used (version 4 and above) or not (version 3 or
11039 below, or if no version was specified at all, as with old sets).
11041 (The wrong/fixed animation frames can be tested with the test level set
11042 "test_gfxframe" and level "000", which contains a specially prepared
11043 custom element at level position (x/y) == (11/9) which uses the zonk
11044 animation mentioned above. Using "game.graphics_engine_version: 4"
11045 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
11046 This can also be seen from the debug output for this test element.)
11049 // when a custom element is about to change (for example by change delay),
11050 // do not reset graphic animation when the custom element is moving
11051 if (game.graphics_engine_version < 4 &&
11054 ResetGfxAnimation(x, y);
11055 ResetRandomAnimationValue(x, y);
11058 if (change->pre_change_function)
11059 change->pre_change_function(x, y);
11063 ChangeDelay[x][y]--;
11065 if (ChangeDelay[x][y] != 0) // continue element change
11067 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11069 // also needed if CE can not change, but has CE delay with CE action
11070 if (IS_ANIMATED(graphic))
11071 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11073 if (change->can_change)
11075 if (change->change_function)
11076 change->change_function(x, y);
11079 else // finish element change
11081 if (ChangePage[x][y] != -1) // remember page from delayed change
11083 page = ChangePage[x][y];
11084 ChangePage[x][y] = -1;
11086 change = &ei->change_page[page];
11089 if (IS_MOVING(x, y)) // never change a running system ;-)
11091 ChangeDelay[x][y] = 1; // try change after next move step
11092 ChangePage[x][y] = page; // remember page to use for change
11097 // special case: set new level random seed before changing element
11098 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11099 handle_action_before_change = TRUE;
11101 if (change->has_action && handle_action_before_change)
11102 ExecuteCustomElementAction(x, y, element, page);
11104 if (change->can_change)
11106 if (ChangeElement(x, y, element, page))
11108 if (change->post_change_function)
11109 change->post_change_function(x, y);
11113 if (change->has_action && !handle_action_before_change)
11114 ExecuteCustomElementAction(x, y, element, page);
11118 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11119 int trigger_element,
11121 int trigger_player,
11125 boolean change_done_any = FALSE;
11126 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11129 if (!(trigger_events[trigger_element][trigger_event]))
11132 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11134 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11136 int element = EL_CUSTOM_START + i;
11137 boolean change_done = FALSE;
11140 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11141 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11144 for (p = 0; p < element_info[element].num_change_pages; p++)
11146 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11148 if (change->can_change_or_has_action &&
11149 change->has_event[trigger_event] &&
11150 change->trigger_side & trigger_side &&
11151 change->trigger_player & trigger_player &&
11152 change->trigger_page & trigger_page_bits &&
11153 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11155 change->actual_trigger_element = trigger_element;
11156 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11157 change->actual_trigger_player_bits = trigger_player;
11158 change->actual_trigger_side = trigger_side;
11159 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11160 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11161 change->actual_trigger_x = trigger_x;
11162 change->actual_trigger_y = trigger_y;
11164 if ((change->can_change && !change_done) || change->has_action)
11168 SCAN_PLAYFIELD(x, y)
11170 if (Tile[x][y] == element)
11172 if (change->can_change && !change_done)
11174 // if element already changed in this frame, not only prevent
11175 // another element change (checked in ChangeElement()), but
11176 // also prevent additional element actions for this element
11178 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11179 !level.use_action_after_change_bug)
11182 ChangeDelay[x][y] = 1;
11183 ChangeEvent[x][y] = trigger_event;
11185 HandleElementChange(x, y, p);
11187 else if (change->has_action)
11189 // if element already changed in this frame, not only prevent
11190 // another element change (checked in ChangeElement()), but
11191 // also prevent additional element actions for this element
11193 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11194 !level.use_action_after_change_bug)
11197 ExecuteCustomElementAction(x, y, element, p);
11198 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11203 if (change->can_change)
11205 change_done = TRUE;
11206 change_done_any = TRUE;
11213 RECURSION_LOOP_DETECTION_END();
11215 return change_done_any;
11218 static boolean CheckElementChangeExt(int x, int y,
11220 int trigger_element,
11222 int trigger_player,
11225 boolean change_done = FALSE;
11228 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11229 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11232 if (Tile[x][y] == EL_BLOCKED)
11234 Blocked2Moving(x, y, &x, &y);
11235 element = Tile[x][y];
11238 // check if element has already changed or is about to change after moving
11239 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11240 Tile[x][y] != element) ||
11242 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11243 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11244 ChangePage[x][y] != -1)))
11247 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11249 for (p = 0; p < element_info[element].num_change_pages; p++)
11251 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11253 /* check trigger element for all events where the element that is checked
11254 for changing interacts with a directly adjacent element -- this is
11255 different to element changes that affect other elements to change on the
11256 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11257 boolean check_trigger_element =
11258 (trigger_event == CE_NEXT_TO_X ||
11259 trigger_event == CE_TOUCHING_X ||
11260 trigger_event == CE_HITTING_X ||
11261 trigger_event == CE_HIT_BY_X ||
11262 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11264 if (change->can_change_or_has_action &&
11265 change->has_event[trigger_event] &&
11266 change->trigger_side & trigger_side &&
11267 change->trigger_player & trigger_player &&
11268 (!check_trigger_element ||
11269 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11271 change->actual_trigger_element = trigger_element;
11272 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11273 change->actual_trigger_player_bits = trigger_player;
11274 change->actual_trigger_side = trigger_side;
11275 change->actual_trigger_ce_value = CustomValue[x][y];
11276 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11277 change->actual_trigger_x = x;
11278 change->actual_trigger_y = y;
11280 // special case: trigger element not at (x,y) position for some events
11281 if (check_trigger_element)
11293 { 0, 0 }, { 0, 0 }, { 0, 0 },
11297 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11298 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11300 change->actual_trigger_ce_value = CustomValue[xx][yy];
11301 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11302 change->actual_trigger_x = xx;
11303 change->actual_trigger_y = yy;
11306 if (change->can_change && !change_done)
11308 ChangeDelay[x][y] = 1;
11309 ChangeEvent[x][y] = trigger_event;
11311 HandleElementChange(x, y, p);
11313 change_done = TRUE;
11315 else if (change->has_action)
11317 ExecuteCustomElementAction(x, y, element, p);
11318 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11323 RECURSION_LOOP_DETECTION_END();
11325 return change_done;
11328 static void PlayPlayerSound(struct PlayerInfo *player)
11330 int jx = player->jx, jy = player->jy;
11331 int sound_element = player->artwork_element;
11332 int last_action = player->last_action_waiting;
11333 int action = player->action_waiting;
11335 if (player->is_waiting)
11337 if (action != last_action)
11338 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11340 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11344 if (action != last_action)
11345 StopSound(element_info[sound_element].sound[last_action]);
11347 if (last_action == ACTION_SLEEPING)
11348 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11352 static void PlayAllPlayersSound(void)
11356 for (i = 0; i < MAX_PLAYERS; i++)
11357 if (stored_player[i].active)
11358 PlayPlayerSound(&stored_player[i]);
11361 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11363 boolean last_waiting = player->is_waiting;
11364 int move_dir = player->MovDir;
11366 player->dir_waiting = move_dir;
11367 player->last_action_waiting = player->action_waiting;
11371 if (!last_waiting) // not waiting -> waiting
11373 player->is_waiting = TRUE;
11375 player->frame_counter_bored =
11377 game.player_boring_delay_fixed +
11378 GetSimpleRandom(game.player_boring_delay_random);
11379 player->frame_counter_sleeping =
11381 game.player_sleeping_delay_fixed +
11382 GetSimpleRandom(game.player_sleeping_delay_random);
11384 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11387 if (game.player_sleeping_delay_fixed +
11388 game.player_sleeping_delay_random > 0 &&
11389 player->anim_delay_counter == 0 &&
11390 player->post_delay_counter == 0 &&
11391 FrameCounter >= player->frame_counter_sleeping)
11392 player->is_sleeping = TRUE;
11393 else if (game.player_boring_delay_fixed +
11394 game.player_boring_delay_random > 0 &&
11395 FrameCounter >= player->frame_counter_bored)
11396 player->is_bored = TRUE;
11398 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11399 player->is_bored ? ACTION_BORING :
11402 if (player->is_sleeping && player->use_murphy)
11404 // special case for sleeping Murphy when leaning against non-free tile
11406 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11407 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11408 !IS_MOVING(player->jx - 1, player->jy)))
11409 move_dir = MV_LEFT;
11410 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11411 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11412 !IS_MOVING(player->jx + 1, player->jy)))
11413 move_dir = MV_RIGHT;
11415 player->is_sleeping = FALSE;
11417 player->dir_waiting = move_dir;
11420 if (player->is_sleeping)
11422 if (player->num_special_action_sleeping > 0)
11424 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11426 int last_special_action = player->special_action_sleeping;
11427 int num_special_action = player->num_special_action_sleeping;
11428 int special_action =
11429 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11430 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11431 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11432 last_special_action + 1 : ACTION_SLEEPING);
11433 int special_graphic =
11434 el_act_dir2img(player->artwork_element, special_action, move_dir);
11436 player->anim_delay_counter =
11437 graphic_info[special_graphic].anim_delay_fixed +
11438 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11439 player->post_delay_counter =
11440 graphic_info[special_graphic].post_delay_fixed +
11441 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11443 player->special_action_sleeping = special_action;
11446 if (player->anim_delay_counter > 0)
11448 player->action_waiting = player->special_action_sleeping;
11449 player->anim_delay_counter--;
11451 else if (player->post_delay_counter > 0)
11453 player->post_delay_counter--;
11457 else if (player->is_bored)
11459 if (player->num_special_action_bored > 0)
11461 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11463 int special_action =
11464 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11465 int special_graphic =
11466 el_act_dir2img(player->artwork_element, special_action, move_dir);
11468 player->anim_delay_counter =
11469 graphic_info[special_graphic].anim_delay_fixed +
11470 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11471 player->post_delay_counter =
11472 graphic_info[special_graphic].post_delay_fixed +
11473 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11475 player->special_action_bored = special_action;
11478 if (player->anim_delay_counter > 0)
11480 player->action_waiting = player->special_action_bored;
11481 player->anim_delay_counter--;
11483 else if (player->post_delay_counter > 0)
11485 player->post_delay_counter--;
11490 else if (last_waiting) // waiting -> not waiting
11492 player->is_waiting = FALSE;
11493 player->is_bored = FALSE;
11494 player->is_sleeping = FALSE;
11496 player->frame_counter_bored = -1;
11497 player->frame_counter_sleeping = -1;
11499 player->anim_delay_counter = 0;
11500 player->post_delay_counter = 0;
11502 player->dir_waiting = player->MovDir;
11503 player->action_waiting = ACTION_DEFAULT;
11505 player->special_action_bored = ACTION_DEFAULT;
11506 player->special_action_sleeping = ACTION_DEFAULT;
11510 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11512 if ((!player->is_moving && player->was_moving) ||
11513 (player->MovPos == 0 && player->was_moving) ||
11514 (player->is_snapping && !player->was_snapping) ||
11515 (player->is_dropping && !player->was_dropping))
11517 if (!CheckSaveEngineSnapshotToList())
11520 player->was_moving = FALSE;
11521 player->was_snapping = TRUE;
11522 player->was_dropping = TRUE;
11526 if (player->is_moving)
11527 player->was_moving = TRUE;
11529 if (!player->is_snapping)
11530 player->was_snapping = FALSE;
11532 if (!player->is_dropping)
11533 player->was_dropping = FALSE;
11536 static struct MouseActionInfo mouse_action_last = { 0 };
11537 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11538 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11541 CheckSaveEngineSnapshotToList();
11543 mouse_action_last = mouse_action;
11546 static void CheckSingleStepMode(struct PlayerInfo *player)
11548 if (tape.single_step && tape.recording && !tape.pausing)
11550 // as it is called "single step mode", just return to pause mode when the
11551 // player stopped moving after one tile (or never starts moving at all)
11552 // (reverse logic needed here in case single step mode used in team mode)
11553 if (player->is_moving ||
11554 player->is_pushing ||
11555 player->is_dropping_pressed ||
11556 player->effective_mouse_action.button)
11557 game.enter_single_step_mode = FALSE;
11560 CheckSaveEngineSnapshot(player);
11563 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11565 int left = player_action & JOY_LEFT;
11566 int right = player_action & JOY_RIGHT;
11567 int up = player_action & JOY_UP;
11568 int down = player_action & JOY_DOWN;
11569 int button1 = player_action & JOY_BUTTON_1;
11570 int button2 = player_action & JOY_BUTTON_2;
11571 int dx = (left ? -1 : right ? 1 : 0);
11572 int dy = (up ? -1 : down ? 1 : 0);
11574 if (!player->active || tape.pausing)
11580 SnapField(player, dx, dy);
11584 DropElement(player);
11586 MovePlayer(player, dx, dy);
11589 CheckSingleStepMode(player);
11591 SetPlayerWaiting(player, FALSE);
11593 return player_action;
11597 // no actions for this player (no input at player's configured device)
11599 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11600 SnapField(player, 0, 0);
11601 CheckGravityMovementWhenNotMoving(player);
11603 if (player->MovPos == 0)
11604 SetPlayerWaiting(player, TRUE);
11606 if (player->MovPos == 0) // needed for tape.playing
11607 player->is_moving = FALSE;
11609 player->is_dropping = FALSE;
11610 player->is_dropping_pressed = FALSE;
11611 player->drop_pressed_delay = 0;
11613 CheckSingleStepMode(player);
11619 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11622 if (!tape.use_mouse_actions)
11625 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11626 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11627 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11630 static void SetTapeActionFromMouseAction(byte *tape_action,
11631 struct MouseActionInfo *mouse_action)
11633 if (!tape.use_mouse_actions)
11636 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11637 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11638 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11641 static void CheckLevelSolved(void)
11643 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11645 if (game_bd.level_solved &&
11646 !game_bd.game_over) // game won
11650 game_bd.game_over = TRUE;
11652 game.all_players_gone = TRUE;
11655 if (game_bd.game_over) // game lost
11656 game.all_players_gone = TRUE;
11658 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11660 if (game_em.level_solved &&
11661 !game_em.game_over) // game won
11665 game_em.game_over = TRUE;
11667 game.all_players_gone = TRUE;
11670 if (game_em.game_over) // game lost
11671 game.all_players_gone = TRUE;
11673 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11675 if (game_sp.level_solved &&
11676 !game_sp.game_over) // game won
11680 game_sp.game_over = TRUE;
11682 game.all_players_gone = TRUE;
11685 if (game_sp.game_over) // game lost
11686 game.all_players_gone = TRUE;
11688 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11690 if (game_mm.level_solved &&
11691 !game_mm.game_over) // game won
11695 game_mm.game_over = TRUE;
11697 game.all_players_gone = TRUE;
11700 if (game_mm.game_over) // game lost
11701 game.all_players_gone = TRUE;
11705 static void PlayTimeoutSound(int seconds_left)
11707 // try to use individual "running out of time" sound for each second left
11708 int sound = SND_GAME_RUNNING_OUT_OF_TIME_0 - seconds_left;
11710 // if special sound per second not defined, use default sound
11711 if (getSoundInfoEntryFilename(sound) == NULL)
11712 sound = SND_GAME_RUNNING_OUT_OF_TIME;
11714 // if out of time, but player still alive, play special "timeout" sound, if defined
11715 if (seconds_left == 0 && !checkGameFailed())
11716 if (getSoundInfoEntryFilename(SND_GAME_TIMEOUT) != NULL)
11717 sound = SND_GAME_TIMEOUT;
11722 static void CheckLevelTime_StepCounter(void)
11732 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11733 PlayTimeoutSound(TimeLeft);
11735 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11737 DisplayGameControlValues();
11739 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11740 for (i = 0; i < MAX_PLAYERS; i++)
11741 KillPlayer(&stored_player[i]);
11743 else if (game.no_level_time_limit && !game.all_players_gone)
11745 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11747 DisplayGameControlValues();
11751 static void CheckLevelTime(void)
11755 if (TimeFrames >= FRAMES_PER_SECOND)
11759 for (i = 0; i < MAX_PLAYERS; i++)
11761 struct PlayerInfo *player = &stored_player[i];
11763 if (SHIELD_ON(player))
11765 player->shield_normal_time_left--;
11767 if (player->shield_deadly_time_left > 0)
11768 player->shield_deadly_time_left--;
11772 if (!game.LevelSolved && !level.use_step_counter)
11780 if (TimeLeft <= 10 && game.time_limit)
11781 PlayTimeoutSound(TimeLeft);
11783 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11784 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11786 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11788 if (!TimeLeft && game.time_limit)
11790 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11791 game_em.lev->killed_out_of_time = TRUE;
11793 for (i = 0; i < MAX_PLAYERS; i++)
11794 KillPlayer(&stored_player[i]);
11797 else if (game.no_level_time_limit && !game.all_players_gone)
11799 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11802 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11806 if (TapeTimeFrames >= FRAMES_PER_SECOND)
11808 TapeTimeFrames = 0;
11811 if (tape.recording || tape.playing)
11812 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11815 if (tape.recording || tape.playing)
11816 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11818 UpdateAndDisplayGameControlValues();
11821 void AdvanceFrameAndPlayerCounters(int player_nr)
11825 // advance frame counters (global frame counter and tape time frame counter)
11829 // advance time frame counter (used to control available time to solve level)
11832 // advance player counters (counters for move delay, move animation etc.)
11833 for (i = 0; i < MAX_PLAYERS; i++)
11835 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11836 int move_delay_value = stored_player[i].move_delay_value;
11837 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11839 if (!advance_player_counters) // not all players may be affected
11842 if (move_frames == 0) // less than one move per game frame
11844 int stepsize = TILEX / move_delay_value;
11845 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11846 int count = (stored_player[i].is_moving ?
11847 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11849 if (count % delay == 0)
11853 stored_player[i].Frame += move_frames;
11855 if (stored_player[i].MovPos != 0)
11856 stored_player[i].StepFrame += move_frames;
11858 if (stored_player[i].move_delay > 0)
11859 stored_player[i].move_delay--;
11861 // due to bugs in previous versions, counter must count up, not down
11862 if (stored_player[i].push_delay != -1)
11863 stored_player[i].push_delay++;
11865 if (stored_player[i].drop_delay > 0)
11866 stored_player[i].drop_delay--;
11868 if (stored_player[i].is_dropping_pressed)
11869 stored_player[i].drop_pressed_delay++;
11873 void AdvanceFrameCounter(void)
11878 void AdvanceGfxFrame(void)
11882 SCAN_PLAYFIELD(x, y)
11888 static void HandleMouseAction(struct MouseActionInfo *mouse_action,
11889 struct MouseActionInfo *mouse_action_last)
11891 if (mouse_action->button)
11893 int new_button = (mouse_action->button && mouse_action_last->button == 0);
11894 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button);
11895 int x = mouse_action->lx;
11896 int y = mouse_action->ly;
11897 int element = Tile[x][y];
11901 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
11902 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
11906 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
11907 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
11910 if (level.use_step_counter)
11912 boolean counted_click = FALSE;
11914 // element clicked that can change when clicked/pressed
11915 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
11916 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
11917 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
11918 counted_click = TRUE;
11920 // element clicked that can trigger change when clicked/pressed
11921 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
11922 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
11923 counted_click = TRUE;
11925 if (new_button && counted_click)
11926 CheckLevelTime_StepCounter();
11931 void StartGameActions(boolean init_network_game, boolean record_tape,
11934 unsigned int new_random_seed = InitRND(random_seed);
11937 TapeStartRecording(new_random_seed);
11939 if (setup.auto_pause_on_start && !tape.pausing)
11940 TapeTogglePause(TAPE_TOGGLE_MANUAL);
11942 if (init_network_game)
11944 SendToServer_LevelFile();
11945 SendToServer_StartPlaying();
11953 static void GameActionsExt(void)
11956 static unsigned int game_frame_delay = 0;
11958 unsigned int game_frame_delay_value;
11959 byte *recorded_player_action;
11960 byte summarized_player_action = 0;
11961 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11964 // detect endless loops, caused by custom element programming
11965 if (recursion_loop_detected && recursion_loop_depth == 0)
11967 char *message = getStringCat3("Internal Error! Element ",
11968 EL_NAME(recursion_loop_element),
11969 " caused endless loop! Quit the game?");
11971 Warn("element '%s' caused endless loop in game engine",
11972 EL_NAME(recursion_loop_element));
11974 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11976 recursion_loop_detected = FALSE; // if game should be continued
11983 if (game.restart_level)
11984 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11986 CheckLevelSolved();
11988 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11991 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11994 if (game_status != GAME_MODE_PLAYING) // status might have changed
11997 game_frame_delay_value =
11998 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
12000 if (tape.playing && tape.warp_forward && !tape.pausing)
12001 game_frame_delay_value = 0;
12003 SetVideoFrameDelay(game_frame_delay_value);
12005 // (de)activate virtual buttons depending on current game status
12006 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
12008 if (game.all_players_gone) // if no players there to be controlled anymore
12009 SetOverlayActive(FALSE);
12010 else if (!tape.playing) // if game continues after tape stopped playing
12011 SetOverlayActive(TRUE);
12016 // ---------- main game synchronization point ----------
12018 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12020 Debug("game:playing:skip", "skip == %d", skip);
12023 // ---------- main game synchronization point ----------
12025 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12029 if (network_playing && !network_player_action_received)
12031 // try to get network player actions in time
12033 // last chance to get network player actions without main loop delay
12034 HandleNetworking();
12036 // game was quit by network peer
12037 if (game_status != GAME_MODE_PLAYING)
12040 // check if network player actions still missing and game still running
12041 if (!network_player_action_received && !checkGameEnded())
12042 return; // failed to get network player actions in time
12044 // do not yet reset "network_player_action_received" (for tape.pausing)
12050 // at this point we know that we really continue executing the game
12052 network_player_action_received = FALSE;
12054 // when playing tape, read previously recorded player input from tape data
12055 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
12057 local_player->effective_mouse_action = local_player->mouse_action;
12059 if (recorded_player_action != NULL)
12060 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
12061 recorded_player_action);
12063 // TapePlayAction() may return NULL when toggling to "pause before death"
12067 if (tape.set_centered_player)
12069 game.centered_player_nr_next = tape.centered_player_nr_next;
12070 game.set_centered_player = TRUE;
12073 for (i = 0; i < MAX_PLAYERS; i++)
12075 summarized_player_action |= stored_player[i].action;
12077 if (!network_playing && (game.team_mode || tape.playing))
12078 stored_player[i].effective_action = stored_player[i].action;
12081 if (network_playing && !checkGameEnded())
12082 SendToServer_MovePlayer(summarized_player_action);
12084 // summarize all actions at local players mapped input device position
12085 // (this allows using different input devices in single player mode)
12086 if (!network.enabled && !game.team_mode)
12087 stored_player[map_player_action[local_player->index_nr]].effective_action =
12088 summarized_player_action;
12090 // summarize all actions at centered player in local team mode
12091 if (tape.recording &&
12092 setup.team_mode && !network.enabled &&
12093 setup.input_on_focus &&
12094 game.centered_player_nr != -1)
12096 for (i = 0; i < MAX_PLAYERS; i++)
12097 stored_player[map_player_action[i]].effective_action =
12098 (i == game.centered_player_nr ? summarized_player_action : 0);
12101 if (recorded_player_action != NULL)
12102 for (i = 0; i < MAX_PLAYERS; i++)
12103 stored_player[i].effective_action = recorded_player_action[i];
12105 for (i = 0; i < MAX_PLAYERS; i++)
12107 tape_action[i] = stored_player[i].effective_action;
12109 /* (this may happen in the RND game engine if a player was not present on
12110 the playfield on level start, but appeared later from a custom element */
12111 if (setup.team_mode &&
12114 !tape.player_participates[i])
12115 tape.player_participates[i] = TRUE;
12118 SetTapeActionFromMouseAction(tape_action,
12119 &local_player->effective_mouse_action);
12121 // only record actions from input devices, but not programmed actions
12122 if (tape.recording)
12123 TapeRecordAction(tape_action);
12125 // remember if game was played (especially after tape stopped playing)
12126 if (!tape.playing && summarized_player_action && !checkGameFailed())
12127 game.GamePlayed = TRUE;
12129 #if USE_NEW_PLAYER_ASSIGNMENTS
12130 // !!! also map player actions in single player mode !!!
12131 // if (game.team_mode)
12134 byte mapped_action[MAX_PLAYERS];
12136 #if DEBUG_PLAYER_ACTIONS
12137 for (i = 0; i < MAX_PLAYERS; i++)
12138 DebugContinued("", "%d, ", stored_player[i].effective_action);
12141 for (i = 0; i < MAX_PLAYERS; i++)
12142 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
12144 for (i = 0; i < MAX_PLAYERS; i++)
12145 stored_player[i].effective_action = mapped_action[i];
12147 #if DEBUG_PLAYER_ACTIONS
12148 DebugContinued("", "=> ");
12149 for (i = 0; i < MAX_PLAYERS; i++)
12150 DebugContinued("", "%d, ", stored_player[i].effective_action);
12151 DebugContinued("game:playing:player", "\n");
12154 #if DEBUG_PLAYER_ACTIONS
12157 for (i = 0; i < MAX_PLAYERS; i++)
12158 DebugContinued("", "%d, ", stored_player[i].effective_action);
12159 DebugContinued("game:playing:player", "\n");
12164 for (i = 0; i < MAX_PLAYERS; i++)
12166 // allow engine snapshot in case of changed movement attempt
12167 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12168 (stored_player[i].effective_action & KEY_MOTION))
12169 game.snapshot.changed_action = TRUE;
12171 // allow engine snapshot in case of snapping/dropping attempt
12172 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12173 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12174 game.snapshot.changed_action = TRUE;
12176 game.snapshot.last_action[i] = stored_player[i].effective_action;
12179 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12181 GameActions_EM_Main();
12183 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12185 GameActions_SP_Main();
12187 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12189 GameActions_MM_Main();
12193 GameActions_RND_Main();
12196 BlitScreenToBitmap(backbuffer);
12198 CheckLevelSolved();
12201 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12203 if (global.show_frames_per_second)
12205 static unsigned int fps_counter = 0;
12206 static int fps_frames = 0;
12207 unsigned int fps_delay_ms = Counter() - fps_counter;
12211 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12213 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12216 fps_counter = Counter();
12218 // always draw FPS to screen after FPS value was updated
12219 redraw_mask |= REDRAW_FPS;
12222 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12223 if (GetDrawDeactivationMask() == REDRAW_NONE)
12224 redraw_mask |= REDRAW_FPS;
12228 static void GameActions_CheckSaveEngineSnapshot(void)
12230 if (!game.snapshot.save_snapshot)
12233 // clear flag for saving snapshot _before_ saving snapshot
12234 game.snapshot.save_snapshot = FALSE;
12236 SaveEngineSnapshotToList();
12239 void GameActions(void)
12243 GameActions_CheckSaveEngineSnapshot();
12246 void GameActions_EM_Main(void)
12248 byte effective_action[MAX_PLAYERS];
12251 for (i = 0; i < MAX_PLAYERS; i++)
12252 effective_action[i] = stored_player[i].effective_action;
12254 GameActions_EM(effective_action);
12257 void GameActions_SP_Main(void)
12259 byte effective_action[MAX_PLAYERS];
12262 for (i = 0; i < MAX_PLAYERS; i++)
12263 effective_action[i] = stored_player[i].effective_action;
12265 GameActions_SP(effective_action);
12267 for (i = 0; i < MAX_PLAYERS; i++)
12269 if (stored_player[i].force_dropping)
12270 stored_player[i].action |= KEY_BUTTON_DROP;
12272 stored_player[i].force_dropping = FALSE;
12276 void GameActions_MM_Main(void)
12280 GameActions_MM(local_player->effective_mouse_action);
12283 void GameActions_RND_Main(void)
12288 void GameActions_RND(void)
12290 static struct MouseActionInfo mouse_action_last = { 0 };
12291 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12292 int magic_wall_x = 0, magic_wall_y = 0;
12293 int i, x, y, element, graphic, last_gfx_frame;
12295 InitPlayfieldScanModeVars();
12297 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12299 SCAN_PLAYFIELD(x, y)
12301 ChangeCount[x][y] = 0;
12302 ChangeEvent[x][y] = -1;
12306 if (game.set_centered_player)
12308 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12310 // switching to "all players" only possible if all players fit to screen
12311 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12313 game.centered_player_nr_next = game.centered_player_nr;
12314 game.set_centered_player = FALSE;
12317 // do not switch focus to non-existing (or non-active) player
12318 if (game.centered_player_nr_next >= 0 &&
12319 !stored_player[game.centered_player_nr_next].active)
12321 game.centered_player_nr_next = game.centered_player_nr;
12322 game.set_centered_player = FALSE;
12326 if (game.set_centered_player &&
12327 ScreenMovPos == 0) // screen currently aligned at tile position
12331 if (game.centered_player_nr_next == -1)
12333 setScreenCenteredToAllPlayers(&sx, &sy);
12337 sx = stored_player[game.centered_player_nr_next].jx;
12338 sy = stored_player[game.centered_player_nr_next].jy;
12341 game.centered_player_nr = game.centered_player_nr_next;
12342 game.set_centered_player = FALSE;
12344 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12345 DrawGameDoorValues();
12348 // check single step mode (set flag and clear again if any player is active)
12349 game.enter_single_step_mode =
12350 (tape.single_step && tape.recording && !tape.pausing);
12352 for (i = 0; i < MAX_PLAYERS; i++)
12354 int actual_player_action = stored_player[i].effective_action;
12357 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12358 - rnd_equinox_tetrachloride 048
12359 - rnd_equinox_tetrachloride_ii 096
12360 - rnd_emanuel_schmieg 002
12361 - doctor_sloan_ww 001, 020
12363 if (stored_player[i].MovPos == 0)
12364 CheckGravityMovement(&stored_player[i]);
12367 // overwrite programmed action with tape action
12368 if (stored_player[i].programmed_action)
12369 actual_player_action = stored_player[i].programmed_action;
12371 PlayerActions(&stored_player[i], actual_player_action);
12373 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12376 // single step pause mode may already have been toggled by "ScrollPlayer()"
12377 if (game.enter_single_step_mode && !tape.pausing)
12378 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12380 ScrollScreen(NULL, SCROLL_GO_ON);
12382 /* for backwards compatibility, the following code emulates a fixed bug that
12383 occured when pushing elements (causing elements that just made their last
12384 pushing step to already (if possible) make their first falling step in the
12385 same game frame, which is bad); this code is also needed to use the famous
12386 "spring push bug" which is used in older levels and might be wanted to be
12387 used also in newer levels, but in this case the buggy pushing code is only
12388 affecting the "spring" element and no other elements */
12390 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12392 for (i = 0; i < MAX_PLAYERS; i++)
12394 struct PlayerInfo *player = &stored_player[i];
12395 int x = player->jx;
12396 int y = player->jy;
12398 if (player->active && player->is_pushing && player->is_moving &&
12400 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12401 Tile[x][y] == EL_SPRING))
12403 ContinueMoving(x, y);
12405 // continue moving after pushing (this is actually a bug)
12406 if (!IS_MOVING(x, y))
12407 Stop[x][y] = FALSE;
12412 SCAN_PLAYFIELD(x, y)
12414 Last[x][y] = Tile[x][y];
12416 ChangeCount[x][y] = 0;
12417 ChangeEvent[x][y] = -1;
12419 // this must be handled before main playfield loop
12420 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12423 if (MovDelay[x][y] <= 0)
12427 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12430 if (MovDelay[x][y] <= 0)
12432 int element = Store[x][y];
12433 int move_direction = MovDir[x][y];
12434 int player_index_bit = Store2[x][y];
12440 TEST_DrawLevelField(x, y);
12442 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12444 if (IS_ENVELOPE(element))
12445 local_player->show_envelope = element;
12450 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12452 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12454 Debug("game:playing:GameActions_RND", "This should never happen!");
12456 ChangePage[x][y] = -1;
12460 Stop[x][y] = FALSE;
12461 if (WasJustMoving[x][y] > 0)
12462 WasJustMoving[x][y]--;
12463 if (WasJustFalling[x][y] > 0)
12464 WasJustFalling[x][y]--;
12465 if (CheckCollision[x][y] > 0)
12466 CheckCollision[x][y]--;
12467 if (CheckImpact[x][y] > 0)
12468 CheckImpact[x][y]--;
12472 /* reset finished pushing action (not done in ContinueMoving() to allow
12473 continuous pushing animation for elements with zero push delay) */
12474 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12476 ResetGfxAnimation(x, y);
12477 TEST_DrawLevelField(x, y);
12481 if (IS_BLOCKED(x, y))
12485 Blocked2Moving(x, y, &oldx, &oldy);
12486 if (!IS_MOVING(oldx, oldy))
12488 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12489 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12490 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12491 Debug("game:playing:GameActions_RND", "This should never happen!");
12497 HandleMouseAction(&mouse_action, &mouse_action_last);
12499 SCAN_PLAYFIELD(x, y)
12501 element = Tile[x][y];
12502 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12503 last_gfx_frame = GfxFrame[x][y];
12505 if (element == EL_EMPTY)
12506 graphic = el2img(GfxElementEmpty[x][y]);
12508 ResetGfxFrame(x, y);
12510 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12511 DrawLevelGraphicAnimation(x, y, graphic);
12513 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12514 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12515 ResetRandomAnimationValue(x, y);
12517 SetRandomAnimationValue(x, y);
12519 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12521 if (IS_INACTIVE(element))
12523 if (IS_ANIMATED(graphic))
12524 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12529 // this may take place after moving, so 'element' may have changed
12530 if (IS_CHANGING(x, y) &&
12531 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12533 int page = element_info[element].event_page_nr[CE_DELAY];
12535 HandleElementChange(x, y, page);
12537 element = Tile[x][y];
12538 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12541 CheckNextToConditions(x, y);
12543 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12547 element = Tile[x][y];
12548 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12550 if (IS_ANIMATED(graphic) &&
12551 !IS_MOVING(x, y) &&
12553 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12555 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12556 TEST_DrawTwinkleOnField(x, y);
12558 else if (element == EL_ACID)
12561 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12563 else if ((element == EL_EXIT_OPEN ||
12564 element == EL_EM_EXIT_OPEN ||
12565 element == EL_SP_EXIT_OPEN ||
12566 element == EL_STEEL_EXIT_OPEN ||
12567 element == EL_EM_STEEL_EXIT_OPEN ||
12568 element == EL_SP_TERMINAL ||
12569 element == EL_SP_TERMINAL_ACTIVE ||
12570 element == EL_EXTRA_TIME ||
12571 element == EL_SHIELD_NORMAL ||
12572 element == EL_SHIELD_DEADLY) &&
12573 IS_ANIMATED(graphic))
12574 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12575 else if (IS_MOVING(x, y))
12576 ContinueMoving(x, y);
12577 else if (IS_ACTIVE_BOMB(element))
12578 CheckDynamite(x, y);
12579 else if (element == EL_AMOEBA_GROWING)
12580 AmoebaGrowing(x, y);
12581 else if (element == EL_AMOEBA_SHRINKING)
12582 AmoebaShrinking(x, y);
12584 #if !USE_NEW_AMOEBA_CODE
12585 else if (IS_AMOEBALIVE(element))
12586 AmoebaReproduce(x, y);
12589 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12591 else if (element == EL_EXIT_CLOSED)
12593 else if (element == EL_EM_EXIT_CLOSED)
12595 else if (element == EL_STEEL_EXIT_CLOSED)
12596 CheckExitSteel(x, y);
12597 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12598 CheckExitSteelEM(x, y);
12599 else if (element == EL_SP_EXIT_CLOSED)
12601 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12602 element == EL_EXPANDABLE_STEELWALL_GROWING)
12604 else if (element == EL_EXPANDABLE_WALL ||
12605 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12606 element == EL_EXPANDABLE_WALL_VERTICAL ||
12607 element == EL_EXPANDABLE_WALL_ANY ||
12608 element == EL_BD_EXPANDABLE_WALL ||
12609 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12610 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12611 element == EL_EXPANDABLE_STEELWALL_ANY)
12612 CheckWallGrowing(x, y);
12613 else if (element == EL_FLAMES)
12614 CheckForDragon(x, y);
12615 else if (element == EL_EXPLOSION)
12616 ; // drawing of correct explosion animation is handled separately
12617 else if (element == EL_ELEMENT_SNAPPING ||
12618 element == EL_DIAGONAL_SHRINKING ||
12619 element == EL_DIAGONAL_GROWING)
12621 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]);
12623 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12625 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12626 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12628 if (IS_BELT_ACTIVE(element))
12629 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12631 if (game.magic_wall_active)
12633 int jx = local_player->jx, jy = local_player->jy;
12635 // play the element sound at the position nearest to the player
12636 if ((element == EL_MAGIC_WALL_FULL ||
12637 element == EL_MAGIC_WALL_ACTIVE ||
12638 element == EL_MAGIC_WALL_EMPTYING ||
12639 element == EL_BD_MAGIC_WALL_FULL ||
12640 element == EL_BD_MAGIC_WALL_ACTIVE ||
12641 element == EL_BD_MAGIC_WALL_EMPTYING ||
12642 element == EL_DC_MAGIC_WALL_FULL ||
12643 element == EL_DC_MAGIC_WALL_ACTIVE ||
12644 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12645 ABS(x - jx) + ABS(y - jy) <
12646 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12654 #if USE_NEW_AMOEBA_CODE
12655 // new experimental amoeba growth stuff
12656 if (!(FrameCounter % 8))
12658 static unsigned int random = 1684108901;
12660 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12662 x = RND(lev_fieldx);
12663 y = RND(lev_fieldy);
12664 element = Tile[x][y];
12666 if (!IS_PLAYER(x, y) &&
12667 (element == EL_EMPTY ||
12668 CAN_GROW_INTO(element) ||
12669 element == EL_QUICKSAND_EMPTY ||
12670 element == EL_QUICKSAND_FAST_EMPTY ||
12671 element == EL_ACID_SPLASH_LEFT ||
12672 element == EL_ACID_SPLASH_RIGHT))
12674 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12675 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12676 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12677 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12678 Tile[x][y] = EL_AMOEBA_DROP;
12681 random = random * 129 + 1;
12686 game.explosions_delayed = FALSE;
12688 SCAN_PLAYFIELD(x, y)
12690 element = Tile[x][y];
12692 if (ExplodeField[x][y])
12693 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12694 else if (element == EL_EXPLOSION)
12695 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12697 ExplodeField[x][y] = EX_TYPE_NONE;
12700 game.explosions_delayed = TRUE;
12702 if (game.magic_wall_active)
12704 if (!(game.magic_wall_time_left % 4))
12706 int element = Tile[magic_wall_x][magic_wall_y];
12708 if (element == EL_BD_MAGIC_WALL_FULL ||
12709 element == EL_BD_MAGIC_WALL_ACTIVE ||
12710 element == EL_BD_MAGIC_WALL_EMPTYING)
12711 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12712 else if (element == EL_DC_MAGIC_WALL_FULL ||
12713 element == EL_DC_MAGIC_WALL_ACTIVE ||
12714 element == EL_DC_MAGIC_WALL_EMPTYING)
12715 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12717 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12720 if (game.magic_wall_time_left > 0)
12722 game.magic_wall_time_left--;
12724 if (!game.magic_wall_time_left)
12726 SCAN_PLAYFIELD(x, y)
12728 element = Tile[x][y];
12730 if (element == EL_MAGIC_WALL_ACTIVE ||
12731 element == EL_MAGIC_WALL_FULL)
12733 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12734 TEST_DrawLevelField(x, y);
12736 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12737 element == EL_BD_MAGIC_WALL_FULL)
12739 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12740 TEST_DrawLevelField(x, y);
12742 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12743 element == EL_DC_MAGIC_WALL_FULL)
12745 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12746 TEST_DrawLevelField(x, y);
12750 game.magic_wall_active = FALSE;
12755 if (game.light_time_left > 0)
12757 game.light_time_left--;
12759 if (game.light_time_left == 0)
12760 RedrawAllLightSwitchesAndInvisibleElements();
12763 if (game.timegate_time_left > 0)
12765 game.timegate_time_left--;
12767 if (game.timegate_time_left == 0)
12768 CloseAllOpenTimegates();
12771 if (game.lenses_time_left > 0)
12773 game.lenses_time_left--;
12775 if (game.lenses_time_left == 0)
12776 RedrawAllInvisibleElementsForLenses();
12779 if (game.magnify_time_left > 0)
12781 game.magnify_time_left--;
12783 if (game.magnify_time_left == 0)
12784 RedrawAllInvisibleElementsForMagnifier();
12787 for (i = 0; i < MAX_PLAYERS; i++)
12789 struct PlayerInfo *player = &stored_player[i];
12791 if (SHIELD_ON(player))
12793 if (player->shield_deadly_time_left)
12794 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12795 else if (player->shield_normal_time_left)
12796 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12800 #if USE_DELAYED_GFX_REDRAW
12801 SCAN_PLAYFIELD(x, y)
12803 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12805 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12806 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12808 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12809 DrawLevelField(x, y);
12811 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12812 DrawLevelFieldCrumbled(x, y);
12814 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12815 DrawLevelFieldCrumbledNeighbours(x, y);
12817 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12818 DrawTwinkleOnField(x, y);
12821 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12826 PlayAllPlayersSound();
12828 for (i = 0; i < MAX_PLAYERS; i++)
12830 struct PlayerInfo *player = &stored_player[i];
12832 if (player->show_envelope != 0 && (!player->active ||
12833 player->MovPos == 0))
12835 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12837 player->show_envelope = 0;
12841 // use random number generator in every frame to make it less predictable
12842 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12845 mouse_action_last = mouse_action;
12848 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12850 int min_x = x, min_y = y, max_x = x, max_y = y;
12851 int scr_fieldx = getScreenFieldSizeX();
12852 int scr_fieldy = getScreenFieldSizeY();
12855 for (i = 0; i < MAX_PLAYERS; i++)
12857 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12859 if (!stored_player[i].active || &stored_player[i] == player)
12862 min_x = MIN(min_x, jx);
12863 min_y = MIN(min_y, jy);
12864 max_x = MAX(max_x, jx);
12865 max_y = MAX(max_y, jy);
12868 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12871 static boolean AllPlayersInVisibleScreen(void)
12875 for (i = 0; i < MAX_PLAYERS; i++)
12877 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12879 if (!stored_player[i].active)
12882 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12889 void ScrollLevel(int dx, int dy)
12891 int scroll_offset = 2 * TILEX_VAR;
12894 BlitBitmap(drawto_field, drawto_field,
12895 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12896 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12897 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12898 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12899 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12900 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12904 x = (dx == 1 ? BX1 : BX2);
12905 for (y = BY1; y <= BY2; y++)
12906 DrawScreenField(x, y);
12911 y = (dy == 1 ? BY1 : BY2);
12912 for (x = BX1; x <= BX2; x++)
12913 DrawScreenField(x, y);
12916 redraw_mask |= REDRAW_FIELD;
12919 static boolean canFallDown(struct PlayerInfo *player)
12921 int jx = player->jx, jy = player->jy;
12923 return (IN_LEV_FIELD(jx, jy + 1) &&
12924 (IS_FREE(jx, jy + 1) ||
12925 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12926 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12927 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12930 static boolean canPassField(int x, int y, int move_dir)
12932 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12933 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12934 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12935 int nextx = x + dx;
12936 int nexty = y + dy;
12937 int element = Tile[x][y];
12939 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12940 !CAN_MOVE(element) &&
12941 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12942 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12943 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12946 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12948 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12949 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12950 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12954 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12955 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12956 (IS_DIGGABLE(Tile[newx][newy]) ||
12957 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12958 canPassField(newx, newy, move_dir)));
12961 static void CheckGravityMovement(struct PlayerInfo *player)
12963 if (player->gravity && !player->programmed_action)
12965 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12966 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12967 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12968 int jx = player->jx, jy = player->jy;
12969 boolean player_is_moving_to_valid_field =
12970 (!player_is_snapping &&
12971 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12972 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12973 boolean player_can_fall_down = canFallDown(player);
12975 if (player_can_fall_down &&
12976 !player_is_moving_to_valid_field)
12977 player->programmed_action = MV_DOWN;
12981 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12983 return CheckGravityMovement(player);
12985 if (player->gravity && !player->programmed_action)
12987 int jx = player->jx, jy = player->jy;
12988 boolean field_under_player_is_free =
12989 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12990 boolean player_is_standing_on_valid_field =
12991 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12992 (IS_WALKABLE(Tile[jx][jy]) &&
12993 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12995 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12996 player->programmed_action = MV_DOWN;
13001 MovePlayerOneStep()
13002 -----------------------------------------------------------------------------
13003 dx, dy: direction (non-diagonal) to try to move the player to
13004 real_dx, real_dy: direction as read from input device (can be diagonal)
13007 boolean MovePlayerOneStep(struct PlayerInfo *player,
13008 int dx, int dy, int real_dx, int real_dy)
13010 int jx = player->jx, jy = player->jy;
13011 int new_jx = jx + dx, new_jy = jy + dy;
13013 boolean player_can_move = !player->cannot_move;
13015 if (!player->active || (!dx && !dy))
13016 return MP_NO_ACTION;
13018 player->MovDir = (dx < 0 ? MV_LEFT :
13019 dx > 0 ? MV_RIGHT :
13021 dy > 0 ? MV_DOWN : MV_NONE);
13023 if (!IN_LEV_FIELD(new_jx, new_jy))
13024 return MP_NO_ACTION;
13026 if (!player_can_move)
13028 if (player->MovPos == 0)
13030 player->is_moving = FALSE;
13031 player->is_digging = FALSE;
13032 player->is_collecting = FALSE;
13033 player->is_snapping = FALSE;
13034 player->is_pushing = FALSE;
13038 if (!network.enabled && game.centered_player_nr == -1 &&
13039 !AllPlayersInSight(player, new_jx, new_jy))
13040 return MP_NO_ACTION;
13042 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG);
13043 if (can_move != MP_MOVING)
13046 // check if DigField() has caused relocation of the player
13047 if (player->jx != jx || player->jy != jy)
13048 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
13050 StorePlayer[jx][jy] = 0;
13051 player->last_jx = jx;
13052 player->last_jy = jy;
13053 player->jx = new_jx;
13054 player->jy = new_jy;
13055 StorePlayer[new_jx][new_jy] = player->element_nr;
13057 if (player->move_delay_value_next != -1)
13059 player->move_delay_value = player->move_delay_value_next;
13060 player->move_delay_value_next = -1;
13064 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
13066 player->step_counter++;
13068 PlayerVisit[jx][jy] = FrameCounter;
13070 player->is_moving = TRUE;
13073 // should better be called in MovePlayer(), but this breaks some tapes
13074 ScrollPlayer(player, SCROLL_INIT);
13080 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
13082 int jx = player->jx, jy = player->jy;
13083 int old_jx = jx, old_jy = jy;
13084 int moved = MP_NO_ACTION;
13086 if (!player->active)
13091 if (player->MovPos == 0)
13093 player->is_moving = FALSE;
13094 player->is_digging = FALSE;
13095 player->is_collecting = FALSE;
13096 player->is_snapping = FALSE;
13097 player->is_pushing = FALSE;
13103 if (player->move_delay > 0)
13106 player->move_delay = -1; // set to "uninitialized" value
13108 // store if player is automatically moved to next field
13109 player->is_auto_moving = (player->programmed_action != MV_NONE);
13111 // remove the last programmed player action
13112 player->programmed_action = 0;
13114 if (player->MovPos)
13116 // should only happen if pre-1.2 tape recordings are played
13117 // this is only for backward compatibility
13119 int original_move_delay_value = player->move_delay_value;
13122 Debug("game:playing:MovePlayer",
13123 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13127 // scroll remaining steps with finest movement resolution
13128 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13130 while (player->MovPos)
13132 ScrollPlayer(player, SCROLL_GO_ON);
13133 ScrollScreen(NULL, SCROLL_GO_ON);
13135 AdvanceFrameAndPlayerCounters(player->index_nr);
13138 BackToFront_WithFrameDelay(0);
13141 player->move_delay_value = original_move_delay_value;
13144 player->is_active = FALSE;
13146 if (player->last_move_dir & MV_HORIZONTAL)
13148 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13149 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13153 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13154 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13157 if (!moved && !player->is_active)
13159 player->is_moving = FALSE;
13160 player->is_digging = FALSE;
13161 player->is_collecting = FALSE;
13162 player->is_snapping = FALSE;
13163 player->is_pushing = FALSE;
13169 if (moved & MP_MOVING && !ScreenMovPos &&
13170 (player->index_nr == game.centered_player_nr ||
13171 game.centered_player_nr == -1))
13173 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13175 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13177 // actual player has left the screen -- scroll in that direction
13178 if (jx != old_jx) // player has moved horizontally
13179 scroll_x += (jx - old_jx);
13180 else // player has moved vertically
13181 scroll_y += (jy - old_jy);
13185 int offset_raw = game.scroll_delay_value;
13187 if (jx != old_jx) // player has moved horizontally
13189 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13190 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13191 int new_scroll_x = jx - MIDPOSX + offset_x;
13193 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13194 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13195 scroll_x = new_scroll_x;
13197 // don't scroll over playfield boundaries
13198 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13200 // don't scroll more than one field at a time
13201 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13203 // don't scroll against the player's moving direction
13204 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13205 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13206 scroll_x = old_scroll_x;
13208 else // player has moved vertically
13210 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13211 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13212 int new_scroll_y = jy - MIDPOSY + offset_y;
13214 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13215 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13216 scroll_y = new_scroll_y;
13218 // don't scroll over playfield boundaries
13219 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13221 // don't scroll more than one field at a time
13222 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13224 // don't scroll against the player's moving direction
13225 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13226 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13227 scroll_y = old_scroll_y;
13231 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13233 if (!network.enabled && game.centered_player_nr == -1 &&
13234 !AllPlayersInVisibleScreen())
13236 scroll_x = old_scroll_x;
13237 scroll_y = old_scroll_y;
13241 ScrollScreen(player, SCROLL_INIT);
13242 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13247 player->StepFrame = 0;
13249 if (moved & MP_MOVING)
13251 if (old_jx != jx && old_jy == jy)
13252 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13253 else if (old_jx == jx && old_jy != jy)
13254 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13256 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13258 player->last_move_dir = player->MovDir;
13259 player->is_moving = TRUE;
13260 player->is_snapping = FALSE;
13261 player->is_switching = FALSE;
13262 player->is_dropping = FALSE;
13263 player->is_dropping_pressed = FALSE;
13264 player->drop_pressed_delay = 0;
13267 // should better be called here than above, but this breaks some tapes
13268 ScrollPlayer(player, SCROLL_INIT);
13273 CheckGravityMovementWhenNotMoving(player);
13275 player->is_moving = FALSE;
13277 /* at this point, the player is allowed to move, but cannot move right now
13278 (e.g. because of something blocking the way) -- ensure that the player
13279 is also allowed to move in the next frame (in old versions before 3.1.1,
13280 the player was forced to wait again for eight frames before next try) */
13282 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13283 player->move_delay = 0; // allow direct movement in the next frame
13286 if (player->move_delay == -1) // not yet initialized by DigField()
13287 player->move_delay = player->move_delay_value;
13289 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13291 TestIfPlayerTouchesBadThing(jx, jy);
13292 TestIfPlayerTouchesCustomElement(jx, jy);
13295 if (!player->active)
13296 RemovePlayer(player);
13301 void ScrollPlayer(struct PlayerInfo *player, int mode)
13303 int jx = player->jx, jy = player->jy;
13304 int last_jx = player->last_jx, last_jy = player->last_jy;
13305 int move_stepsize = TILEX / player->move_delay_value;
13307 if (!player->active)
13310 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13313 if (mode == SCROLL_INIT)
13315 player->actual_frame_counter.count = FrameCounter;
13316 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13318 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13319 Tile[last_jx][last_jy] == EL_EMPTY)
13321 int last_field_block_delay = 0; // start with no blocking at all
13322 int block_delay_adjustment = player->block_delay_adjustment;
13324 // if player blocks last field, add delay for exactly one move
13325 if (player->block_last_field)
13327 last_field_block_delay += player->move_delay_value;
13329 // when blocking enabled, prevent moving up despite gravity
13330 if (player->gravity && player->MovDir == MV_UP)
13331 block_delay_adjustment = -1;
13334 // add block delay adjustment (also possible when not blocking)
13335 last_field_block_delay += block_delay_adjustment;
13337 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13338 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13341 if (player->MovPos != 0) // player has not yet reached destination
13344 else if (!FrameReached(&player->actual_frame_counter))
13347 if (player->MovPos != 0)
13349 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13350 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13352 // before DrawPlayer() to draw correct player graphic for this case
13353 if (player->MovPos == 0)
13354 CheckGravityMovement(player);
13357 if (player->MovPos == 0) // player reached destination field
13359 if (player->move_delay_reset_counter > 0)
13361 player->move_delay_reset_counter--;
13363 if (player->move_delay_reset_counter == 0)
13365 // continue with normal speed after quickly moving through gate
13366 HALVE_PLAYER_SPEED(player);
13368 // be able to make the next move without delay
13369 player->move_delay = 0;
13373 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13374 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13375 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13376 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13377 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13378 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13379 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13380 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13382 ExitPlayer(player);
13384 if (game.players_still_needed == 0 &&
13385 (game.friends_still_needed == 0 ||
13386 IS_SP_ELEMENT(Tile[jx][jy])))
13390 player->last_jx = jx;
13391 player->last_jy = jy;
13393 // this breaks one level: "machine", level 000
13395 int move_direction = player->MovDir;
13396 int enter_side = MV_DIR_OPPOSITE(move_direction);
13397 int leave_side = move_direction;
13398 int old_jx = last_jx;
13399 int old_jy = last_jy;
13400 int old_element = Tile[old_jx][old_jy];
13401 int new_element = Tile[jx][jy];
13403 if (IS_CUSTOM_ELEMENT(old_element))
13404 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13406 player->index_bit, leave_side);
13408 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13409 CE_PLAYER_LEAVES_X,
13410 player->index_bit, leave_side);
13412 // needed because pushed element has not yet reached its destination,
13413 // so it would trigger a change event at its previous field location
13414 if (!player->is_pushing)
13416 if (IS_CUSTOM_ELEMENT(new_element))
13417 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13418 player->index_bit, enter_side);
13420 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13421 CE_PLAYER_ENTERS_X,
13422 player->index_bit, enter_side);
13425 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13426 CE_MOVE_OF_X, move_direction);
13429 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13431 TestIfPlayerTouchesBadThing(jx, jy);
13432 TestIfPlayerTouchesCustomElement(jx, jy);
13434 // needed because pushed element has not yet reached its destination,
13435 // so it would trigger a change event at its previous field location
13436 if (!player->is_pushing)
13437 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13439 if (level.finish_dig_collect &&
13440 (player->is_digging || player->is_collecting))
13442 int last_element = player->last_removed_element;
13443 int move_direction = player->MovDir;
13444 int enter_side = MV_DIR_OPPOSITE(move_direction);
13445 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13446 CE_PLAYER_COLLECTS_X);
13448 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13449 player->index_bit, enter_side);
13451 player->last_removed_element = EL_UNDEFINED;
13454 if (!player->active)
13455 RemovePlayer(player);
13458 if (level.use_step_counter)
13459 CheckLevelTime_StepCounter();
13461 if (tape.single_step && tape.recording && !tape.pausing &&
13462 !player->programmed_action)
13463 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13465 if (!player->programmed_action)
13466 CheckSaveEngineSnapshot(player);
13470 void ScrollScreen(struct PlayerInfo *player, int mode)
13472 static DelayCounter screen_frame_counter = { 0 };
13474 if (mode == SCROLL_INIT)
13476 // set scrolling step size according to actual player's moving speed
13477 ScrollStepSize = TILEX / player->move_delay_value;
13479 screen_frame_counter.count = FrameCounter;
13480 screen_frame_counter.value = 1;
13482 ScreenMovDir = player->MovDir;
13483 ScreenMovPos = player->MovPos;
13484 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13487 else if (!FrameReached(&screen_frame_counter))
13492 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13493 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13494 redraw_mask |= REDRAW_FIELD;
13497 ScreenMovDir = MV_NONE;
13500 void CheckNextToConditions(int x, int y)
13502 int element = Tile[x][y];
13504 if (IS_PLAYER(x, y))
13505 TestIfPlayerNextToCustomElement(x, y);
13507 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13508 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13509 TestIfElementNextToCustomElement(x, y);
13512 void TestIfPlayerNextToCustomElement(int x, int y)
13514 struct XY *xy = xy_topdown;
13515 static int trigger_sides[4][2] =
13517 // center side border side
13518 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13519 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13520 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13521 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13525 if (!IS_PLAYER(x, y))
13528 struct PlayerInfo *player = PLAYERINFO(x, y);
13530 if (player->is_moving)
13533 for (i = 0; i < NUM_DIRECTIONS; i++)
13535 int xx = x + xy[i].x;
13536 int yy = y + xy[i].y;
13537 int border_side = trigger_sides[i][1];
13538 int border_element;
13540 if (!IN_LEV_FIELD(xx, yy))
13543 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13544 continue; // center and border element not connected
13546 border_element = Tile[xx][yy];
13548 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13549 player->index_bit, border_side);
13550 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13551 CE_PLAYER_NEXT_TO_X,
13552 player->index_bit, border_side);
13554 /* use player element that is initially defined in the level playfield,
13555 not the player element that corresponds to the runtime player number
13556 (example: a level that contains EL_PLAYER_3 as the only player would
13557 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13559 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13560 CE_NEXT_TO_X, border_side);
13564 void TestIfPlayerTouchesCustomElement(int x, int y)
13566 struct XY *xy = xy_topdown;
13567 static int trigger_sides[4][2] =
13569 // center side border side
13570 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13571 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13572 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13573 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13575 static int touch_dir[4] =
13577 MV_LEFT | MV_RIGHT,
13582 int center_element = Tile[x][y]; // should always be non-moving!
13585 for (i = 0; i < NUM_DIRECTIONS; i++)
13587 int xx = x + xy[i].x;
13588 int yy = y + xy[i].y;
13589 int center_side = trigger_sides[i][0];
13590 int border_side = trigger_sides[i][1];
13591 int border_element;
13593 if (!IN_LEV_FIELD(xx, yy))
13596 if (IS_PLAYER(x, y)) // player found at center element
13598 struct PlayerInfo *player = PLAYERINFO(x, y);
13600 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13601 border_element = Tile[xx][yy]; // may be moving!
13602 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13603 border_element = Tile[xx][yy];
13604 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13605 border_element = MovingOrBlocked2Element(xx, yy);
13607 continue; // center and border element do not touch
13609 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13610 player->index_bit, border_side);
13611 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13612 CE_PLAYER_TOUCHES_X,
13613 player->index_bit, border_side);
13616 /* use player element that is initially defined in the level playfield,
13617 not the player element that corresponds to the runtime player number
13618 (example: a level that contains EL_PLAYER_3 as the only player would
13619 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13620 int player_element = PLAYERINFO(x, y)->initial_element;
13622 // as element "X" is the player here, check opposite (center) side
13623 CheckElementChangeBySide(xx, yy, border_element, player_element,
13624 CE_TOUCHING_X, center_side);
13627 else if (IS_PLAYER(xx, yy)) // player found at border element
13629 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13631 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13633 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13634 continue; // center and border element do not touch
13637 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13638 player->index_bit, center_side);
13639 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13640 CE_PLAYER_TOUCHES_X,
13641 player->index_bit, center_side);
13644 /* use player element that is initially defined in the level playfield,
13645 not the player element that corresponds to the runtime player number
13646 (example: a level that contains EL_PLAYER_3 as the only player would
13647 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13648 int player_element = PLAYERINFO(xx, yy)->initial_element;
13650 // as element "X" is the player here, check opposite (border) side
13651 CheckElementChangeBySide(x, y, center_element, player_element,
13652 CE_TOUCHING_X, border_side);
13660 void TestIfElementNextToCustomElement(int x, int y)
13662 struct XY *xy = xy_topdown;
13663 static int trigger_sides[4][2] =
13665 // center side border side
13666 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13667 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13668 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13669 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13671 int center_element = Tile[x][y]; // should always be non-moving!
13674 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13677 for (i = 0; i < NUM_DIRECTIONS; i++)
13679 int xx = x + xy[i].x;
13680 int yy = y + xy[i].y;
13681 int border_side = trigger_sides[i][1];
13682 int border_element;
13684 if (!IN_LEV_FIELD(xx, yy))
13687 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13688 continue; // center and border element not connected
13690 border_element = Tile[xx][yy];
13692 // check for change of center element (but change it only once)
13693 if (CheckElementChangeBySide(x, y, center_element, border_element,
13694 CE_NEXT_TO_X, border_side))
13699 void TestIfElementTouchesCustomElement(int x, int y)
13701 struct XY *xy = xy_topdown;
13702 static int trigger_sides[4][2] =
13704 // center side border side
13705 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13706 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13707 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13708 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13710 static int touch_dir[4] =
13712 MV_LEFT | MV_RIGHT,
13717 boolean change_center_element = FALSE;
13718 int center_element = Tile[x][y]; // should always be non-moving!
13719 int border_element_old[NUM_DIRECTIONS];
13722 for (i = 0; i < NUM_DIRECTIONS; i++)
13724 int xx = x + xy[i].x;
13725 int yy = y + xy[i].y;
13726 int border_element;
13728 border_element_old[i] = -1;
13730 if (!IN_LEV_FIELD(xx, yy))
13733 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13734 border_element = Tile[xx][yy]; // may be moving!
13735 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13736 border_element = Tile[xx][yy];
13737 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13738 border_element = MovingOrBlocked2Element(xx, yy);
13740 continue; // center and border element do not touch
13742 border_element_old[i] = border_element;
13745 for (i = 0; i < NUM_DIRECTIONS; i++)
13747 int xx = x + xy[i].x;
13748 int yy = y + xy[i].y;
13749 int center_side = trigger_sides[i][0];
13750 int border_element = border_element_old[i];
13752 if (border_element == -1)
13755 // check for change of border element
13756 CheckElementChangeBySide(xx, yy, border_element, center_element,
13757 CE_TOUCHING_X, center_side);
13759 // (center element cannot be player, so we don't have to check this here)
13762 for (i = 0; i < NUM_DIRECTIONS; i++)
13764 int xx = x + xy[i].x;
13765 int yy = y + xy[i].y;
13766 int border_side = trigger_sides[i][1];
13767 int border_element = border_element_old[i];
13769 if (border_element == -1)
13772 // check for change of center element (but change it only once)
13773 if (!change_center_element)
13774 change_center_element =
13775 CheckElementChangeBySide(x, y, center_element, border_element,
13776 CE_TOUCHING_X, border_side);
13778 if (IS_PLAYER(xx, yy))
13780 /* use player element that is initially defined in the level playfield,
13781 not the player element that corresponds to the runtime player number
13782 (example: a level that contains EL_PLAYER_3 as the only player would
13783 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13784 int player_element = PLAYERINFO(xx, yy)->initial_element;
13786 // as element "X" is the player here, check opposite (border) side
13787 CheckElementChangeBySide(x, y, center_element, player_element,
13788 CE_TOUCHING_X, border_side);
13793 void TestIfElementHitsCustomElement(int x, int y, int direction)
13795 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13796 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13797 int hitx = x + dx, hity = y + dy;
13798 int hitting_element = Tile[x][y];
13799 int touched_element;
13801 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13804 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13805 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13807 if (IN_LEV_FIELD(hitx, hity))
13809 int opposite_direction = MV_DIR_OPPOSITE(direction);
13810 int hitting_side = direction;
13811 int touched_side = opposite_direction;
13812 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13813 MovDir[hitx][hity] != direction ||
13814 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13820 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13821 CE_HITTING_X, touched_side);
13823 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13824 CE_HIT_BY_X, hitting_side);
13826 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13827 CE_HIT_BY_SOMETHING, opposite_direction);
13829 if (IS_PLAYER(hitx, hity))
13831 /* use player element that is initially defined in the level playfield,
13832 not the player element that corresponds to the runtime player number
13833 (example: a level that contains EL_PLAYER_3 as the only player would
13834 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13835 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13837 CheckElementChangeBySide(x, y, hitting_element, player_element,
13838 CE_HITTING_X, touched_side);
13843 // "hitting something" is also true when hitting the playfield border
13844 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13845 CE_HITTING_SOMETHING, direction);
13848 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13850 int i, kill_x = -1, kill_y = -1;
13852 int bad_element = -1;
13853 struct XY *test_xy = xy_topdown;
13854 static int test_dir[4] =
13862 for (i = 0; i < NUM_DIRECTIONS; i++)
13864 int test_x, test_y, test_move_dir, test_element;
13866 test_x = good_x + test_xy[i].x;
13867 test_y = good_y + test_xy[i].y;
13869 if (!IN_LEV_FIELD(test_x, test_y))
13873 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13875 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13877 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13878 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13880 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13881 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13885 bad_element = test_element;
13891 if (kill_x != -1 || kill_y != -1)
13893 if (IS_PLAYER(good_x, good_y))
13895 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13897 if (player->shield_deadly_time_left > 0 &&
13898 !IS_INDESTRUCTIBLE(bad_element))
13899 Bang(kill_x, kill_y);
13900 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13901 KillPlayer(player);
13904 Bang(good_x, good_y);
13908 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13910 int i, kill_x = -1, kill_y = -1;
13911 int bad_element = Tile[bad_x][bad_y];
13912 struct XY *test_xy = xy_topdown;
13913 static int touch_dir[4] =
13915 MV_LEFT | MV_RIGHT,
13920 static int test_dir[4] =
13928 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13931 for (i = 0; i < NUM_DIRECTIONS; i++)
13933 int test_x, test_y, test_move_dir, test_element;
13935 test_x = bad_x + test_xy[i].x;
13936 test_y = bad_y + test_xy[i].y;
13938 if (!IN_LEV_FIELD(test_x, test_y))
13942 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13944 test_element = Tile[test_x][test_y];
13946 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13947 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13949 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13950 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13952 // good thing is player or penguin that does not move away
13953 if (IS_PLAYER(test_x, test_y))
13955 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13957 if (bad_element == EL_ROBOT && player->is_moving)
13958 continue; // robot does not kill player if he is moving
13960 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13962 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13963 continue; // center and border element do not touch
13971 else if (test_element == EL_PENGUIN)
13981 if (kill_x != -1 || kill_y != -1)
13983 if (IS_PLAYER(kill_x, kill_y))
13985 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13987 if (player->shield_deadly_time_left > 0 &&
13988 !IS_INDESTRUCTIBLE(bad_element))
13989 Bang(bad_x, bad_y);
13990 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13991 KillPlayer(player);
13994 Bang(kill_x, kill_y);
13998 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
14000 int bad_element = Tile[bad_x][bad_y];
14001 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
14002 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
14003 int test_x = bad_x + dx, test_y = bad_y + dy;
14004 int test_move_dir, test_element;
14005 int kill_x = -1, kill_y = -1;
14007 if (!IN_LEV_FIELD(test_x, test_y))
14011 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14013 test_element = Tile[test_x][test_y];
14015 if (test_move_dir != bad_move_dir)
14017 // good thing can be player or penguin that does not move away
14018 if (IS_PLAYER(test_x, test_y))
14020 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14022 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
14023 player as being hit when he is moving towards the bad thing, because
14024 the "get hit by" condition would be lost after the player stops) */
14025 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
14026 return; // player moves away from bad thing
14031 else if (test_element == EL_PENGUIN)
14038 if (kill_x != -1 || kill_y != -1)
14040 if (IS_PLAYER(kill_x, kill_y))
14042 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14044 if (player->shield_deadly_time_left > 0 &&
14045 !IS_INDESTRUCTIBLE(bad_element))
14046 Bang(bad_x, bad_y);
14047 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14048 KillPlayer(player);
14051 Bang(kill_x, kill_y);
14055 void TestIfPlayerTouchesBadThing(int x, int y)
14057 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14060 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
14062 TestIfGoodThingHitsBadThing(x, y, move_dir);
14065 void TestIfBadThingTouchesPlayer(int x, int y)
14067 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14070 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
14072 TestIfBadThingHitsGoodThing(x, y, move_dir);
14075 void TestIfFriendTouchesBadThing(int x, int y)
14077 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14080 void TestIfBadThingTouchesFriend(int x, int y)
14082 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14085 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
14087 int i, kill_x = bad_x, kill_y = bad_y;
14088 struct XY *xy = xy_topdown;
14090 for (i = 0; i < NUM_DIRECTIONS; i++)
14094 x = bad_x + xy[i].x;
14095 y = bad_y + xy[i].y;
14096 if (!IN_LEV_FIELD(x, y))
14099 element = Tile[x][y];
14100 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14101 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14109 if (kill_x != bad_x || kill_y != bad_y)
14110 Bang(bad_x, bad_y);
14113 void KillPlayer(struct PlayerInfo *player)
14115 int jx = player->jx, jy = player->jy;
14117 if (!player->active)
14121 Debug("game:playing:KillPlayer",
14122 "0: killed == %d, active == %d, reanimated == %d",
14123 player->killed, player->active, player->reanimated);
14126 /* the following code was introduced to prevent an infinite loop when calling
14128 -> CheckTriggeredElementChangeExt()
14129 -> ExecuteCustomElementAction()
14131 -> (infinitely repeating the above sequence of function calls)
14132 which occurs when killing the player while having a CE with the setting
14133 "kill player X when explosion of <player X>"; the solution using a new
14134 field "player->killed" was chosen for backwards compatibility, although
14135 clever use of the fields "player->active" etc. would probably also work */
14137 if (player->killed)
14141 player->killed = TRUE;
14143 // remove accessible field at the player's position
14144 RemoveField(jx, jy);
14146 // deactivate shield (else Bang()/Explode() would not work right)
14147 player->shield_normal_time_left = 0;
14148 player->shield_deadly_time_left = 0;
14151 Debug("game:playing:KillPlayer",
14152 "1: killed == %d, active == %d, reanimated == %d",
14153 player->killed, player->active, player->reanimated);
14159 Debug("game:playing:KillPlayer",
14160 "2: killed == %d, active == %d, reanimated == %d",
14161 player->killed, player->active, player->reanimated);
14164 if (player->reanimated) // killed player may have been reanimated
14165 player->killed = player->reanimated = FALSE;
14167 BuryPlayer(player);
14170 static void KillPlayerUnlessEnemyProtected(int x, int y)
14172 if (!PLAYER_ENEMY_PROTECTED(x, y))
14173 KillPlayer(PLAYERINFO(x, y));
14176 static void KillPlayerUnlessExplosionProtected(int x, int y)
14178 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14179 KillPlayer(PLAYERINFO(x, y));
14182 void BuryPlayer(struct PlayerInfo *player)
14184 int jx = player->jx, jy = player->jy;
14186 if (!player->active)
14189 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14191 RemovePlayer(player);
14193 player->buried = TRUE;
14195 if (game.all_players_gone)
14196 game.GameOver = TRUE;
14199 void RemovePlayer(struct PlayerInfo *player)
14201 int jx = player->jx, jy = player->jy;
14202 int i, found = FALSE;
14204 player->present = FALSE;
14205 player->active = FALSE;
14207 // required for some CE actions (even if the player is not active anymore)
14208 player->MovPos = 0;
14210 if (!ExplodeField[jx][jy])
14211 StorePlayer[jx][jy] = 0;
14213 if (player->is_moving)
14214 TEST_DrawLevelField(player->last_jx, player->last_jy);
14216 for (i = 0; i < MAX_PLAYERS; i++)
14217 if (stored_player[i].active)
14222 game.all_players_gone = TRUE;
14223 game.GameOver = TRUE;
14226 game.exit_x = game.robot_wheel_x = jx;
14227 game.exit_y = game.robot_wheel_y = jy;
14230 void ExitPlayer(struct PlayerInfo *player)
14232 DrawPlayer(player); // needed here only to cleanup last field
14233 RemovePlayer(player);
14235 if (game.players_still_needed > 0)
14236 game.players_still_needed--;
14239 static void SetFieldForSnapping(int x, int y, int element, int direction,
14240 int player_index_bit)
14242 struct ElementInfo *ei = &element_info[element];
14243 int direction_bit = MV_DIR_TO_BIT(direction);
14244 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14245 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14246 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14248 Tile[x][y] = EL_ELEMENT_SNAPPING;
14249 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14250 MovDir[x][y] = direction;
14251 Store[x][y] = element;
14252 Store2[x][y] = player_index_bit;
14254 ResetGfxAnimation(x, y);
14256 GfxElement[x][y] = element;
14257 GfxAction[x][y] = action;
14258 GfxDir[x][y] = direction;
14259 GfxFrame[x][y] = -1;
14262 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14263 int player_index_bit)
14265 TestIfElementTouchesCustomElement(x, y); // for empty space
14267 if (level.finish_dig_collect)
14269 int dig_side = MV_DIR_OPPOSITE(direction);
14270 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14271 CE_PLAYER_COLLECTS_X);
14273 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14274 player_index_bit, dig_side);
14275 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14276 player_index_bit, dig_side);
14281 =============================================================================
14282 checkDiagonalPushing()
14283 -----------------------------------------------------------------------------
14284 check if diagonal input device direction results in pushing of object
14285 (by checking if the alternative direction is walkable, diggable, ...)
14286 =============================================================================
14289 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14290 int x, int y, int real_dx, int real_dy)
14292 int jx, jy, dx, dy, xx, yy;
14294 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14297 // diagonal direction: check alternative direction
14302 xx = jx + (dx == 0 ? real_dx : 0);
14303 yy = jy + (dy == 0 ? real_dy : 0);
14305 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14309 =============================================================================
14311 -----------------------------------------------------------------------------
14312 x, y: field next to player (non-diagonal) to try to dig to
14313 real_dx, real_dy: direction as read from input device (can be diagonal)
14314 =============================================================================
14317 static int DigField(struct PlayerInfo *player,
14318 int oldx, int oldy, int x, int y,
14319 int real_dx, int real_dy, int mode)
14321 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14322 boolean player_was_pushing = player->is_pushing;
14323 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14324 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14325 int jx = oldx, jy = oldy;
14326 int dx = x - jx, dy = y - jy;
14327 int nextx = x + dx, nexty = y + dy;
14328 int move_direction = (dx == -1 ? MV_LEFT :
14329 dx == +1 ? MV_RIGHT :
14331 dy == +1 ? MV_DOWN : MV_NONE);
14332 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14333 int dig_side = MV_DIR_OPPOSITE(move_direction);
14334 int old_element = Tile[jx][jy];
14335 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14338 if (is_player) // function can also be called by EL_PENGUIN
14340 if (player->MovPos == 0)
14342 player->is_digging = FALSE;
14343 player->is_collecting = FALSE;
14346 if (player->MovPos == 0) // last pushing move finished
14347 player->is_pushing = FALSE;
14349 if (mode == DF_NO_PUSH) // player just stopped pushing
14351 player->is_switching = FALSE;
14352 player->push_delay = -1;
14354 return MP_NO_ACTION;
14357 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14358 old_element = Back[jx][jy];
14360 // in case of element dropped at player position, check background
14361 else if (Back[jx][jy] != EL_EMPTY &&
14362 game.engine_version >= VERSION_IDENT(2,2,0,0))
14363 old_element = Back[jx][jy];
14365 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14366 return MP_NO_ACTION; // field has no opening in this direction
14368 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction))
14369 return MP_NO_ACTION; // field has no opening in this direction
14371 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14375 Tile[jx][jy] = player->artwork_element;
14376 InitMovingField(jx, jy, MV_DOWN);
14377 Store[jx][jy] = EL_ACID;
14378 ContinueMoving(jx, jy);
14379 BuryPlayer(player);
14381 return MP_DONT_RUN_INTO;
14384 if (player_can_move && DONT_RUN_INTO(element))
14386 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14388 return MP_DONT_RUN_INTO;
14391 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14392 return MP_NO_ACTION;
14394 collect_count = element_info[element].collect_count_initial;
14396 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14397 return MP_NO_ACTION;
14399 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14400 player_can_move = player_can_move_or_snap;
14402 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14403 game.engine_version >= VERSION_IDENT(2,2,0,0))
14405 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14406 player->index_bit, dig_side);
14407 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14408 player->index_bit, dig_side);
14410 if (element == EL_DC_LANDMINE)
14413 if (Tile[x][y] != element) // field changed by snapping
14416 return MP_NO_ACTION;
14419 if (player->gravity && is_player && !player->is_auto_moving &&
14420 canFallDown(player) && move_direction != MV_DOWN &&
14421 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14422 return MP_NO_ACTION; // player cannot walk here due to gravity
14424 if (player_can_move &&
14425 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14427 int sound_element = SND_ELEMENT(element);
14428 int sound_action = ACTION_WALKING;
14430 if (IS_RND_GATE(element))
14432 if (!player->key[RND_GATE_NR(element)])
14433 return MP_NO_ACTION;
14435 else if (IS_RND_GATE_GRAY(element))
14437 if (!player->key[RND_GATE_GRAY_NR(element)])
14438 return MP_NO_ACTION;
14440 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14442 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14443 return MP_NO_ACTION;
14445 else if (element == EL_EXIT_OPEN ||
14446 element == EL_EM_EXIT_OPEN ||
14447 element == EL_EM_EXIT_OPENING ||
14448 element == EL_STEEL_EXIT_OPEN ||
14449 element == EL_EM_STEEL_EXIT_OPEN ||
14450 element == EL_EM_STEEL_EXIT_OPENING ||
14451 element == EL_SP_EXIT_OPEN ||
14452 element == EL_SP_EXIT_OPENING)
14454 sound_action = ACTION_PASSING; // player is passing exit
14456 else if (element == EL_EMPTY)
14458 sound_action = ACTION_MOVING; // nothing to walk on
14461 // play sound from background or player, whatever is available
14462 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14463 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14465 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14467 else if (player_can_move &&
14468 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14470 if (!ACCESS_FROM(element, opposite_direction))
14471 return MP_NO_ACTION; // field not accessible from this direction
14473 if (CAN_MOVE(element)) // only fixed elements can be passed!
14474 return MP_NO_ACTION;
14476 if (IS_EM_GATE(element))
14478 if (!player->key[EM_GATE_NR(element)])
14479 return MP_NO_ACTION;
14481 else if (IS_EM_GATE_GRAY(element))
14483 if (!player->key[EM_GATE_GRAY_NR(element)])
14484 return MP_NO_ACTION;
14486 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14488 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14489 return MP_NO_ACTION;
14491 else if (IS_EMC_GATE(element))
14493 if (!player->key[EMC_GATE_NR(element)])
14494 return MP_NO_ACTION;
14496 else if (IS_EMC_GATE_GRAY(element))
14498 if (!player->key[EMC_GATE_GRAY_NR(element)])
14499 return MP_NO_ACTION;
14501 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14503 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14504 return MP_NO_ACTION;
14506 else if (element == EL_DC_GATE_WHITE ||
14507 element == EL_DC_GATE_WHITE_GRAY ||
14508 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14510 if (player->num_white_keys == 0)
14511 return MP_NO_ACTION;
14513 player->num_white_keys--;
14515 else if (IS_SP_PORT(element))
14517 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14518 element == EL_SP_GRAVITY_PORT_RIGHT ||
14519 element == EL_SP_GRAVITY_PORT_UP ||
14520 element == EL_SP_GRAVITY_PORT_DOWN)
14521 player->gravity = !player->gravity;
14522 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14523 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14524 element == EL_SP_GRAVITY_ON_PORT_UP ||
14525 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14526 player->gravity = TRUE;
14527 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14528 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14529 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14530 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14531 player->gravity = FALSE;
14534 // automatically move to the next field with double speed
14535 player->programmed_action = move_direction;
14537 if (player->move_delay_reset_counter == 0)
14539 player->move_delay_reset_counter = 2; // two double speed steps
14541 DOUBLE_PLAYER_SPEED(player);
14544 PlayLevelSoundAction(x, y, ACTION_PASSING);
14546 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14550 if (mode != DF_SNAP)
14552 GfxElement[x][y] = GFX_ELEMENT(element);
14553 player->is_digging = TRUE;
14556 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14558 // use old behaviour for old levels (digging)
14559 if (!level.finish_dig_collect)
14561 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14562 player->index_bit, dig_side);
14564 // if digging triggered player relocation, finish digging tile
14565 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14566 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14569 if (mode == DF_SNAP)
14571 if (level.block_snap_field)
14572 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14574 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14576 // use old behaviour for old levels (snapping)
14577 if (!level.finish_dig_collect)
14578 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14579 player->index_bit, dig_side);
14582 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14586 if (is_player && mode != DF_SNAP)
14588 GfxElement[x][y] = element;
14589 player->is_collecting = TRUE;
14592 if (element == EL_SPEED_PILL)
14594 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14596 else if (element == EL_EXTRA_TIME && level.time > 0)
14598 TimeLeft += level.extra_time;
14600 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14602 DisplayGameControlValues();
14604 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14606 int shield_time = (element == EL_SHIELD_DEADLY ?
14607 level.shield_deadly_time :
14608 level.shield_normal_time);
14610 player->shield_normal_time_left += shield_time;
14611 if (element == EL_SHIELD_DEADLY)
14612 player->shield_deadly_time_left += shield_time;
14614 else if (element == EL_DYNAMITE ||
14615 element == EL_EM_DYNAMITE ||
14616 element == EL_SP_DISK_RED)
14618 if (player->inventory_size < MAX_INVENTORY_SIZE)
14619 player->inventory_element[player->inventory_size++] = element;
14621 DrawGameDoorValues();
14623 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14625 player->dynabomb_count++;
14626 player->dynabombs_left++;
14628 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14630 player->dynabomb_size++;
14632 else if (element == EL_DYNABOMB_INCREASE_POWER)
14634 player->dynabomb_xl = TRUE;
14636 else if (IS_KEY(element))
14638 player->key[KEY_NR(element)] = TRUE;
14640 DrawGameDoorValues();
14642 else if (element == EL_DC_KEY_WHITE)
14644 player->num_white_keys++;
14646 // display white keys?
14647 // DrawGameDoorValues();
14649 else if (IS_ENVELOPE(element))
14651 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14653 if (!wait_for_snapping)
14654 player->show_envelope = element;
14656 else if (element == EL_EMC_LENSES)
14658 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14660 RedrawAllInvisibleElementsForLenses();
14662 else if (element == EL_EMC_MAGNIFIER)
14664 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14666 RedrawAllInvisibleElementsForMagnifier();
14668 else if (IS_DROPPABLE(element) ||
14669 IS_THROWABLE(element)) // can be collected and dropped
14673 if (collect_count == 0)
14674 player->inventory_infinite_element = element;
14676 for (i = 0; i < collect_count; i++)
14677 if (player->inventory_size < MAX_INVENTORY_SIZE)
14678 player->inventory_element[player->inventory_size++] = element;
14680 DrawGameDoorValues();
14682 else if (collect_count > 0)
14684 game.gems_still_needed -= collect_count;
14685 if (game.gems_still_needed < 0)
14686 game.gems_still_needed = 0;
14688 game.snapshot.collected_item = TRUE;
14690 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14692 DisplayGameControlValues();
14695 RaiseScoreElement(element);
14696 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14698 // use old behaviour for old levels (collecting)
14699 if (!level.finish_dig_collect && is_player)
14701 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14702 player->index_bit, dig_side);
14704 // if collecting triggered player relocation, finish collecting tile
14705 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14706 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14709 if (mode == DF_SNAP)
14711 if (level.block_snap_field)
14712 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14714 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14716 // use old behaviour for old levels (snapping)
14717 if (!level.finish_dig_collect)
14718 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14719 player->index_bit, dig_side);
14722 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14724 if (mode == DF_SNAP && element != EL_BD_ROCK)
14725 return MP_NO_ACTION;
14727 if (CAN_FALL(element) && dy)
14728 return MP_NO_ACTION;
14730 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14731 !(element == EL_SPRING && level.use_spring_bug))
14732 return MP_NO_ACTION;
14734 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14735 ((move_direction & MV_VERTICAL &&
14736 ((element_info[element].move_pattern & MV_LEFT &&
14737 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14738 (element_info[element].move_pattern & MV_RIGHT &&
14739 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14740 (move_direction & MV_HORIZONTAL &&
14741 ((element_info[element].move_pattern & MV_UP &&
14742 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14743 (element_info[element].move_pattern & MV_DOWN &&
14744 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14745 return MP_NO_ACTION;
14747 // do not push elements already moving away faster than player
14748 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14749 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14750 return MP_NO_ACTION;
14752 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14754 if (player->push_delay_value == -1 || !player_was_pushing)
14755 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14757 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14759 if (player->push_delay_value == -1)
14760 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14762 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14764 if (!player->is_pushing)
14765 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14768 player->is_pushing = TRUE;
14769 player->is_active = TRUE;
14771 if (!(IN_LEV_FIELD(nextx, nexty) &&
14772 (IS_FREE(nextx, nexty) ||
14773 (IS_SB_ELEMENT(element) &&
14774 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14775 (IS_CUSTOM_ELEMENT(element) &&
14776 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14777 return MP_NO_ACTION;
14779 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14780 return MP_NO_ACTION;
14782 if (player->push_delay == -1) // new pushing; restart delay
14783 player->push_delay = 0;
14785 if (player->push_delay < player->push_delay_value &&
14786 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14787 element != EL_SPRING && element != EL_BALLOON)
14789 // make sure that there is no move delay before next try to push
14790 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14791 player->move_delay = 0;
14793 return MP_NO_ACTION;
14796 if (IS_CUSTOM_ELEMENT(element) &&
14797 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14799 if (!DigFieldByCE(nextx, nexty, element))
14800 return MP_NO_ACTION;
14803 if (IS_SB_ELEMENT(element))
14805 boolean sokoban_task_solved = FALSE;
14807 if (element == EL_SOKOBAN_FIELD_FULL)
14809 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14811 IncrementSokobanFieldsNeeded();
14812 IncrementSokobanObjectsNeeded();
14815 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14817 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14819 DecrementSokobanFieldsNeeded();
14820 DecrementSokobanObjectsNeeded();
14822 // sokoban object was pushed from empty field to sokoban field
14823 if (Back[x][y] == EL_EMPTY)
14824 sokoban_task_solved = TRUE;
14827 Tile[x][y] = EL_SOKOBAN_OBJECT;
14829 if (Back[x][y] == Back[nextx][nexty])
14830 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14831 else if (Back[x][y] != 0)
14832 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14835 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14838 if (sokoban_task_solved &&
14839 game.sokoban_fields_still_needed == 0 &&
14840 game.sokoban_objects_still_needed == 0 &&
14841 level.auto_exit_sokoban)
14843 game.players_still_needed = 0;
14847 PlaySound(SND_GAME_SOKOBAN_SOLVING);
14851 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14853 InitMovingField(x, y, move_direction);
14854 GfxAction[x][y] = ACTION_PUSHING;
14856 if (mode == DF_SNAP)
14857 ContinueMoving(x, y);
14859 MovPos[x][y] = (dx != 0 ? dx : dy);
14861 Pushed[x][y] = TRUE;
14862 Pushed[nextx][nexty] = TRUE;
14864 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14865 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14867 player->push_delay_value = -1; // get new value later
14869 // check for element change _after_ element has been pushed
14870 if (game.use_change_when_pushing_bug)
14872 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14873 player->index_bit, dig_side);
14874 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14875 player->index_bit, dig_side);
14878 else if (IS_SWITCHABLE(element))
14880 if (PLAYER_SWITCHING(player, x, y))
14882 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14883 player->index_bit, dig_side);
14888 player->is_switching = TRUE;
14889 player->switch_x = x;
14890 player->switch_y = y;
14892 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14894 if (element == EL_ROBOT_WHEEL)
14896 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14898 game.robot_wheel_x = x;
14899 game.robot_wheel_y = y;
14900 game.robot_wheel_active = TRUE;
14902 TEST_DrawLevelField(x, y);
14904 else if (element == EL_SP_TERMINAL)
14908 SCAN_PLAYFIELD(xx, yy)
14910 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14914 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14916 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14918 ResetGfxAnimation(xx, yy);
14919 TEST_DrawLevelField(xx, yy);
14923 else if (IS_BELT_SWITCH(element))
14925 ToggleBeltSwitch(x, y);
14927 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14928 element == EL_SWITCHGATE_SWITCH_DOWN ||
14929 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14930 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14932 ToggleSwitchgateSwitch();
14934 else if (element == EL_LIGHT_SWITCH ||
14935 element == EL_LIGHT_SWITCH_ACTIVE)
14937 ToggleLightSwitch(x, y);
14939 else if (element == EL_TIMEGATE_SWITCH ||
14940 element == EL_DC_TIMEGATE_SWITCH)
14942 ActivateTimegateSwitch(x, y);
14944 else if (element == EL_BALLOON_SWITCH_LEFT ||
14945 element == EL_BALLOON_SWITCH_RIGHT ||
14946 element == EL_BALLOON_SWITCH_UP ||
14947 element == EL_BALLOON_SWITCH_DOWN ||
14948 element == EL_BALLOON_SWITCH_NONE ||
14949 element == EL_BALLOON_SWITCH_ANY)
14951 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14952 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14953 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14954 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14955 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14958 else if (element == EL_LAMP)
14960 Tile[x][y] = EL_LAMP_ACTIVE;
14961 game.lights_still_needed--;
14963 ResetGfxAnimation(x, y);
14964 TEST_DrawLevelField(x, y);
14966 else if (element == EL_TIME_ORB_FULL)
14968 Tile[x][y] = EL_TIME_ORB_EMPTY;
14970 if (level.time > 0 || level.use_time_orb_bug)
14972 TimeLeft += level.time_orb_time;
14973 game.no_level_time_limit = FALSE;
14975 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14977 DisplayGameControlValues();
14980 ResetGfxAnimation(x, y);
14981 TEST_DrawLevelField(x, y);
14983 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14984 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14988 game.ball_active = !game.ball_active;
14990 SCAN_PLAYFIELD(xx, yy)
14992 int e = Tile[xx][yy];
14994 if (game.ball_active)
14996 if (e == EL_EMC_MAGIC_BALL)
14997 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14998 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14999 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
15003 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
15004 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
15005 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15006 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
15011 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15012 player->index_bit, dig_side);
15014 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15015 player->index_bit, dig_side);
15017 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15018 player->index_bit, dig_side);
15024 if (!PLAYER_SWITCHING(player, x, y))
15026 player->is_switching = TRUE;
15027 player->switch_x = x;
15028 player->switch_y = y;
15030 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
15031 player->index_bit, dig_side);
15032 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15033 player->index_bit, dig_side);
15035 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
15036 player->index_bit, dig_side);
15037 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15038 player->index_bit, dig_side);
15041 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
15042 player->index_bit, dig_side);
15043 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15044 player->index_bit, dig_side);
15046 return MP_NO_ACTION;
15049 player->push_delay = -1;
15051 if (is_player) // function can also be called by EL_PENGUIN
15053 if (Tile[x][y] != element) // really digged/collected something
15055 player->is_collecting = !player->is_digging;
15056 player->is_active = TRUE;
15058 player->last_removed_element = element;
15065 static boolean DigFieldByCE(int x, int y, int digging_element)
15067 int element = Tile[x][y];
15069 if (!IS_FREE(x, y))
15071 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
15072 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
15075 // no element can dig solid indestructible elements
15076 if (IS_INDESTRUCTIBLE(element) &&
15077 !IS_DIGGABLE(element) &&
15078 !IS_COLLECTIBLE(element))
15081 if (AmoebaNr[x][y] &&
15082 (element == EL_AMOEBA_FULL ||
15083 element == EL_BD_AMOEBA ||
15084 element == EL_AMOEBA_GROWING))
15086 AmoebaCnt[AmoebaNr[x][y]]--;
15087 AmoebaCnt2[AmoebaNr[x][y]]--;
15090 if (IS_MOVING(x, y))
15091 RemoveMovingField(x, y);
15095 TEST_DrawLevelField(x, y);
15098 // if digged element was about to explode, prevent the explosion
15099 ExplodeField[x][y] = EX_TYPE_NONE;
15101 PlayLevelSoundAction(x, y, action);
15104 Store[x][y] = EL_EMPTY;
15106 // this makes it possible to leave the removed element again
15107 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15108 Store[x][y] = element;
15113 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15115 int jx = player->jx, jy = player->jy;
15116 int x = jx + dx, y = jy + dy;
15117 int snap_direction = (dx == -1 ? MV_LEFT :
15118 dx == +1 ? MV_RIGHT :
15120 dy == +1 ? MV_DOWN : MV_NONE);
15121 boolean can_continue_snapping = (level.continuous_snapping &&
15122 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15124 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15127 if (!player->active || !IN_LEV_FIELD(x, y))
15135 if (player->MovPos == 0)
15136 player->is_pushing = FALSE;
15138 player->is_snapping = FALSE;
15140 if (player->MovPos == 0)
15142 player->is_moving = FALSE;
15143 player->is_digging = FALSE;
15144 player->is_collecting = FALSE;
15150 // prevent snapping with already pressed snap key when not allowed
15151 if (player->is_snapping && !can_continue_snapping)
15154 player->MovDir = snap_direction;
15156 if (player->MovPos == 0)
15158 player->is_moving = FALSE;
15159 player->is_digging = FALSE;
15160 player->is_collecting = FALSE;
15163 player->is_dropping = FALSE;
15164 player->is_dropping_pressed = FALSE;
15165 player->drop_pressed_delay = 0;
15167 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15170 player->is_snapping = TRUE;
15171 player->is_active = TRUE;
15173 if (player->MovPos == 0)
15175 player->is_moving = FALSE;
15176 player->is_digging = FALSE;
15177 player->is_collecting = FALSE;
15180 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15181 TEST_DrawLevelField(player->last_jx, player->last_jy);
15183 TEST_DrawLevelField(x, y);
15188 static boolean DropElement(struct PlayerInfo *player)
15190 int old_element, new_element;
15191 int dropx = player->jx, dropy = player->jy;
15192 int drop_direction = player->MovDir;
15193 int drop_side = drop_direction;
15194 int drop_element = get_next_dropped_element(player);
15196 /* do not drop an element on top of another element; when holding drop key
15197 pressed without moving, dropped element must move away before the next
15198 element can be dropped (this is especially important if the next element
15199 is dynamite, which can be placed on background for historical reasons) */
15200 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15203 if (IS_THROWABLE(drop_element))
15205 dropx += GET_DX_FROM_DIR(drop_direction);
15206 dropy += GET_DY_FROM_DIR(drop_direction);
15208 if (!IN_LEV_FIELD(dropx, dropy))
15212 old_element = Tile[dropx][dropy]; // old element at dropping position
15213 new_element = drop_element; // default: no change when dropping
15215 // check if player is active, not moving and ready to drop
15216 if (!player->active || player->MovPos || player->drop_delay > 0)
15219 // check if player has anything that can be dropped
15220 if (new_element == EL_UNDEFINED)
15223 // only set if player has anything that can be dropped
15224 player->is_dropping_pressed = TRUE;
15226 // check if drop key was pressed long enough for EM style dynamite
15227 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15230 // check if anything can be dropped at the current position
15231 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15234 // collected custom elements can only be dropped on empty fields
15235 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15238 if (old_element != EL_EMPTY)
15239 Back[dropx][dropy] = old_element; // store old element on this field
15241 ResetGfxAnimation(dropx, dropy);
15242 ResetRandomAnimationValue(dropx, dropy);
15244 if (player->inventory_size > 0 ||
15245 player->inventory_infinite_element != EL_UNDEFINED)
15247 if (player->inventory_size > 0)
15249 player->inventory_size--;
15251 DrawGameDoorValues();
15253 if (new_element == EL_DYNAMITE)
15254 new_element = EL_DYNAMITE_ACTIVE;
15255 else if (new_element == EL_EM_DYNAMITE)
15256 new_element = EL_EM_DYNAMITE_ACTIVE;
15257 else if (new_element == EL_SP_DISK_RED)
15258 new_element = EL_SP_DISK_RED_ACTIVE;
15261 Tile[dropx][dropy] = new_element;
15263 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15264 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15265 el2img(Tile[dropx][dropy]), 0);
15267 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15269 // needed if previous element just changed to "empty" in the last frame
15270 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15272 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15273 player->index_bit, drop_side);
15274 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15276 player->index_bit, drop_side);
15278 TestIfElementTouchesCustomElement(dropx, dropy);
15280 else // player is dropping a dyna bomb
15282 player->dynabombs_left--;
15284 Tile[dropx][dropy] = new_element;
15286 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15287 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15288 el2img(Tile[dropx][dropy]), 0);
15290 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15293 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15294 InitField_WithBug1(dropx, dropy, FALSE);
15296 new_element = Tile[dropx][dropy]; // element might have changed
15298 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15299 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15301 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15302 MovDir[dropx][dropy] = drop_direction;
15304 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15306 // do not cause impact style collision by dropping elements that can fall
15307 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15310 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15311 player->is_dropping = TRUE;
15313 player->drop_pressed_delay = 0;
15314 player->is_dropping_pressed = FALSE;
15316 player->drop_x = dropx;
15317 player->drop_y = dropy;
15322 // ----------------------------------------------------------------------------
15323 // game sound playing functions
15324 // ----------------------------------------------------------------------------
15326 static int *loop_sound_frame = NULL;
15327 static int *loop_sound_volume = NULL;
15329 void InitPlayLevelSound(void)
15331 int num_sounds = getSoundListSize();
15333 checked_free(loop_sound_frame);
15334 checked_free(loop_sound_volume);
15336 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15337 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15340 static void PlayLevelSoundExt(int x, int y, int nr, boolean is_loop_sound)
15342 int sx = SCREENX(x), sy = SCREENY(y);
15343 int volume, stereo_position;
15344 int max_distance = 8;
15345 int type = (is_loop_sound ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15347 if ((!setup.sound_simple && !is_loop_sound) ||
15348 (!setup.sound_loops && is_loop_sound))
15351 if (!IN_LEV_FIELD(x, y) ||
15352 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15353 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15356 volume = SOUND_MAX_VOLUME;
15358 if (!IN_SCR_FIELD(sx, sy))
15360 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15361 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15363 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15366 stereo_position = (SOUND_MAX_LEFT +
15367 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15368 (SCR_FIELDX + 2 * max_distance));
15372 /* This assures that quieter loop sounds do not overwrite louder ones,
15373 while restarting sound volume comparison with each new game frame. */
15375 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15378 loop_sound_volume[nr] = volume;
15379 loop_sound_frame[nr] = FrameCounter;
15382 PlaySoundExt(nr, volume, stereo_position, type);
15385 static void PlayLevelSound(int x, int y, int nr)
15387 PlayLevelSoundExt(x, y, nr, IS_LOOP_SOUND(nr));
15390 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15392 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15393 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15394 y < LEVELY(BY1) ? LEVELY(BY1) :
15395 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15399 static void PlayLevelSoundAction(int x, int y, int action)
15401 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15404 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15406 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15408 if (sound_effect != SND_UNDEFINED)
15409 PlayLevelSound(x, y, sound_effect);
15412 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15415 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15417 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15418 PlayLevelSound(x, y, sound_effect);
15421 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15423 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15425 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15426 PlayLevelSound(x, y, sound_effect);
15429 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15431 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15433 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15434 StopSound(sound_effect);
15437 static int getLevelMusicNr(void)
15439 int level_pos = level_nr - leveldir_current->first_level;
15441 if (levelset.music[level_nr] != MUS_UNDEFINED)
15442 return levelset.music[level_nr]; // from config file
15444 return MAP_NOCONF_MUSIC(level_pos); // from music dir
15447 static void FadeLevelSounds(void)
15452 static void FadeLevelMusic(void)
15454 int music_nr = getLevelMusicNr();
15455 char *curr_music = getCurrentlyPlayingMusicFilename();
15456 char *next_music = getMusicInfoEntryFilename(music_nr);
15458 if (!strEqual(curr_music, next_music))
15462 void FadeLevelSoundsAndMusic(void)
15468 static void PlayLevelMusic(void)
15470 int music_nr = getLevelMusicNr();
15471 char *curr_music = getCurrentlyPlayingMusicFilename();
15472 char *next_music = getMusicInfoEntryFilename(music_nr);
15474 if (!strEqual(curr_music, next_music))
15475 PlayMusicLoop(music_nr);
15478 static int getSoundAction_BD(int sample)
15484 case GD_S_DIRT_BALL:
15486 case GD_S_FALLING_WALL:
15487 return ACTION_IMPACT;
15489 case GD_S_NUT_CRACK:
15490 return ACTION_BREAKING;
15492 case GD_S_EXPANDING_WALL:
15493 case GD_S_WALL_REAPPEAR:
15496 case GD_S_ACID_SPREAD:
15497 return ACTION_GROWING;
15499 case GD_S_DIAMOND_COLLECT:
15500 case GD_S_SKELETON_COLLECT:
15501 case GD_S_PNEUMATIC_COLLECT:
15502 case GD_S_BOMB_COLLECT:
15503 case GD_S_CLOCK_COLLECT:
15504 case GD_S_SWEET_COLLECT:
15505 case GD_S_KEY_COLLECT:
15506 case GD_S_DIAMOND_KEY_COLLECT:
15507 return ACTION_COLLECTING;
15509 case GD_S_BOMB_PLACE:
15510 case GD_S_REPLICATOR:
15511 return ACTION_DROPPING;
15513 case GD_S_BLADDER_MOVE:
15514 return ACTION_MOVING;
15516 case GD_S_BLADDER_SPENDER:
15517 case GD_S_BLADDER_CONVERT:
15518 case GD_S_GRAVITY_CHANGE:
15519 return ACTION_CHANGING;
15521 case GD_S_BITER_EAT:
15522 return ACTION_EATING;
15524 case GD_S_DOOR_OPEN:
15526 return ACTION_OPENING;
15528 case GD_S_WALK_EARTH:
15529 return ACTION_DIGGING;
15531 case GD_S_WALK_EMPTY:
15532 return ACTION_WALKING;
15534 case GD_S_SWITCH_BITER:
15535 case GD_S_SWITCH_CREATURES:
15536 case GD_S_SWITCH_GRAVITY:
15537 case GD_S_SWITCH_EXPANDING:
15538 case GD_S_SWITCH_CONVEYOR:
15539 case GD_S_SWITCH_REPLICATOR:
15540 case GD_S_STIRRING:
15541 return ACTION_ACTIVATING;
15543 case GD_S_BOX_PUSH:
15544 return ACTION_PUSHING;
15546 case GD_S_TELEPORTER:
15547 return ACTION_PASSING;
15549 case GD_S_EXPLOSION:
15550 case GD_S_BOMB_EXPLOSION:
15551 case GD_S_GHOST_EXPLOSION:
15552 case GD_S_VOODOO_EXPLOSION:
15553 case GD_S_NITRO_EXPLOSION:
15554 return ACTION_EXPLODING;
15558 case GD_S_AMOEBA_MAGIC:
15559 case GD_S_MAGIC_WALL:
15560 case GD_S_PNEUMATIC_HAMMER:
15562 return ACTION_ACTIVE;
15564 case GD_S_DIAMOND_RANDOM:
15565 case GD_S_DIAMOND_1:
15566 case GD_S_DIAMOND_2:
15567 case GD_S_DIAMOND_3:
15568 case GD_S_DIAMOND_4:
15569 case GD_S_DIAMOND_5:
15570 case GD_S_DIAMOND_6:
15571 case GD_S_DIAMOND_7:
15572 case GD_S_DIAMOND_8:
15573 case GD_S_TIMEOUT_0:
15574 case GD_S_TIMEOUT_1:
15575 case GD_S_TIMEOUT_2:
15576 case GD_S_TIMEOUT_3:
15577 case GD_S_TIMEOUT_4:
15578 case GD_S_TIMEOUT_5:
15579 case GD_S_TIMEOUT_6:
15580 case GD_S_TIMEOUT_7:
15581 case GD_S_TIMEOUT_8:
15582 case GD_S_TIMEOUT_9:
15583 case GD_S_TIMEOUT_10:
15584 case GD_S_BONUS_LIFE:
15585 // kludge to prevent playing as loop sound
15586 return ACTION_OTHER;
15588 case GD_S_FINISHED:
15589 return ACTION_DEFAULT;
15592 return ACTION_DEFAULT;
15596 static int getSoundEffect_BD(int element_bd, int sample)
15598 int sound_action = getSoundAction_BD(sample);
15599 int sound_effect = element_info[SND_ELEMENT(element_bd)].sound[sound_action];
15603 if (sound_action != ACTION_OTHER &&
15604 sound_action != ACTION_DEFAULT)
15605 return sound_effect;
15610 case GD_S_DIAMOND_RANDOM:
15611 nr = GetSimpleRandom(8);
15612 sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr;
15615 case GD_S_DIAMOND_1:
15616 case GD_S_DIAMOND_2:
15617 case GD_S_DIAMOND_3:
15618 case GD_S_DIAMOND_4:
15619 case GD_S_DIAMOND_5:
15620 case GD_S_DIAMOND_6:
15621 case GD_S_DIAMOND_7:
15622 case GD_S_DIAMOND_8:
15623 nr = sample - GD_S_DIAMOND_1;
15624 sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr;
15627 case GD_S_TIMEOUT_0:
15628 case GD_S_TIMEOUT_1:
15629 case GD_S_TIMEOUT_2:
15630 case GD_S_TIMEOUT_3:
15631 case GD_S_TIMEOUT_4:
15632 case GD_S_TIMEOUT_5:
15633 case GD_S_TIMEOUT_6:
15634 case GD_S_TIMEOUT_7:
15635 case GD_S_TIMEOUT_8:
15636 case GD_S_TIMEOUT_9:
15637 case GD_S_TIMEOUT_10:
15638 nr = sample - GD_S_TIMEOUT_0;
15639 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME_0 + nr;
15641 if (getSoundInfoEntryFilename(sound_effect) == NULL && sample != GD_S_TIMEOUT_0)
15642 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME;
15645 case GD_S_FINISHED:
15646 sound_effect = SND_GAME_LEVELTIME_BONUS;
15649 case GD_S_BONUS_LIFE:
15650 sound_effect = SND_GAME_HEALTH_BONUS;
15654 sound_effect = SND_UNDEFINED;
15658 return sound_effect;
15661 void PlayLevelSound_BD(int xx, int yy, int element_bd, int sample)
15663 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15664 int sound_effect = getSoundEffect_BD(element, sample);
15665 int sound_action = getSoundAction_BD(sample);
15666 boolean is_loop_sound = IS_LOOP_SOUND(sound_effect);
15668 int x = xx - offset;
15669 int y = yy - offset;
15671 if (sound_action == ACTION_OTHER)
15672 is_loop_sound = FALSE;
15674 if (sound_effect != SND_UNDEFINED)
15675 PlayLevelSoundExt(x, y, sound_effect, is_loop_sound);
15678 void StopSound_BD(int element_bd, int sample)
15680 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15681 int sound_effect = getSoundEffect_BD(element, sample);
15683 if (sound_effect != SND_UNDEFINED)
15684 StopSound(sound_effect);
15687 boolean isSoundPlaying_BD(int element_bd, int sample)
15689 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15690 int sound_effect = getSoundEffect_BD(element, sample);
15692 if (sound_effect != SND_UNDEFINED)
15693 return isSoundPlaying(sound_effect);
15698 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15700 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15702 int x = xx - offset;
15703 int y = yy - offset;
15708 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15712 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15716 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15720 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15724 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15728 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15732 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15735 case SOUND_android_clone:
15736 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15739 case SOUND_android_move:
15740 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15744 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15748 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15752 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15755 case SOUND_eater_eat:
15756 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15760 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15763 case SOUND_collect:
15764 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15767 case SOUND_diamond:
15768 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15772 // !!! CHECK THIS !!!
15774 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15776 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15780 case SOUND_wonderfall:
15781 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15785 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15789 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15793 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15797 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15801 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15805 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15809 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15813 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15816 case SOUND_exit_open:
15817 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15820 case SOUND_exit_leave:
15821 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15824 case SOUND_dynamite:
15825 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15829 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15833 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15837 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15841 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15845 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15849 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15853 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15858 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15860 int element = map_element_SP_to_RND(element_sp);
15861 int action = map_action_SP_to_RND(action_sp);
15862 int offset = (setup.sp_show_border_elements ? 0 : 1);
15863 int x = xx - offset;
15864 int y = yy - offset;
15866 PlayLevelSoundElementAction(x, y, element, action);
15869 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15871 int element = map_element_MM_to_RND(element_mm);
15872 int action = map_action_MM_to_RND(action_mm);
15874 int x = xx - offset;
15875 int y = yy - offset;
15877 if (!IS_MM_ELEMENT(element))
15878 element = EL_MM_DEFAULT;
15880 PlayLevelSoundElementAction(x, y, element, action);
15883 void PlaySound_MM(int sound_mm)
15885 int sound = map_sound_MM_to_RND(sound_mm);
15887 if (sound == SND_UNDEFINED)
15893 void PlaySoundLoop_MM(int sound_mm)
15895 int sound = map_sound_MM_to_RND(sound_mm);
15897 if (sound == SND_UNDEFINED)
15900 PlaySoundLoop(sound);
15903 void StopSound_MM(int sound_mm)
15905 int sound = map_sound_MM_to_RND(sound_mm);
15907 if (sound == SND_UNDEFINED)
15913 void RaiseScore(int value)
15915 game.score += value;
15917 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15919 DisplayGameControlValues();
15922 void RaiseScoreElement(int element)
15927 case EL_BD_DIAMOND:
15928 case EL_EMERALD_YELLOW:
15929 case EL_EMERALD_RED:
15930 case EL_EMERALD_PURPLE:
15931 case EL_SP_INFOTRON:
15932 RaiseScore(level.score[SC_EMERALD]);
15935 RaiseScore(level.score[SC_DIAMOND]);
15938 RaiseScore(level.score[SC_CRYSTAL]);
15941 RaiseScore(level.score[SC_PEARL]);
15944 case EL_BD_BUTTERFLY:
15945 case EL_SP_ELECTRON:
15946 RaiseScore(level.score[SC_BUG]);
15949 case EL_BD_FIREFLY:
15950 case EL_SP_SNIKSNAK:
15951 RaiseScore(level.score[SC_SPACESHIP]);
15954 case EL_DARK_YAMYAM:
15955 RaiseScore(level.score[SC_YAMYAM]);
15958 RaiseScore(level.score[SC_ROBOT]);
15961 RaiseScore(level.score[SC_PACMAN]);
15964 RaiseScore(level.score[SC_NUT]);
15967 case EL_EM_DYNAMITE:
15968 case EL_SP_DISK_RED:
15969 case EL_DYNABOMB_INCREASE_NUMBER:
15970 case EL_DYNABOMB_INCREASE_SIZE:
15971 case EL_DYNABOMB_INCREASE_POWER:
15972 RaiseScore(level.score[SC_DYNAMITE]);
15974 case EL_SHIELD_NORMAL:
15975 case EL_SHIELD_DEADLY:
15976 RaiseScore(level.score[SC_SHIELD]);
15978 case EL_EXTRA_TIME:
15979 RaiseScore(level.extra_time_score);
15993 case EL_DC_KEY_WHITE:
15994 RaiseScore(level.score[SC_KEY]);
15997 RaiseScore(element_info[element].collect_score);
16002 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
16004 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
16008 // prevent short reactivation of overlay buttons while closing door
16009 SetOverlayActive(FALSE);
16010 UnmapGameButtons();
16012 // door may still be open due to skipped or envelope style request
16013 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
16016 if (network.enabled)
16018 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
16023 FadeSkipNextFadeIn();
16025 SetGameStatus(GAME_MODE_MAIN);
16030 else // continue playing the game
16032 if (tape.playing && tape.deactivate_display)
16033 TapeDeactivateDisplayOff(TRUE);
16035 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
16037 if (tape.playing && tape.deactivate_display)
16038 TapeDeactivateDisplayOn();
16042 void RequestQuitGame(boolean escape_key_pressed)
16044 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
16045 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
16046 level_editor_test_game);
16047 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
16048 quick_quit || score_info_tape_play);
16050 RequestQuitGameExt(skip_request, quick_quit,
16051 "Do you really want to quit the game?");
16054 static char *getRestartGameMessage(void)
16056 boolean play_again = hasStartedNetworkGame();
16057 static char message[MAX_OUTPUT_LINESIZE];
16058 char *game_over_text = "Game over!";
16059 char *play_again_text = " Play it again?";
16061 if (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
16062 game_mm.game_over_message != NULL)
16063 game_over_text = game_mm.game_over_message;
16065 snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text,
16066 (play_again ? play_again_text : ""));
16071 static void RequestRestartGame(void)
16073 char *message = getRestartGameMessage();
16074 boolean has_started_game = hasStartedNetworkGame();
16075 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
16076 int door_state = DOOR_CLOSE_1;
16078 if (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game)
16080 CloseDoor(door_state);
16082 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16086 // if game was invoked from level editor, also close tape recorder door
16087 if (level_editor_test_game)
16088 door_state = DOOR_CLOSE_ALL;
16090 CloseDoor(door_state);
16092 SetGameStatus(GAME_MODE_MAIN);
16098 boolean CheckRestartGame(void)
16100 static int game_over_delay = 0;
16101 int game_over_delay_value = 50;
16102 boolean game_over = checkGameFailed();
16106 game_over_delay = game_over_delay_value;
16111 if (game_over_delay > 0)
16113 if (game_over_delay == game_over_delay_value / 2)
16114 PlaySound(SND_GAME_LOSING);
16121 // do not ask to play again if request dialog is already active
16122 if (game.request_active)
16125 // do not ask to play again if request dialog already handled
16126 if (game.RestartGameRequested)
16129 // do not ask to play again if game was never actually played
16130 if (!game.GamePlayed)
16133 // do not ask to play again if this was disabled in setup menu
16134 if (!setup.ask_on_game_over)
16137 game.RestartGameRequested = TRUE;
16139 RequestRestartGame();
16144 boolean checkGameRunning(void)
16146 if (game_status != GAME_MODE_PLAYING)
16149 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGameRunning_BD())
16155 boolean checkGameSolved(void)
16157 // set for all game engines if level was solved
16158 return game.LevelSolved_GameEnd;
16161 boolean checkGameFailed(void)
16163 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
16164 return (game_bd.game_over && !game_bd.level_solved);
16165 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16166 return (game_em.game_over && !game_em.level_solved);
16167 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16168 return (game_sp.game_over && !game_sp.level_solved);
16169 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16170 return (game_mm.game_over && !game_mm.level_solved);
16171 else // GAME_ENGINE_TYPE_RND
16172 return (game.GameOver && !game.LevelSolved);
16175 boolean checkGameEnded(void)
16177 return (checkGameSolved() || checkGameFailed());
16181 // ----------------------------------------------------------------------------
16182 // random generator functions
16183 // ----------------------------------------------------------------------------
16185 unsigned int InitEngineRandom_RND(int seed)
16187 game.num_random_calls = 0;
16189 return InitEngineRandom(seed);
16192 unsigned int RND(int max)
16196 game.num_random_calls++;
16198 return GetEngineRandom(max);
16205 // ----------------------------------------------------------------------------
16206 // game engine snapshot handling functions
16207 // ----------------------------------------------------------------------------
16209 struct EngineSnapshotInfo
16211 // runtime values for custom element collect score
16212 int collect_score[NUM_CUSTOM_ELEMENTS];
16214 // runtime values for group element choice position
16215 int choice_pos[NUM_GROUP_ELEMENTS];
16217 // runtime values for belt position animations
16218 int belt_graphic[4][NUM_BELT_PARTS];
16219 int belt_anim_mode[4][NUM_BELT_PARTS];
16222 static struct EngineSnapshotInfo engine_snapshot_rnd;
16223 static char *snapshot_level_identifier = NULL;
16224 static int snapshot_level_nr = -1;
16226 static void SaveEngineSnapshotValues_RND(void)
16228 static int belt_base_active_element[4] =
16230 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
16231 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
16232 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
16233 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
16237 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16239 int element = EL_CUSTOM_START + i;
16241 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
16244 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16246 int element = EL_GROUP_START + i;
16248 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
16251 for (i = 0; i < 4; i++)
16253 for (j = 0; j < NUM_BELT_PARTS; j++)
16255 int element = belt_base_active_element[i] + j;
16256 int graphic = el2img(element);
16257 int anim_mode = graphic_info[graphic].anim_mode;
16259 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
16260 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
16265 static void LoadEngineSnapshotValues_RND(void)
16267 unsigned int num_random_calls = game.num_random_calls;
16270 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16272 int element = EL_CUSTOM_START + i;
16274 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
16277 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16279 int element = EL_GROUP_START + i;
16281 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
16284 for (i = 0; i < 4; i++)
16286 for (j = 0; j < NUM_BELT_PARTS; j++)
16288 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
16289 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
16291 graphic_info[graphic].anim_mode = anim_mode;
16295 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16297 InitRND(tape.random_seed);
16298 for (i = 0; i < num_random_calls; i++)
16302 if (game.num_random_calls != num_random_calls)
16304 Error("number of random calls out of sync");
16305 Error("number of random calls should be %d", num_random_calls);
16306 Error("number of random calls is %d", game.num_random_calls);
16308 Fail("this should not happen -- please debug");
16312 void FreeEngineSnapshotSingle(void)
16314 FreeSnapshotSingle();
16316 setString(&snapshot_level_identifier, NULL);
16317 snapshot_level_nr = -1;
16320 void FreeEngineSnapshotList(void)
16322 FreeSnapshotList();
16325 static ListNode *SaveEngineSnapshotBuffers(void)
16327 ListNode *buffers = NULL;
16329 // copy some special values to a structure better suited for the snapshot
16331 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16332 SaveEngineSnapshotValues_RND();
16333 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16334 SaveEngineSnapshotValues_EM();
16335 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16336 SaveEngineSnapshotValues_SP(&buffers);
16337 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16338 SaveEngineSnapshotValues_MM();
16340 // save values stored in special snapshot structure
16342 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16343 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
16344 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16345 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
16346 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16347 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
16348 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16349 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
16351 // save further RND engine values
16353 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
16354 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
16355 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
16357 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
16358 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
16359 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
16360 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16361 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTimeFrames));
16362 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16364 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16365 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16366 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16368 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16370 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16371 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16373 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16374 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16375 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16376 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16377 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16378 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16379 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16380 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16381 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16382 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16383 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16384 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16385 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16386 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16387 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16388 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16389 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16390 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16392 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16393 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16395 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16396 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16397 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16399 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16400 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16402 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16403 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16404 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16405 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16406 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16407 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16409 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16410 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16413 ListNode *node = engine_snapshot_list_rnd;
16416 while (node != NULL)
16418 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16423 Debug("game:playing:SaveEngineSnapshotBuffers",
16424 "size of engine snapshot: %d bytes", num_bytes);
16430 void SaveEngineSnapshotSingle(void)
16432 ListNode *buffers = SaveEngineSnapshotBuffers();
16434 // finally save all snapshot buffers to single snapshot
16435 SaveSnapshotSingle(buffers);
16437 // save level identification information
16438 setString(&snapshot_level_identifier, leveldir_current->identifier);
16439 snapshot_level_nr = level_nr;
16442 boolean CheckSaveEngineSnapshotToList(void)
16444 boolean save_snapshot =
16445 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16446 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16447 game.snapshot.changed_action) ||
16448 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16449 game.snapshot.collected_item));
16451 game.snapshot.changed_action = FALSE;
16452 game.snapshot.collected_item = FALSE;
16453 game.snapshot.save_snapshot = save_snapshot;
16455 return save_snapshot;
16458 void SaveEngineSnapshotToList(void)
16460 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16464 ListNode *buffers = SaveEngineSnapshotBuffers();
16466 // finally save all snapshot buffers to snapshot list
16467 SaveSnapshotToList(buffers);
16470 void SaveEngineSnapshotToListInitial(void)
16472 FreeEngineSnapshotList();
16474 SaveEngineSnapshotToList();
16477 static void LoadEngineSnapshotValues(void)
16479 // restore special values from snapshot structure
16481 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16482 LoadEngineSnapshotValues_RND();
16483 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16484 LoadEngineSnapshotValues_EM();
16485 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16486 LoadEngineSnapshotValues_SP();
16487 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16488 LoadEngineSnapshotValues_MM();
16491 void LoadEngineSnapshotSingle(void)
16493 LoadSnapshotSingle();
16495 LoadEngineSnapshotValues();
16498 static void LoadEngineSnapshot_Undo(int steps)
16500 LoadSnapshotFromList_Older(steps);
16502 LoadEngineSnapshotValues();
16505 static void LoadEngineSnapshot_Redo(int steps)
16507 LoadSnapshotFromList_Newer(steps);
16509 LoadEngineSnapshotValues();
16512 boolean CheckEngineSnapshotSingle(void)
16514 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16515 snapshot_level_nr == level_nr);
16518 boolean CheckEngineSnapshotList(void)
16520 return CheckSnapshotList();
16524 // ---------- new game button stuff -------------------------------------------
16531 boolean *setup_value;
16532 boolean allowed_on_tape;
16533 boolean is_touch_button;
16535 } gamebutton_info[NUM_GAME_BUTTONS] =
16538 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16539 GAME_CTRL_ID_STOP, NULL,
16540 TRUE, FALSE, "stop game"
16543 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16544 GAME_CTRL_ID_PAUSE, NULL,
16545 TRUE, FALSE, "pause game"
16548 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16549 GAME_CTRL_ID_PLAY, NULL,
16550 TRUE, FALSE, "play game"
16553 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16554 GAME_CTRL_ID_UNDO, NULL,
16555 TRUE, FALSE, "undo step"
16558 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16559 GAME_CTRL_ID_REDO, NULL,
16560 TRUE, FALSE, "redo step"
16563 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16564 GAME_CTRL_ID_SAVE, NULL,
16565 TRUE, FALSE, "save game"
16568 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16569 GAME_CTRL_ID_PAUSE2, NULL,
16570 TRUE, FALSE, "pause game"
16573 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16574 GAME_CTRL_ID_LOAD, NULL,
16575 TRUE, FALSE, "load game"
16578 IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart,
16579 GAME_CTRL_ID_RESTART, NULL,
16580 TRUE, FALSE, "restart game"
16583 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16584 GAME_CTRL_ID_PANEL_STOP, NULL,
16585 FALSE, FALSE, "stop game"
16588 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16589 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16590 FALSE, FALSE, "pause game"
16593 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16594 GAME_CTRL_ID_PANEL_PLAY, NULL,
16595 FALSE, FALSE, "play game"
16598 IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart,
16599 GAME_CTRL_ID_PANEL_RESTART, NULL,
16600 FALSE, FALSE, "restart game"
16603 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16604 GAME_CTRL_ID_TOUCH_STOP, NULL,
16605 FALSE, TRUE, "stop game"
16608 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16609 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16610 FALSE, TRUE, "pause game"
16613 IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart,
16614 GAME_CTRL_ID_TOUCH_RESTART, NULL,
16615 FALSE, TRUE, "restart game"
16618 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16619 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16620 TRUE, FALSE, "background music on/off"
16623 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16624 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16625 TRUE, FALSE, "sound loops on/off"
16628 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16629 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16630 TRUE, FALSE, "normal sounds on/off"
16633 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16634 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16635 FALSE, FALSE, "background music on/off"
16638 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16639 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16640 FALSE, FALSE, "sound loops on/off"
16643 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16644 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16645 FALSE, FALSE, "normal sounds on/off"
16649 void CreateGameButtons(void)
16653 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16655 int graphic = gamebutton_info[i].graphic;
16656 struct GraphicInfo *gfx = &graphic_info[graphic];
16657 struct XY *pos = gamebutton_info[i].pos;
16658 struct GadgetInfo *gi;
16661 unsigned int event_mask;
16662 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16663 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16664 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16665 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16666 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16667 int gd_x = gfx->src_x;
16668 int gd_y = gfx->src_y;
16669 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16670 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16671 int gd_xa = gfx->src_x + gfx->active_xoffset;
16672 int gd_ya = gfx->src_y + gfx->active_yoffset;
16673 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16674 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16675 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16676 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16679 // do not use touch buttons if overlay touch buttons are disabled
16680 if (is_touch_button && !setup.touch.overlay_buttons)
16683 if (gfx->bitmap == NULL)
16685 game_gadget[id] = NULL;
16690 if (id == GAME_CTRL_ID_STOP ||
16691 id == GAME_CTRL_ID_PANEL_STOP ||
16692 id == GAME_CTRL_ID_TOUCH_STOP ||
16693 id == GAME_CTRL_ID_PLAY ||
16694 id == GAME_CTRL_ID_PANEL_PLAY ||
16695 id == GAME_CTRL_ID_SAVE ||
16696 id == GAME_CTRL_ID_LOAD ||
16697 id == GAME_CTRL_ID_RESTART ||
16698 id == GAME_CTRL_ID_PANEL_RESTART ||
16699 id == GAME_CTRL_ID_TOUCH_RESTART)
16701 button_type = GD_TYPE_NORMAL_BUTTON;
16703 event_mask = GD_EVENT_RELEASED;
16705 else if (id == GAME_CTRL_ID_UNDO ||
16706 id == GAME_CTRL_ID_REDO)
16708 button_type = GD_TYPE_NORMAL_BUTTON;
16710 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16714 button_type = GD_TYPE_CHECK_BUTTON;
16715 checked = (gamebutton_info[i].setup_value != NULL ?
16716 *gamebutton_info[i].setup_value : FALSE);
16717 event_mask = GD_EVENT_PRESSED;
16720 gi = CreateGadget(GDI_CUSTOM_ID, id,
16721 GDI_IMAGE_ID, graphic,
16722 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16725 GDI_WIDTH, gfx->width,
16726 GDI_HEIGHT, gfx->height,
16727 GDI_TYPE, button_type,
16728 GDI_STATE, GD_BUTTON_UNPRESSED,
16729 GDI_CHECKED, checked,
16730 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16731 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16732 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16733 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16734 GDI_DIRECT_DRAW, FALSE,
16735 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16736 GDI_EVENT_MASK, event_mask,
16737 GDI_CALLBACK_ACTION, HandleGameButtons,
16741 Fail("cannot create gadget");
16743 game_gadget[id] = gi;
16747 void FreeGameButtons(void)
16751 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16752 FreeGadget(game_gadget[i]);
16755 static void UnmapGameButtonsAtSamePosition(int id)
16759 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16761 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16762 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16763 UnmapGadget(game_gadget[i]);
16766 static void UnmapGameButtonsAtSamePosition_All(void)
16768 if (setup.show_load_save_buttons)
16770 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16771 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16772 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16774 else if (setup.show_undo_redo_buttons)
16776 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16777 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16778 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16782 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16783 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16784 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16786 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16787 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16788 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16792 void MapLoadSaveButtons(void)
16794 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16795 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16797 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16798 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16801 void MapUndoRedoButtons(void)
16803 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16804 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16806 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16807 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16810 void ModifyPauseButtons(void)
16814 GAME_CTRL_ID_PAUSE,
16815 GAME_CTRL_ID_PAUSE2,
16816 GAME_CTRL_ID_PANEL_PAUSE,
16817 GAME_CTRL_ID_TOUCH_PAUSE,
16822 // do not redraw pause button on closed door (may happen when restarting game)
16823 if (!(GetDoorState() & DOOR_OPEN_1))
16826 for (i = 0; ids[i] > -1; i++)
16827 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16830 static void MapGameButtonsExt(boolean on_tape)
16834 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16836 if ((i == GAME_CTRL_ID_UNDO ||
16837 i == GAME_CTRL_ID_REDO) &&
16838 game_status != GAME_MODE_PLAYING)
16841 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16842 MapGadget(game_gadget[i]);
16845 UnmapGameButtonsAtSamePosition_All();
16847 RedrawGameButtons();
16850 static void UnmapGameButtonsExt(boolean on_tape)
16854 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16855 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16856 UnmapGadget(game_gadget[i]);
16859 static void RedrawGameButtonsExt(boolean on_tape)
16863 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16864 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16865 RedrawGadget(game_gadget[i]);
16868 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16873 gi->checked = state;
16876 static void RedrawSoundButtonGadget(int id)
16878 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16879 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16880 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16881 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16882 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16883 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16886 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16887 RedrawGadget(game_gadget[id2]);
16890 void MapGameButtons(void)
16892 MapGameButtonsExt(FALSE);
16895 void UnmapGameButtons(void)
16897 UnmapGameButtonsExt(FALSE);
16900 void RedrawGameButtons(void)
16902 RedrawGameButtonsExt(FALSE);
16905 void MapGameButtonsOnTape(void)
16907 MapGameButtonsExt(TRUE);
16910 void UnmapGameButtonsOnTape(void)
16912 UnmapGameButtonsExt(TRUE);
16915 void RedrawGameButtonsOnTape(void)
16917 RedrawGameButtonsExt(TRUE);
16920 static void GameUndoRedoExt(void)
16922 ClearPlayerAction();
16924 tape.pausing = TRUE;
16927 UpdateAndDisplayGameControlValues();
16929 DrawCompleteVideoDisplay();
16930 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16931 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16932 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16934 ModifyPauseButtons();
16939 static void GameUndo(int steps)
16941 if (!CheckEngineSnapshotList())
16944 int tape_property_bits = tape.property_bits;
16946 LoadEngineSnapshot_Undo(steps);
16948 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16953 static void GameRedo(int steps)
16955 if (!CheckEngineSnapshotList())
16958 int tape_property_bits = tape.property_bits;
16960 LoadEngineSnapshot_Redo(steps);
16962 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16967 static void HandleGameButtonsExt(int id, int button)
16969 static boolean game_undo_executed = FALSE;
16970 int steps = BUTTON_STEPSIZE(button);
16971 boolean handle_game_buttons =
16972 (game_status == GAME_MODE_PLAYING ||
16973 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16975 if (!handle_game_buttons)
16980 case GAME_CTRL_ID_STOP:
16981 case GAME_CTRL_ID_PANEL_STOP:
16982 case GAME_CTRL_ID_TOUCH_STOP:
16987 case GAME_CTRL_ID_PAUSE:
16988 case GAME_CTRL_ID_PAUSE2:
16989 case GAME_CTRL_ID_PANEL_PAUSE:
16990 case GAME_CTRL_ID_TOUCH_PAUSE:
16991 if (network.enabled && game_status == GAME_MODE_PLAYING)
16994 SendToServer_ContinuePlaying();
16996 SendToServer_PausePlaying();
16999 TapeTogglePause(TAPE_TOGGLE_MANUAL);
17001 game_undo_executed = FALSE;
17005 case GAME_CTRL_ID_PLAY:
17006 case GAME_CTRL_ID_PANEL_PLAY:
17007 if (game_status == GAME_MODE_MAIN)
17009 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
17011 else if (tape.pausing)
17013 if (network.enabled)
17014 SendToServer_ContinuePlaying();
17016 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
17020 case GAME_CTRL_ID_UNDO:
17021 // Important: When using "save snapshot when collecting an item" mode,
17022 // load last (current) snapshot for first "undo" after pressing "pause"
17023 // (else the last-but-one snapshot would be loaded, because the snapshot
17024 // pointer already points to the last snapshot when pressing "pause",
17025 // which is fine for "every step/move" mode, but not for "every collect")
17026 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
17027 !game_undo_executed)
17030 game_undo_executed = TRUE;
17035 case GAME_CTRL_ID_REDO:
17039 case GAME_CTRL_ID_SAVE:
17043 case GAME_CTRL_ID_LOAD:
17047 case GAME_CTRL_ID_RESTART:
17048 case GAME_CTRL_ID_PANEL_RESTART:
17049 case GAME_CTRL_ID_TOUCH_RESTART:
17054 case SOUND_CTRL_ID_MUSIC:
17055 case SOUND_CTRL_ID_PANEL_MUSIC:
17056 if (setup.sound_music)
17058 setup.sound_music = FALSE;
17062 else if (audio.music_available)
17064 setup.sound = setup.sound_music = TRUE;
17066 SetAudioMode(setup.sound);
17068 if (game_status == GAME_MODE_PLAYING)
17072 RedrawSoundButtonGadget(id);
17076 case SOUND_CTRL_ID_LOOPS:
17077 case SOUND_CTRL_ID_PANEL_LOOPS:
17078 if (setup.sound_loops)
17079 setup.sound_loops = FALSE;
17080 else if (audio.loops_available)
17082 setup.sound = setup.sound_loops = TRUE;
17084 SetAudioMode(setup.sound);
17087 RedrawSoundButtonGadget(id);
17091 case SOUND_CTRL_ID_SIMPLE:
17092 case SOUND_CTRL_ID_PANEL_SIMPLE:
17093 if (setup.sound_simple)
17094 setup.sound_simple = FALSE;
17095 else if (audio.sound_available)
17097 setup.sound = setup.sound_simple = TRUE;
17099 SetAudioMode(setup.sound);
17102 RedrawSoundButtonGadget(id);
17111 static void HandleGameButtons(struct GadgetInfo *gi)
17113 HandleGameButtonsExt(gi->custom_id, gi->event.button);
17116 void HandleSoundButtonKeys(Key key)
17118 if (key == setup.shortcut.sound_simple)
17119 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
17120 else if (key == setup.shortcut.sound_loops)
17121 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
17122 else if (key == setup.shortcut.sound_music)
17123 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);