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_EM ?
2267 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2268 game_sp.time_played :
2269 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2270 game_mm.energy_left :
2271 game.no_level_time_limit ? TimePlayed : TimeLeft);
2272 int score = (game.LevelSolved ?
2273 game.LevelSolved_CountingScore :
2274 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2275 game_em.lev->score :
2276 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2278 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2281 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2282 game_em.lev->gems_needed :
2283 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2284 game_sp.infotrons_still_needed :
2285 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2286 game_mm.kettles_still_needed :
2287 game.gems_still_needed);
2288 int gems_total = level.gems_needed;
2289 int gems_collected = gems_total - gems;
2290 int gems_score = level.score[SC_EMERALD];
2291 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2292 game_em.lev->gems_needed > 0 :
2293 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2294 game_sp.infotrons_still_needed > 0 :
2295 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2296 game_mm.kettles_still_needed > 0 ||
2297 game_mm.lights_still_needed > 0 :
2298 game.gems_still_needed > 0 ||
2299 game.sokoban_fields_still_needed > 0 ||
2300 game.sokoban_objects_still_needed > 0 ||
2301 game.lights_still_needed > 0);
2302 int health = (game.LevelSolved ?
2303 game.LevelSolved_CountingHealth :
2304 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2305 MM_HEALTH(game_mm.laser_overload_value) :
2307 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2309 UpdatePlayfieldElementCount();
2311 // update game panel control values
2313 // used instead of "level_nr" (for network games)
2314 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2315 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2316 game_panel_controls[GAME_PANEL_GEMS_TOTAL].value = gems_total;
2317 game_panel_controls[GAME_PANEL_GEMS_COLLECTED].value = gems_collected;
2318 game_panel_controls[GAME_PANEL_GEMS_SCORE].value = gems_score;
2320 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2321 for (i = 0; i < MAX_NUM_KEYS; i++)
2322 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2323 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2324 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2326 if (game.centered_player_nr == -1)
2328 for (i = 0; i < MAX_PLAYERS; i++)
2330 // only one player in Supaplex game engine
2331 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2334 for (k = 0; k < MAX_NUM_KEYS; k++)
2336 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2338 if (game_em.ply[i]->keys & (1 << k))
2339 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2340 get_key_element_from_nr(k);
2342 else if (stored_player[i].key[k])
2343 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2344 get_key_element_from_nr(k);
2347 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2348 getPlayerInventorySize(i);
2350 if (stored_player[i].num_white_keys > 0)
2351 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2354 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2355 stored_player[i].num_white_keys;
2360 int player_nr = game.centered_player_nr;
2362 for (k = 0; k < MAX_NUM_KEYS; k++)
2364 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2366 if (game_em.ply[player_nr]->keys & (1 << k))
2367 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2368 get_key_element_from_nr(k);
2370 else if (stored_player[player_nr].key[k])
2371 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2372 get_key_element_from_nr(k);
2375 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2376 getPlayerInventorySize(player_nr);
2378 if (stored_player[player_nr].num_white_keys > 0)
2379 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2381 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2382 stored_player[player_nr].num_white_keys;
2385 // re-arrange keys on game panel, if needed or if defined by style settings
2386 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2388 int nr = GAME_PANEL_KEY_1 + i;
2389 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2390 struct TextPosInfo *pos = gpc->pos;
2392 // skip check if key is not in the player's inventory
2393 if (gpc->value == EL_EMPTY)
2396 // check if keys should be arranged on panel from left to right
2397 if (pos->style == STYLE_LEFTMOST_POSITION)
2399 // check previous key positions (left from current key)
2400 for (k = 0; k < i; k++)
2402 int nr_new = GAME_PANEL_KEY_1 + k;
2404 if (game_panel_controls[nr_new].value == EL_EMPTY)
2406 game_panel_controls[nr_new].value = gpc->value;
2407 gpc->value = EL_EMPTY;
2414 // check if "undefined" keys can be placed at some other position
2415 if (pos->x == -1 && pos->y == -1)
2417 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2419 // 1st try: display key at the same position as normal or EM keys
2420 if (game_panel_controls[nr_new].value == EL_EMPTY)
2422 game_panel_controls[nr_new].value = gpc->value;
2426 // 2nd try: display key at the next free position in the key panel
2427 for (k = 0; k < STD_NUM_KEYS; k++)
2429 nr_new = GAME_PANEL_KEY_1 + k;
2431 if (game_panel_controls[nr_new].value == EL_EMPTY)
2433 game_panel_controls[nr_new].value = gpc->value;
2442 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2444 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2445 get_inventory_element_from_pos(local_player, i);
2446 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2447 get_inventory_element_from_pos(local_player, -i - 1);
2450 game_panel_controls[GAME_PANEL_SCORE].value = score;
2451 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2453 game_panel_controls[GAME_PANEL_TIME].value = time;
2455 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2456 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2457 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2459 if (level.time == 0)
2460 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2462 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2464 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2465 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2467 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2469 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2470 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2472 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2473 local_player->shield_normal_time_left;
2474 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2475 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2477 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2478 local_player->shield_deadly_time_left;
2480 game_panel_controls[GAME_PANEL_EXIT].value =
2481 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2483 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2484 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2485 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2486 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2487 EL_EMC_MAGIC_BALL_SWITCH);
2489 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2490 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2491 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2492 game.light_time_left;
2494 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2495 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2496 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2497 game.timegate_time_left;
2499 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2500 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2502 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2503 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2504 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2505 game.lenses_time_left;
2507 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2508 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2509 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2510 game.magnify_time_left;
2512 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2513 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2514 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2515 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2516 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2517 EL_BALLOON_SWITCH_NONE);
2519 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2520 local_player->dynabomb_count;
2521 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2522 local_player->dynabomb_size;
2523 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2524 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2526 game_panel_controls[GAME_PANEL_PENGUINS].value =
2527 game.friends_still_needed;
2529 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2530 game.sokoban_objects_still_needed;
2531 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2532 game.sokoban_fields_still_needed;
2534 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2535 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2537 for (i = 0; i < NUM_BELTS; i++)
2539 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2540 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2541 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2542 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2543 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2546 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2547 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2548 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2549 game.magic_wall_time_left;
2551 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2552 local_player->gravity;
2554 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2555 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2557 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2558 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2559 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2560 game.panel.element[i].id : EL_UNDEFINED);
2562 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2563 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2564 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2565 element_info[game.panel.element_count[i].id].element_count : 0);
2567 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2568 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2569 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2570 element_info[game.panel.ce_score[i].id].collect_score : 0);
2572 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2573 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2574 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2575 element_info[game.panel.ce_score_element[i].id].collect_score :
2578 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2579 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2580 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2582 // update game panel control frames
2584 for (i = 0; game_panel_controls[i].nr != -1; i++)
2586 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2588 if (gpc->type == TYPE_ELEMENT)
2590 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2592 int last_anim_random_frame = gfx.anim_random_frame;
2593 int element = gpc->value;
2594 int graphic = el2panelimg(element);
2595 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2597 graphic_info[graphic].anim_global_anim_sync ?
2598 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2600 if (gpc->value != gpc->last_value)
2603 gpc->gfx_random = init_gfx_random;
2609 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2610 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2611 gpc->gfx_random = init_gfx_random;
2614 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2615 gfx.anim_random_frame = gpc->gfx_random;
2617 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2618 gpc->gfx_frame = element_info[element].collect_score;
2620 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2622 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2623 gfx.anim_random_frame = last_anim_random_frame;
2626 else if (gpc->type == TYPE_GRAPHIC)
2628 if (gpc->graphic != IMG_UNDEFINED)
2630 int last_anim_random_frame = gfx.anim_random_frame;
2631 int graphic = gpc->graphic;
2632 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2634 graphic_info[graphic].anim_global_anim_sync ?
2635 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2637 if (gpc->value != gpc->last_value)
2640 gpc->gfx_random = init_gfx_random;
2646 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2647 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2648 gpc->gfx_random = init_gfx_random;
2651 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2652 gfx.anim_random_frame = gpc->gfx_random;
2654 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2656 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2657 gfx.anim_random_frame = last_anim_random_frame;
2663 static void DisplayGameControlValues(void)
2665 boolean redraw_panel = FALSE;
2668 for (i = 0; game_panel_controls[i].nr != -1; i++)
2670 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2672 if (PANEL_DEACTIVATED(gpc->pos))
2675 if (gpc->value == gpc->last_value &&
2676 gpc->frame == gpc->last_frame)
2679 redraw_panel = TRUE;
2685 // copy default game door content to main double buffer
2687 // !!! CHECK AGAIN !!!
2688 SetPanelBackground();
2689 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2690 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2692 // redraw game control buttons
2693 RedrawGameButtons();
2695 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2697 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2699 int nr = game_panel_order[i].nr;
2700 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2701 struct TextPosInfo *pos = gpc->pos;
2702 int type = gpc->type;
2703 int value = gpc->value;
2704 int frame = gpc->frame;
2705 int size = pos->size;
2706 int font = pos->font;
2707 boolean draw_masked = pos->draw_masked;
2708 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2710 if (PANEL_DEACTIVATED(pos))
2713 if (pos->class == get_hash_from_key("extra_panel_items") &&
2714 !setup.prefer_extra_panel_items)
2717 gpc->last_value = value;
2718 gpc->last_frame = frame;
2720 if (type == TYPE_INTEGER)
2722 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2723 nr == GAME_PANEL_INVENTORY_COUNT ||
2724 nr == GAME_PANEL_SCORE ||
2725 nr == GAME_PANEL_HIGHSCORE ||
2726 nr == GAME_PANEL_TIME)
2728 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2730 if (use_dynamic_size) // use dynamic number of digits
2732 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 :
2733 nr == GAME_PANEL_INVENTORY_COUNT ||
2734 nr == GAME_PANEL_TIME ? 1000 : 100000);
2735 int size_add = (nr == GAME_PANEL_LEVEL_NUMBER ||
2736 nr == GAME_PANEL_INVENTORY_COUNT ||
2737 nr == GAME_PANEL_TIME ? 1 : 2);
2738 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 :
2739 nr == GAME_PANEL_INVENTORY_COUNT ||
2740 nr == GAME_PANEL_TIME ? 3 : 5);
2741 int size2 = size1 + size_add;
2742 int font1 = pos->font;
2743 int font2 = pos->font_alt;
2745 size = (value < value_change ? size1 : size2);
2746 font = (value < value_change ? font1 : font2);
2750 // correct text size if "digits" is zero or less
2752 size = strlen(int2str(value, size));
2754 // dynamically correct text alignment
2755 pos->width = size * getFontWidth(font);
2757 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2758 int2str(value, size), font, mask_mode);
2760 else if (type == TYPE_ELEMENT)
2762 int element, graphic;
2766 int dst_x = PANEL_XPOS(pos);
2767 int dst_y = PANEL_YPOS(pos);
2769 if (value != EL_UNDEFINED && value != EL_EMPTY)
2772 graphic = el2panelimg(value);
2775 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2776 element, EL_NAME(element), size);
2779 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2782 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2785 width = graphic_info[graphic].width * size / TILESIZE;
2786 height = graphic_info[graphic].height * size / TILESIZE;
2789 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2792 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2796 else if (type == TYPE_GRAPHIC)
2798 int graphic = gpc->graphic;
2799 int graphic_active = gpc->graphic_active;
2803 int dst_x = PANEL_XPOS(pos);
2804 int dst_y = PANEL_YPOS(pos);
2805 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2806 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2808 if (graphic != IMG_UNDEFINED && !skip)
2810 if (pos->style == STYLE_REVERSE)
2811 value = 100 - value;
2813 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2815 if (pos->direction & MV_HORIZONTAL)
2817 width = graphic_info[graphic_active].width * value / 100;
2818 height = graphic_info[graphic_active].height;
2820 if (pos->direction == MV_LEFT)
2822 src_x += graphic_info[graphic_active].width - width;
2823 dst_x += graphic_info[graphic_active].width - width;
2828 width = graphic_info[graphic_active].width;
2829 height = graphic_info[graphic_active].height * value / 100;
2831 if (pos->direction == MV_UP)
2833 src_y += graphic_info[graphic_active].height - height;
2834 dst_y += graphic_info[graphic_active].height - height;
2839 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2842 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2845 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2847 if (pos->direction & MV_HORIZONTAL)
2849 if (pos->direction == MV_RIGHT)
2856 dst_x = PANEL_XPOS(pos);
2859 width = graphic_info[graphic].width - width;
2863 if (pos->direction == MV_DOWN)
2870 dst_y = PANEL_YPOS(pos);
2873 height = graphic_info[graphic].height - height;
2877 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2880 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2884 else if (type == TYPE_STRING)
2886 boolean active = (value != 0);
2887 char *state_normal = "off";
2888 char *state_active = "on";
2889 char *state = (active ? state_active : state_normal);
2890 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2891 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2892 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2893 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2895 if (nr == GAME_PANEL_GRAVITY_STATE)
2897 int font1 = pos->font; // (used for normal state)
2898 int font2 = pos->font_alt; // (used for active state)
2900 font = (active ? font2 : font1);
2909 // don't truncate output if "chars" is zero or less
2912 // dynamically correct text alignment
2913 pos->width = size * getFontWidth(font);
2916 s_cut = getStringCopyN(s, size);
2918 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2919 s_cut, font, mask_mode);
2925 redraw_mask |= REDRAW_DOOR_1;
2928 SetGameStatus(GAME_MODE_PLAYING);
2931 void UpdateAndDisplayGameControlValues(void)
2933 if (tape.deactivate_display)
2936 UpdateGameControlValues();
2937 DisplayGameControlValues();
2940 void UpdateGameDoorValues(void)
2942 UpdateGameControlValues();
2945 void DrawGameDoorValues(void)
2947 DisplayGameControlValues();
2951 // ============================================================================
2953 // ----------------------------------------------------------------------------
2954 // initialize game engine due to level / tape version number
2955 // ============================================================================
2957 static void InitGameEngine(void)
2959 int i, j, k, l, x, y;
2961 // set game engine from tape file when re-playing, else from level file
2962 game.engine_version = (tape.playing ? tape.engine_version :
2963 level.game_version);
2965 // set single or multi-player game mode (needed for re-playing tapes)
2966 game.team_mode = setup.team_mode;
2970 int num_players = 0;
2972 for (i = 0; i < MAX_PLAYERS; i++)
2973 if (tape.player_participates[i])
2976 // multi-player tapes contain input data for more than one player
2977 game.team_mode = (num_players > 1);
2981 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2982 level.game_version);
2983 Debug("game:init:level", " tape.file_version == %06d",
2985 Debug("game:init:level", " tape.game_version == %06d",
2987 Debug("game:init:level", " tape.engine_version == %06d",
2988 tape.engine_version);
2989 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2990 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2993 // --------------------------------------------------------------------------
2994 // set flags for bugs and changes according to active game engine version
2995 // --------------------------------------------------------------------------
2999 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
3001 Bug was introduced in version:
3004 Bug was fixed in version:
3008 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
3009 but the property "can fall" was missing, which caused some levels to be
3010 unsolvable. This was fixed in version 4.2.0.0.
3012 Affected levels/tapes:
3013 An example for a tape that was fixed by this bugfix is tape 029 from the
3014 level set "rnd_sam_bateman".
3015 The wrong behaviour will still be used for all levels or tapes that were
3016 created/recorded with it. An example for this is tape 023 from the level
3017 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
3020 boolean use_amoeba_dropping_cannot_fall_bug =
3021 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
3022 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
3024 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3025 tape.game_version < VERSION_IDENT(4,2,0,0)));
3028 Summary of bugfix/change:
3029 Fixed move speed of elements entering or leaving magic wall.
3031 Fixed/changed in version:
3035 Before 2.0.1, move speed of elements entering or leaving magic wall was
3036 twice as fast as it is now.
3037 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
3039 Affected levels/tapes:
3040 The first condition is generally needed for all levels/tapes before version
3041 2.0.1, which might use the old behaviour before it was changed; known tapes
3042 that are affected: Tape 014 from the level set "rnd_conor_mancone".
3043 The second condition is an exception from the above case and is needed for
3044 the special case of tapes recorded with game (not engine!) version 2.0.1 or
3045 above, but before it was known that this change would break tapes like the
3046 above and was fixed in 4.2.0.0, so that the changed behaviour was active
3047 although the engine version while recording maybe was before 2.0.1. There
3048 are a lot of tapes that are affected by this exception, like tape 006 from
3049 the level set "rnd_conor_mancone".
3052 boolean use_old_move_stepsize_for_magic_wall =
3053 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
3055 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3056 tape.game_version < VERSION_IDENT(4,2,0,0)));
3059 Summary of bugfix/change:
3060 Fixed handling for custom elements that change when pushed by the player.
3062 Fixed/changed in version:
3066 Before 3.1.0, custom elements that "change when pushing" changed directly
3067 after the player started pushing them (until then handled in "DigField()").
3068 Since 3.1.0, these custom elements are not changed until the "pushing"
3069 move of the element is finished (now handled in "ContinueMoving()").
3071 Affected levels/tapes:
3072 The first condition is generally needed for all levels/tapes before version
3073 3.1.0, which might use the old behaviour before it was changed; known tapes
3074 that are affected are some tapes from the level set "Walpurgis Gardens" by
3076 The second condition is an exception from the above case and is needed for
3077 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3078 above (including some development versions of 3.1.0), but before it was
3079 known that this change would break tapes like the above and was fixed in
3080 3.1.1, so that the changed behaviour was active although the engine version
3081 while recording maybe was before 3.1.0. There is at least one tape that is
3082 affected by this exception, which is the tape for the one-level set "Bug
3083 Machine" by Juergen Bonhagen.
3086 game.use_change_when_pushing_bug =
3087 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3089 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3090 tape.game_version < VERSION_IDENT(3,1,1,0)));
3093 Summary of bugfix/change:
3094 Fixed handling for blocking the field the player leaves when moving.
3096 Fixed/changed in version:
3100 Before 3.1.1, when "block last field when moving" was enabled, the field
3101 the player is leaving when moving was blocked for the time of the move,
3102 and was directly unblocked afterwards. This resulted in the last field
3103 being blocked for exactly one less than the number of frames of one player
3104 move. Additionally, even when blocking was disabled, the last field was
3105 blocked for exactly one frame.
3106 Since 3.1.1, due to changes in player movement handling, the last field
3107 is not blocked at all when blocking is disabled. When blocking is enabled,
3108 the last field is blocked for exactly the number of frames of one player
3109 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3110 last field is blocked for exactly one more than the number of frames of
3113 Affected levels/tapes:
3114 (!!! yet to be determined -- probably many !!!)
3117 game.use_block_last_field_bug =
3118 (game.engine_version < VERSION_IDENT(3,1,1,0));
3120 /* various special flags and settings for native Emerald Mine game engine */
3122 game_em.use_single_button =
3123 (game.engine_version > VERSION_IDENT(4,0,0,2));
3125 game_em.use_push_delay =
3126 (game.engine_version > VERSION_IDENT(4,3,7,1));
3128 game_em.use_snap_key_bug =
3129 (game.engine_version < VERSION_IDENT(4,0,1,0));
3131 game_em.use_random_bug =
3132 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3134 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3136 game_em.use_old_explosions = use_old_em_engine;
3137 game_em.use_old_android = use_old_em_engine;
3138 game_em.use_old_push_elements = use_old_em_engine;
3139 game_em.use_old_push_into_acid = use_old_em_engine;
3141 game_em.use_wrap_around = !use_old_em_engine;
3143 // --------------------------------------------------------------------------
3145 // set maximal allowed number of custom element changes per game frame
3146 game.max_num_changes_per_frame = 1;
3148 // default scan direction: scan playfield from top/left to bottom/right
3149 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3151 // dynamically adjust element properties according to game engine version
3152 InitElementPropertiesEngine(game.engine_version);
3154 // ---------- initialize special element properties -------------------------
3156 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3157 if (use_amoeba_dropping_cannot_fall_bug)
3158 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3160 // ---------- initialize player's initial move delay ------------------------
3162 // dynamically adjust player properties according to level information
3163 for (i = 0; i < MAX_PLAYERS; i++)
3164 game.initial_move_delay_value[i] =
3165 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3167 // dynamically adjust player properties according to game engine version
3168 for (i = 0; i < MAX_PLAYERS; i++)
3169 game.initial_move_delay[i] =
3170 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3171 game.initial_move_delay_value[i] : 0);
3173 // ---------- initialize player's initial push delay ------------------------
3175 // dynamically adjust player properties according to game engine version
3176 game.initial_push_delay_value =
3177 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3179 // ---------- initialize changing elements ----------------------------------
3181 // initialize changing elements information
3182 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3184 struct ElementInfo *ei = &element_info[i];
3186 // this pointer might have been changed in the level editor
3187 ei->change = &ei->change_page[0];
3189 if (!IS_CUSTOM_ELEMENT(i))
3191 ei->change->target_element = EL_EMPTY_SPACE;
3192 ei->change->delay_fixed = 0;
3193 ei->change->delay_random = 0;
3194 ei->change->delay_frames = 1;
3197 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3199 ei->has_change_event[j] = FALSE;
3201 ei->event_page_nr[j] = 0;
3202 ei->event_page[j] = &ei->change_page[0];
3206 // add changing elements from pre-defined list
3207 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3209 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3210 struct ElementInfo *ei = &element_info[ch_delay->element];
3212 ei->change->target_element = ch_delay->target_element;
3213 ei->change->delay_fixed = ch_delay->change_delay;
3215 ei->change->pre_change_function = ch_delay->pre_change_function;
3216 ei->change->change_function = ch_delay->change_function;
3217 ei->change->post_change_function = ch_delay->post_change_function;
3219 ei->change->can_change = TRUE;
3220 ei->change->can_change_or_has_action = TRUE;
3222 ei->has_change_event[CE_DELAY] = TRUE;
3224 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3225 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3228 // ---------- initialize if element can trigger global animations -----------
3230 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3232 struct ElementInfo *ei = &element_info[i];
3234 ei->has_anim_event = FALSE;
3237 InitGlobalAnimEventsForCustomElements();
3239 // ---------- initialize internal run-time variables ------------------------
3241 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3243 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3245 for (j = 0; j < ei->num_change_pages; j++)
3247 ei->change_page[j].can_change_or_has_action =
3248 (ei->change_page[j].can_change |
3249 ei->change_page[j].has_action);
3253 // add change events from custom element configuration
3254 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3256 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3258 for (j = 0; j < ei->num_change_pages; j++)
3260 if (!ei->change_page[j].can_change_or_has_action)
3263 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3265 // only add event page for the first page found with this event
3266 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3268 ei->has_change_event[k] = TRUE;
3270 ei->event_page_nr[k] = j;
3271 ei->event_page[k] = &ei->change_page[j];
3277 // ---------- initialize reference elements in change conditions ------------
3279 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3281 int element = EL_CUSTOM_START + i;
3282 struct ElementInfo *ei = &element_info[element];
3284 for (j = 0; j < ei->num_change_pages; j++)
3286 int trigger_element = ei->change_page[j].initial_trigger_element;
3288 if (trigger_element >= EL_PREV_CE_8 &&
3289 trigger_element <= EL_NEXT_CE_8)
3290 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3292 ei->change_page[j].trigger_element = trigger_element;
3296 // ---------- initialize run-time trigger player and element ----------------
3298 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3300 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3302 for (j = 0; j < ei->num_change_pages; j++)
3304 struct ElementChangeInfo *change = &ei->change_page[j];
3306 change->actual_trigger_element = EL_EMPTY;
3307 change->actual_trigger_player = EL_EMPTY;
3308 change->actual_trigger_player_bits = CH_PLAYER_NONE;
3309 change->actual_trigger_side = CH_SIDE_NONE;
3310 change->actual_trigger_ce_value = 0;
3311 change->actual_trigger_ce_score = 0;
3312 change->actual_trigger_x = -1;
3313 change->actual_trigger_y = -1;
3317 // ---------- initialize trigger events -------------------------------------
3319 // initialize trigger events information
3320 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3321 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3322 trigger_events[i][j] = FALSE;
3324 // add trigger events from element change event properties
3325 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3327 struct ElementInfo *ei = &element_info[i];
3329 for (j = 0; j < ei->num_change_pages; j++)
3331 struct ElementChangeInfo *change = &ei->change_page[j];
3333 if (!change->can_change_or_has_action)
3336 if (change->has_event[CE_BY_OTHER_ACTION])
3338 int trigger_element = change->trigger_element;
3340 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3342 if (change->has_event[k])
3344 if (IS_GROUP_ELEMENT(trigger_element))
3346 struct ElementGroupInfo *group =
3347 element_info[trigger_element].group;
3349 for (l = 0; l < group->num_elements_resolved; l++)
3350 trigger_events[group->element_resolved[l]][k] = TRUE;
3352 else if (trigger_element == EL_ANY_ELEMENT)
3353 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3354 trigger_events[l][k] = TRUE;
3356 trigger_events[trigger_element][k] = TRUE;
3363 // ---------- initialize push delay -----------------------------------------
3365 // initialize push delay values to default
3366 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3368 if (!IS_CUSTOM_ELEMENT(i))
3370 // set default push delay values (corrected since version 3.0.7-1)
3371 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3373 element_info[i].push_delay_fixed = 2;
3374 element_info[i].push_delay_random = 8;
3378 element_info[i].push_delay_fixed = 8;
3379 element_info[i].push_delay_random = 8;
3384 // set push delay value for certain elements from pre-defined list
3385 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3387 int e = push_delay_list[i].element;
3389 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3390 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3393 // set push delay value for Supaplex elements for newer engine versions
3394 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3396 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3398 if (IS_SP_ELEMENT(i))
3400 // set SP push delay to just enough to push under a falling zonk
3401 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3403 element_info[i].push_delay_fixed = delay;
3404 element_info[i].push_delay_random = 0;
3409 // ---------- initialize move stepsize --------------------------------------
3411 // initialize move stepsize values to default
3412 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3413 if (!IS_CUSTOM_ELEMENT(i))
3414 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3416 // set move stepsize value for certain elements from pre-defined list
3417 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3419 int e = move_stepsize_list[i].element;
3421 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3423 // set move stepsize value for certain elements for older engine versions
3424 if (use_old_move_stepsize_for_magic_wall)
3426 if (e == EL_MAGIC_WALL_FILLING ||
3427 e == EL_MAGIC_WALL_EMPTYING ||
3428 e == EL_BD_MAGIC_WALL_FILLING ||
3429 e == EL_BD_MAGIC_WALL_EMPTYING)
3430 element_info[e].move_stepsize *= 2;
3434 // ---------- initialize collect score --------------------------------------
3436 // initialize collect score values for custom elements from initial value
3437 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3438 if (IS_CUSTOM_ELEMENT(i))
3439 element_info[i].collect_score = element_info[i].collect_score_initial;
3441 // ---------- initialize collect count --------------------------------------
3443 // initialize collect count values for non-custom elements
3444 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3445 if (!IS_CUSTOM_ELEMENT(i))
3446 element_info[i].collect_count_initial = 0;
3448 // add collect count values for all elements from pre-defined list
3449 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3450 element_info[collect_count_list[i].element].collect_count_initial =
3451 collect_count_list[i].count;
3453 // ---------- initialize access direction -----------------------------------
3455 // initialize access direction values to default (access from every side)
3456 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3457 if (!IS_CUSTOM_ELEMENT(i))
3458 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3460 // set access direction value for certain elements from pre-defined list
3461 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3462 element_info[access_direction_list[i].element].access_direction =
3463 access_direction_list[i].direction;
3465 // ---------- initialize explosion content ----------------------------------
3466 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3468 if (IS_CUSTOM_ELEMENT(i))
3471 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3473 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3475 element_info[i].content.e[x][y] =
3476 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3477 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3478 i == EL_PLAYER_3 ? EL_EMERALD :
3479 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3480 i == EL_MOLE ? EL_EMERALD_RED :
3481 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3482 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3483 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3484 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3485 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3486 i == EL_WALL_EMERALD ? EL_EMERALD :
3487 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3488 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3489 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3490 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3491 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3492 i == EL_WALL_PEARL ? EL_PEARL :
3493 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3498 // ---------- initialize recursion detection --------------------------------
3499 recursion_loop_depth = 0;
3500 recursion_loop_detected = FALSE;
3501 recursion_loop_element = EL_UNDEFINED;
3503 // ---------- initialize graphics engine ------------------------------------
3504 game.scroll_delay_value =
3505 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3506 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3507 !setup.forced_scroll_delay ? 0 :
3508 setup.scroll_delay ? setup.scroll_delay_value : 0);
3509 if (game.forced_scroll_delay_value == -1)
3510 game.scroll_delay_value =
3511 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3513 // ---------- initialize game engine snapshots ------------------------------
3514 for (i = 0; i < MAX_PLAYERS; i++)
3515 game.snapshot.last_action[i] = 0;
3516 game.snapshot.changed_action = FALSE;
3517 game.snapshot.collected_item = FALSE;
3518 game.snapshot.mode =
3519 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3520 SNAPSHOT_MODE_EVERY_STEP :
3521 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3522 SNAPSHOT_MODE_EVERY_MOVE :
3523 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3524 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3525 game.snapshot.save_snapshot = FALSE;
3527 // ---------- initialize level time for Supaplex engine ---------------------
3528 // Supaplex levels with time limit currently unsupported -- should be added
3529 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3532 // ---------- initialize flags for handling game actions --------------------
3534 // set flags for game actions to default values
3535 game.use_key_actions = TRUE;
3536 game.use_mouse_actions = FALSE;
3538 // when using Mirror Magic game engine, handle mouse events only
3539 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3541 game.use_key_actions = FALSE;
3542 game.use_mouse_actions = TRUE;
3545 // check for custom elements with mouse click events
3546 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3548 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3550 int element = EL_CUSTOM_START + i;
3552 if (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3553 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3554 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3555 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3556 game.use_mouse_actions = TRUE;
3561 static int get_num_special_action(int element, int action_first,
3564 int num_special_action = 0;
3567 for (i = action_first; i <= action_last; i++)
3569 boolean found = FALSE;
3571 for (j = 0; j < NUM_DIRECTIONS; j++)
3572 if (el_act_dir2img(element, i, j) !=
3573 el_act_dir2img(element, ACTION_DEFAULT, j))
3577 num_special_action++;
3582 return num_special_action;
3586 // ============================================================================
3588 // ----------------------------------------------------------------------------
3589 // initialize and start new game
3590 // ============================================================================
3592 #if DEBUG_INIT_PLAYER
3593 static void DebugPrintPlayerStatus(char *message)
3600 Debug("game:init:player", "%s:", message);
3602 for (i = 0; i < MAX_PLAYERS; i++)
3604 struct PlayerInfo *player = &stored_player[i];
3606 Debug("game:init:player",
3607 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3611 player->connected_locally,
3612 player->connected_network,
3614 (local_player == player ? " (local player)" : ""));
3621 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3622 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3623 int fade_mask = REDRAW_FIELD;
3624 boolean restarting = (game_status == GAME_MODE_PLAYING);
3625 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3626 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3627 int initial_move_dir = MV_DOWN;
3630 // required here to update video display before fading (FIX THIS)
3631 DrawMaskedBorder(REDRAW_DOOR_2);
3633 if (!game.restart_level)
3634 CloseDoor(DOOR_CLOSE_1);
3638 // force fading out global animations displayed during game play
3639 SetGameStatus(GAME_MODE_PSEUDO_RESTARTING);
3643 SetGameStatus(GAME_MODE_PLAYING);
3646 if (level_editor_test_game)
3647 FadeSkipNextFadeOut();
3649 FadeSetEnterScreen();
3652 fade_mask = REDRAW_ALL;
3654 FadeLevelSoundsAndMusic();
3656 ExpireSoundLoops(TRUE);
3662 // force restarting global animations displayed during game play
3663 RestartGlobalAnimsByStatus(GAME_MODE_PSEUDO_RESTARTING);
3665 // this is required for "transforming" fade modes like cross-fading
3666 // (else global animations will be stopped, but not restarted here)
3667 SetAnimStatusBeforeFading(GAME_MODE_PSEUDO_RESTARTING);
3669 SetGameStatus(GAME_MODE_PLAYING);
3672 if (level_editor_test_game)
3673 FadeSkipNextFadeIn();
3675 // needed if different viewport properties defined for playing
3676 ChangeViewportPropertiesIfNeeded();
3680 DrawCompleteVideoDisplay();
3682 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3685 InitGameControlValues();
3689 // initialize tape actions from game when recording tape
3690 tape.use_key_actions = game.use_key_actions;
3691 tape.use_mouse_actions = game.use_mouse_actions;
3693 // initialize visible playfield size when recording tape (for team mode)
3694 tape.scr_fieldx = SCR_FIELDX;
3695 tape.scr_fieldy = SCR_FIELDY;
3698 // don't play tapes over network
3699 network_playing = (network.enabled && !tape.playing);
3701 for (i = 0; i < MAX_PLAYERS; i++)
3703 struct PlayerInfo *player = &stored_player[i];
3705 player->index_nr = i;
3706 player->index_bit = (1 << i);
3707 player->element_nr = EL_PLAYER_1 + i;
3709 player->present = FALSE;
3710 player->active = FALSE;
3711 player->mapped = FALSE;
3713 player->killed = FALSE;
3714 player->reanimated = FALSE;
3715 player->buried = FALSE;
3718 player->effective_action = 0;
3719 player->programmed_action = 0;
3720 player->snap_action = 0;
3722 player->mouse_action.lx = 0;
3723 player->mouse_action.ly = 0;
3724 player->mouse_action.button = 0;
3725 player->mouse_action.button_hint = 0;
3727 player->effective_mouse_action.lx = 0;
3728 player->effective_mouse_action.ly = 0;
3729 player->effective_mouse_action.button = 0;
3730 player->effective_mouse_action.button_hint = 0;
3732 for (j = 0; j < MAX_NUM_KEYS; j++)
3733 player->key[j] = FALSE;
3735 player->num_white_keys = 0;
3737 player->dynabomb_count = 0;
3738 player->dynabomb_size = 1;
3739 player->dynabombs_left = 0;
3740 player->dynabomb_xl = FALSE;
3742 player->MovDir = initial_move_dir;
3745 player->GfxDir = initial_move_dir;
3746 player->GfxAction = ACTION_DEFAULT;
3748 player->StepFrame = 0;
3750 player->initial_element = player->element_nr;
3751 player->artwork_element =
3752 (level.use_artwork_element[i] ? level.artwork_element[i] :
3753 player->element_nr);
3754 player->use_murphy = FALSE;
3756 player->block_last_field = FALSE; // initialized in InitPlayerField()
3757 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3759 player->gravity = level.initial_player_gravity[i];
3761 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3763 player->actual_frame_counter.count = 0;
3764 player->actual_frame_counter.value = 1;
3766 player->step_counter = 0;
3768 player->last_move_dir = initial_move_dir;
3770 player->is_active = FALSE;
3772 player->is_waiting = FALSE;
3773 player->is_moving = FALSE;
3774 player->is_auto_moving = FALSE;
3775 player->is_digging = FALSE;
3776 player->is_snapping = FALSE;
3777 player->is_collecting = FALSE;
3778 player->is_pushing = FALSE;
3779 player->is_switching = FALSE;
3780 player->is_dropping = FALSE;
3781 player->is_dropping_pressed = FALSE;
3783 player->is_bored = FALSE;
3784 player->is_sleeping = FALSE;
3786 player->was_waiting = TRUE;
3787 player->was_moving = FALSE;
3788 player->was_snapping = FALSE;
3789 player->was_dropping = FALSE;
3791 player->force_dropping = FALSE;
3793 player->frame_counter_bored = -1;
3794 player->frame_counter_sleeping = -1;
3796 player->anim_delay_counter = 0;
3797 player->post_delay_counter = 0;
3799 player->dir_waiting = initial_move_dir;
3800 player->action_waiting = ACTION_DEFAULT;
3801 player->last_action_waiting = ACTION_DEFAULT;
3802 player->special_action_bored = ACTION_DEFAULT;
3803 player->special_action_sleeping = ACTION_DEFAULT;
3805 player->switch_x = -1;
3806 player->switch_y = -1;
3808 player->drop_x = -1;
3809 player->drop_y = -1;
3811 player->show_envelope = 0;
3813 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3815 player->push_delay = -1; // initialized when pushing starts
3816 player->push_delay_value = game.initial_push_delay_value;
3818 player->drop_delay = 0;
3819 player->drop_pressed_delay = 0;
3821 player->last_jx = -1;
3822 player->last_jy = -1;
3826 player->shield_normal_time_left = 0;
3827 player->shield_deadly_time_left = 0;
3829 player->last_removed_element = EL_UNDEFINED;
3831 player->inventory_infinite_element = EL_UNDEFINED;
3832 player->inventory_size = 0;
3834 if (level.use_initial_inventory[i])
3836 for (j = 0; j < level.initial_inventory_size[i]; j++)
3838 int element = level.initial_inventory_content[i][j];
3839 int collect_count = element_info[element].collect_count_initial;
3842 if (!IS_CUSTOM_ELEMENT(element))
3845 if (collect_count == 0)
3846 player->inventory_infinite_element = element;
3848 for (k = 0; k < collect_count; k++)
3849 if (player->inventory_size < MAX_INVENTORY_SIZE)
3850 player->inventory_element[player->inventory_size++] = element;
3854 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3855 SnapField(player, 0, 0);
3857 map_player_action[i] = i;
3860 network_player_action_received = FALSE;
3862 // initial null action
3863 if (network_playing)
3864 SendToServer_MovePlayer(MV_NONE);
3869 TimeLeft = level.time;
3874 ScreenMovDir = MV_NONE;
3878 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3880 game.robot_wheel_x = -1;
3881 game.robot_wheel_y = -1;
3886 game.all_players_gone = FALSE;
3888 game.LevelSolved = FALSE;
3889 game.GameOver = FALSE;
3891 game.GamePlayed = !tape.playing;
3893 game.LevelSolved_GameWon = FALSE;
3894 game.LevelSolved_GameEnd = FALSE;
3895 game.LevelSolved_SaveTape = FALSE;
3896 game.LevelSolved_SaveScore = FALSE;
3898 game.LevelSolved_CountingTime = 0;
3899 game.LevelSolved_CountingScore = 0;
3900 game.LevelSolved_CountingHealth = 0;
3902 game.RestartGameRequested = FALSE;
3904 game.panel.active = TRUE;
3906 game.no_level_time_limit = (level.time == 0);
3907 game.time_limit = (leveldir_current->time_limit && setup.time_limit);
3909 game.yamyam_content_nr = 0;
3910 game.robot_wheel_active = FALSE;
3911 game.magic_wall_active = FALSE;
3912 game.magic_wall_time_left = 0;
3913 game.light_time_left = 0;
3914 game.timegate_time_left = 0;
3915 game.switchgate_pos = 0;
3916 game.wind_direction = level.wind_direction_initial;
3918 game.time_final = 0;
3919 game.score_time_final = 0;
3922 game.score_final = 0;
3924 game.health = MAX_HEALTH;
3925 game.health_final = MAX_HEALTH;
3927 game.gems_still_needed = level.gems_needed;
3928 game.sokoban_fields_still_needed = 0;
3929 game.sokoban_objects_still_needed = 0;
3930 game.lights_still_needed = 0;
3931 game.players_still_needed = 0;
3932 game.friends_still_needed = 0;
3934 game.lenses_time_left = 0;
3935 game.magnify_time_left = 0;
3937 game.ball_active = level.ball_active_initial;
3938 game.ball_content_nr = 0;
3940 game.explosions_delayed = TRUE;
3942 game.envelope_active = FALSE;
3944 // special case: set custom artwork setting to initial value
3945 game.use_masked_elements = game.use_masked_elements_initial;
3947 for (i = 0; i < NUM_BELTS; i++)
3949 game.belt_dir[i] = MV_NONE;
3950 game.belt_dir_nr[i] = 3; // not moving, next moving left
3953 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3954 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3956 #if DEBUG_INIT_PLAYER
3957 DebugPrintPlayerStatus("Player status at level initialization");
3960 SCAN_PLAYFIELD(x, y)
3962 Tile[x][y] = Last[x][y] = level.field[x][y];
3963 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3964 ChangeDelay[x][y] = 0;
3965 ChangePage[x][y] = -1;
3966 CustomValue[x][y] = 0; // initialized in InitField()
3967 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3969 WasJustMoving[x][y] = 0;
3970 WasJustFalling[x][y] = 0;
3971 CheckCollision[x][y] = 0;
3972 CheckImpact[x][y] = 0;
3974 Pushed[x][y] = FALSE;
3976 ChangeCount[x][y] = 0;
3977 ChangeEvent[x][y] = -1;
3979 ExplodePhase[x][y] = 0;
3980 ExplodeDelay[x][y] = 0;
3981 ExplodeField[x][y] = EX_TYPE_NONE;
3983 RunnerVisit[x][y] = 0;
3984 PlayerVisit[x][y] = 0;
3987 GfxRandom[x][y] = INIT_GFX_RANDOM();
3988 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
3989 GfxElement[x][y] = EL_UNDEFINED;
3990 GfxElementEmpty[x][y] = EL_EMPTY;
3991 GfxAction[x][y] = ACTION_DEFAULT;
3992 GfxDir[x][y] = MV_NONE;
3993 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3996 SCAN_PLAYFIELD(x, y)
3998 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
4000 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
4003 InitField(x, y, TRUE);
4005 ResetGfxAnimation(x, y);
4010 // required if level does not contain any "empty space" element
4011 if (element_info[EL_EMPTY].use_gfx_element)
4012 game.use_masked_elements = TRUE;
4014 for (i = 0; i < MAX_PLAYERS; i++)
4016 struct PlayerInfo *player = &stored_player[i];
4018 // set number of special actions for bored and sleeping animation
4019 player->num_special_action_bored =
4020 get_num_special_action(player->artwork_element,
4021 ACTION_BORING_1, ACTION_BORING_LAST);
4022 player->num_special_action_sleeping =
4023 get_num_special_action(player->artwork_element,
4024 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
4027 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
4028 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
4030 // initialize type of slippery elements
4031 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4033 if (!IS_CUSTOM_ELEMENT(i))
4035 // default: elements slip down either to the left or right randomly
4036 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
4038 // SP style elements prefer to slip down on the left side
4039 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
4040 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4042 // BD style elements prefer to slip down on the left side
4043 if (game.emulation == EMU_BOULDERDASH)
4044 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4048 // initialize explosion and ignition delay
4049 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4051 if (!IS_CUSTOM_ELEMENT(i))
4054 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
4055 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
4056 game.emulation == EMU_SUPAPLEX ? 3 : 2);
4057 int last_phase = (num_phase + 1) * delay;
4058 int half_phase = (num_phase / 2) * delay;
4060 element_info[i].explosion_delay = last_phase - 1;
4061 element_info[i].ignition_delay = half_phase;
4063 if (i == EL_BLACK_ORB)
4064 element_info[i].ignition_delay = 1;
4068 // correct non-moving belts to start moving left
4069 for (i = 0; i < NUM_BELTS; i++)
4070 if (game.belt_dir[i] == MV_NONE)
4071 game.belt_dir_nr[i] = 3; // not moving, next moving left
4073 #if USE_NEW_PLAYER_ASSIGNMENTS
4074 // use preferred player also in local single-player mode
4075 if (!network.enabled && !game.team_mode)
4077 int new_index_nr = setup.network_player_nr;
4079 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
4081 for (i = 0; i < MAX_PLAYERS; i++)
4082 stored_player[i].connected_locally = FALSE;
4084 stored_player[new_index_nr].connected_locally = TRUE;
4088 for (i = 0; i < MAX_PLAYERS; i++)
4090 stored_player[i].connected = FALSE;
4092 // in network game mode, the local player might not be the first player
4093 if (stored_player[i].connected_locally)
4094 local_player = &stored_player[i];
4097 if (!network.enabled)
4098 local_player->connected = TRUE;
4102 for (i = 0; i < MAX_PLAYERS; i++)
4103 stored_player[i].connected = tape.player_participates[i];
4105 else if (network.enabled)
4107 // add team mode players connected over the network (needed for correct
4108 // assignment of player figures from level to locally playing players)
4110 for (i = 0; i < MAX_PLAYERS; i++)
4111 if (stored_player[i].connected_network)
4112 stored_player[i].connected = TRUE;
4114 else if (game.team_mode)
4116 // try to guess locally connected team mode players (needed for correct
4117 // assignment of player figures from level to locally playing players)
4119 for (i = 0; i < MAX_PLAYERS; i++)
4120 if (setup.input[i].use_joystick ||
4121 setup.input[i].key.left != KSYM_UNDEFINED)
4122 stored_player[i].connected = TRUE;
4125 #if DEBUG_INIT_PLAYER
4126 DebugPrintPlayerStatus("Player status after level initialization");
4129 #if DEBUG_INIT_PLAYER
4130 Debug("game:init:player", "Reassigning players ...");
4133 // check if any connected player was not found in playfield
4134 for (i = 0; i < MAX_PLAYERS; i++)
4136 struct PlayerInfo *player = &stored_player[i];
4138 if (player->connected && !player->present)
4140 struct PlayerInfo *field_player = NULL;
4142 #if DEBUG_INIT_PLAYER
4143 Debug("game:init:player",
4144 "- looking for field player for player %d ...", i + 1);
4147 // assign first free player found that is present in the playfield
4149 // first try: look for unmapped playfield player that is not connected
4150 for (j = 0; j < MAX_PLAYERS; j++)
4151 if (field_player == NULL &&
4152 stored_player[j].present &&
4153 !stored_player[j].mapped &&
4154 !stored_player[j].connected)
4155 field_player = &stored_player[j];
4157 // second try: look for *any* unmapped playfield player
4158 for (j = 0; j < MAX_PLAYERS; j++)
4159 if (field_player == NULL &&
4160 stored_player[j].present &&
4161 !stored_player[j].mapped)
4162 field_player = &stored_player[j];
4164 if (field_player != NULL)
4166 int jx = field_player->jx, jy = field_player->jy;
4168 #if DEBUG_INIT_PLAYER
4169 Debug("game:init:player", "- found player %d",
4170 field_player->index_nr + 1);
4173 player->present = FALSE;
4174 player->active = FALSE;
4176 field_player->present = TRUE;
4177 field_player->active = TRUE;
4180 player->initial_element = field_player->initial_element;
4181 player->artwork_element = field_player->artwork_element;
4183 player->block_last_field = field_player->block_last_field;
4184 player->block_delay_adjustment = field_player->block_delay_adjustment;
4187 StorePlayer[jx][jy] = field_player->element_nr;
4189 field_player->jx = field_player->last_jx = jx;
4190 field_player->jy = field_player->last_jy = jy;
4192 if (local_player == player)
4193 local_player = field_player;
4195 map_player_action[field_player->index_nr] = i;
4197 field_player->mapped = TRUE;
4199 #if DEBUG_INIT_PLAYER
4200 Debug("game:init:player", "- map_player_action[%d] == %d",
4201 field_player->index_nr + 1, i + 1);
4206 if (player->connected && player->present)
4207 player->mapped = TRUE;
4210 #if DEBUG_INIT_PLAYER
4211 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4216 // check if any connected player was not found in playfield
4217 for (i = 0; i < MAX_PLAYERS; i++)
4219 struct PlayerInfo *player = &stored_player[i];
4221 if (player->connected && !player->present)
4223 for (j = 0; j < MAX_PLAYERS; j++)
4225 struct PlayerInfo *field_player = &stored_player[j];
4226 int jx = field_player->jx, jy = field_player->jy;
4228 // assign first free player found that is present in the playfield
4229 if (field_player->present && !field_player->connected)
4231 player->present = TRUE;
4232 player->active = TRUE;
4234 field_player->present = FALSE;
4235 field_player->active = FALSE;
4237 player->initial_element = field_player->initial_element;
4238 player->artwork_element = field_player->artwork_element;
4240 player->block_last_field = field_player->block_last_field;
4241 player->block_delay_adjustment = field_player->block_delay_adjustment;
4243 StorePlayer[jx][jy] = player->element_nr;
4245 player->jx = player->last_jx = jx;
4246 player->jy = player->last_jy = jy;
4256 Debug("game:init:player", "local_player->present == %d",
4257 local_player->present);
4260 // set focus to local player for network games, else to all players
4261 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4262 game.centered_player_nr_next = game.centered_player_nr;
4263 game.set_centered_player = FALSE;
4264 game.set_centered_player_wrap = FALSE;
4266 if (network_playing && tape.recording)
4268 // store client dependent player focus when recording network games
4269 tape.centered_player_nr_next = game.centered_player_nr_next;
4270 tape.set_centered_player = TRUE;
4275 // when playing a tape, eliminate all players who do not participate
4277 #if USE_NEW_PLAYER_ASSIGNMENTS
4279 if (!game.team_mode)
4281 for (i = 0; i < MAX_PLAYERS; i++)
4283 if (stored_player[i].active &&
4284 !tape.player_participates[map_player_action[i]])
4286 struct PlayerInfo *player = &stored_player[i];
4287 int jx = player->jx, jy = player->jy;
4289 #if DEBUG_INIT_PLAYER
4290 Debug("game:init:player", "Removing player %d at (%d, %d)",
4294 player->active = FALSE;
4295 StorePlayer[jx][jy] = 0;
4296 Tile[jx][jy] = EL_EMPTY;
4303 for (i = 0; i < MAX_PLAYERS; i++)
4305 if (stored_player[i].active &&
4306 !tape.player_participates[i])
4308 struct PlayerInfo *player = &stored_player[i];
4309 int jx = player->jx, jy = player->jy;
4311 player->active = FALSE;
4312 StorePlayer[jx][jy] = 0;
4313 Tile[jx][jy] = EL_EMPTY;
4318 else if (!network.enabled && !game.team_mode) // && !tape.playing
4320 // when in single player mode, eliminate all but the local player
4322 for (i = 0; i < MAX_PLAYERS; i++)
4324 struct PlayerInfo *player = &stored_player[i];
4326 if (player->active && player != local_player)
4328 int jx = player->jx, jy = player->jy;
4330 player->active = FALSE;
4331 player->present = FALSE;
4333 StorePlayer[jx][jy] = 0;
4334 Tile[jx][jy] = EL_EMPTY;
4339 for (i = 0; i < MAX_PLAYERS; i++)
4340 if (stored_player[i].active)
4341 game.players_still_needed++;
4343 if (level.solved_by_one_player)
4344 game.players_still_needed = 1;
4346 // when recording the game, store which players take part in the game
4349 #if USE_NEW_PLAYER_ASSIGNMENTS
4350 for (i = 0; i < MAX_PLAYERS; i++)
4351 if (stored_player[i].connected)
4352 tape.player_participates[i] = TRUE;
4354 for (i = 0; i < MAX_PLAYERS; i++)
4355 if (stored_player[i].active)
4356 tape.player_participates[i] = TRUE;
4360 #if DEBUG_INIT_PLAYER
4361 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4364 if (BorderElement == EL_EMPTY)
4367 SBX_Right = lev_fieldx - SCR_FIELDX;
4369 SBY_Lower = lev_fieldy - SCR_FIELDY;
4374 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4376 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4379 if (full_lev_fieldx <= SCR_FIELDX)
4380 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4381 if (full_lev_fieldy <= SCR_FIELDY)
4382 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4384 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4386 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4389 // if local player not found, look for custom element that might create
4390 // the player (make some assumptions about the right custom element)
4391 if (!local_player->present)
4393 int start_x = 0, start_y = 0;
4394 int found_rating = 0;
4395 int found_element = EL_UNDEFINED;
4396 int player_nr = local_player->index_nr;
4398 SCAN_PLAYFIELD(x, y)
4400 int element = Tile[x][y];
4405 if (level.use_start_element[player_nr] &&
4406 level.start_element[player_nr] == element &&
4413 found_element = element;
4416 if (!IS_CUSTOM_ELEMENT(element))
4419 if (CAN_CHANGE(element))
4421 for (i = 0; i < element_info[element].num_change_pages; i++)
4423 // check for player created from custom element as single target
4424 content = element_info[element].change_page[i].target_element;
4425 is_player = IS_PLAYER_ELEMENT(content);
4427 if (is_player && (found_rating < 3 ||
4428 (found_rating == 3 && element < found_element)))
4434 found_element = element;
4439 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4441 // check for player created from custom element as explosion content
4442 content = element_info[element].content.e[xx][yy];
4443 is_player = IS_PLAYER_ELEMENT(content);
4445 if (is_player && (found_rating < 2 ||
4446 (found_rating == 2 && element < found_element)))
4448 start_x = x + xx - 1;
4449 start_y = y + yy - 1;
4452 found_element = element;
4455 if (!CAN_CHANGE(element))
4458 for (i = 0; i < element_info[element].num_change_pages; i++)
4460 // check for player created from custom element as extended target
4462 element_info[element].change_page[i].target_content.e[xx][yy];
4464 is_player = IS_PLAYER_ELEMENT(content);
4466 if (is_player && (found_rating < 1 ||
4467 (found_rating == 1 && element < found_element)))
4469 start_x = x + xx - 1;
4470 start_y = y + yy - 1;
4473 found_element = element;
4479 scroll_x = SCROLL_POSITION_X(start_x);
4480 scroll_y = SCROLL_POSITION_Y(start_y);
4484 scroll_x = SCROLL_POSITION_X(local_player->jx);
4485 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4488 if (game.forced_scroll_x != ARG_UNDEFINED_VALUE)
4489 scroll_x = game.forced_scroll_x;
4490 if (game.forced_scroll_y != ARG_UNDEFINED_VALUE)
4491 scroll_y = game.forced_scroll_y;
4493 // !!! FIX THIS (START) !!!
4494 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4496 InitGameEngine_EM();
4498 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4500 InitGameEngine_SP();
4502 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4504 InitGameEngine_MM();
4508 DrawLevel(REDRAW_FIELD);
4511 // after drawing the level, correct some elements
4512 if (game.timegate_time_left == 0)
4513 CloseAllOpenTimegates();
4516 // blit playfield from scroll buffer to normal back buffer for fading in
4517 BlitScreenToBitmap(backbuffer);
4518 // !!! FIX THIS (END) !!!
4520 DrawMaskedBorder(fade_mask);
4525 // full screen redraw is required at this point in the following cases:
4526 // - special editor door undrawn when game was started from level editor
4527 // - drawing area (playfield) was changed and has to be removed completely
4528 redraw_mask = REDRAW_ALL;
4532 if (!game.restart_level)
4534 // copy default game door content to main double buffer
4536 // !!! CHECK AGAIN !!!
4537 SetPanelBackground();
4538 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4539 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4542 SetPanelBackground();
4543 SetDrawBackgroundMask(REDRAW_DOOR_1);
4545 UpdateAndDisplayGameControlValues();
4547 if (!game.restart_level)
4553 CreateGameButtons();
4558 // copy actual game door content to door double buffer for OpenDoor()
4559 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4561 OpenDoor(DOOR_OPEN_ALL);
4563 KeyboardAutoRepeatOffUnlessAutoplay();
4565 #if DEBUG_INIT_PLAYER
4566 DebugPrintPlayerStatus("Player status (final)");
4575 if (!game.restart_level && !tape.playing)
4577 LevelStats_incPlayed(level_nr);
4579 SaveLevelSetup_SeriesInfo();
4582 game.restart_level = FALSE;
4583 game.request_active = FALSE;
4585 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4586 InitGameActions_MM();
4588 SaveEngineSnapshotToListInitial();
4590 if (!game.restart_level)
4592 PlaySound(SND_GAME_STARTING);
4594 if (setup.sound_music)
4598 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4601 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4602 int actual_player_x, int actual_player_y)
4604 // this is used for non-R'n'D game engines to update certain engine values
4606 // needed to determine if sounds are played within the visible screen area
4607 scroll_x = actual_scroll_x;
4608 scroll_y = actual_scroll_y;
4610 // needed to get player position for "follow finger" playing input method
4611 local_player->jx = actual_player_x;
4612 local_player->jy = actual_player_y;
4615 void InitMovDir(int x, int y)
4617 int i, element = Tile[x][y];
4618 static int xy[4][2] =
4625 static int direction[3][4] =
4627 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4628 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4629 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4638 Tile[x][y] = EL_BUG;
4639 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4642 case EL_SPACESHIP_RIGHT:
4643 case EL_SPACESHIP_UP:
4644 case EL_SPACESHIP_LEFT:
4645 case EL_SPACESHIP_DOWN:
4646 Tile[x][y] = EL_SPACESHIP;
4647 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4650 case EL_BD_BUTTERFLY_RIGHT:
4651 case EL_BD_BUTTERFLY_UP:
4652 case EL_BD_BUTTERFLY_LEFT:
4653 case EL_BD_BUTTERFLY_DOWN:
4654 Tile[x][y] = EL_BD_BUTTERFLY;
4655 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4658 case EL_BD_FIREFLY_RIGHT:
4659 case EL_BD_FIREFLY_UP:
4660 case EL_BD_FIREFLY_LEFT:
4661 case EL_BD_FIREFLY_DOWN:
4662 Tile[x][y] = EL_BD_FIREFLY;
4663 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4666 case EL_PACMAN_RIGHT:
4668 case EL_PACMAN_LEFT:
4669 case EL_PACMAN_DOWN:
4670 Tile[x][y] = EL_PACMAN;
4671 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4674 case EL_YAMYAM_LEFT:
4675 case EL_YAMYAM_RIGHT:
4677 case EL_YAMYAM_DOWN:
4678 Tile[x][y] = EL_YAMYAM;
4679 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4682 case EL_SP_SNIKSNAK:
4683 MovDir[x][y] = MV_UP;
4686 case EL_SP_ELECTRON:
4687 MovDir[x][y] = MV_LEFT;
4694 Tile[x][y] = EL_MOLE;
4695 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4698 case EL_SPRING_LEFT:
4699 case EL_SPRING_RIGHT:
4700 Tile[x][y] = EL_SPRING;
4701 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4705 if (IS_CUSTOM_ELEMENT(element))
4707 struct ElementInfo *ei = &element_info[element];
4708 int move_direction_initial = ei->move_direction_initial;
4709 int move_pattern = ei->move_pattern;
4711 if (move_direction_initial == MV_START_PREVIOUS)
4713 if (MovDir[x][y] != MV_NONE)
4716 move_direction_initial = MV_START_AUTOMATIC;
4719 if (move_direction_initial == MV_START_RANDOM)
4720 MovDir[x][y] = 1 << RND(4);
4721 else if (move_direction_initial & MV_ANY_DIRECTION)
4722 MovDir[x][y] = move_direction_initial;
4723 else if (move_pattern == MV_ALL_DIRECTIONS ||
4724 move_pattern == MV_TURNING_LEFT ||
4725 move_pattern == MV_TURNING_RIGHT ||
4726 move_pattern == MV_TURNING_LEFT_RIGHT ||
4727 move_pattern == MV_TURNING_RIGHT_LEFT ||
4728 move_pattern == MV_TURNING_RANDOM)
4729 MovDir[x][y] = 1 << RND(4);
4730 else if (move_pattern == MV_HORIZONTAL)
4731 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4732 else if (move_pattern == MV_VERTICAL)
4733 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4734 else if (move_pattern & MV_ANY_DIRECTION)
4735 MovDir[x][y] = element_info[element].move_pattern;
4736 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4737 move_pattern == MV_ALONG_RIGHT_SIDE)
4739 // use random direction as default start direction
4740 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4741 MovDir[x][y] = 1 << RND(4);
4743 for (i = 0; i < NUM_DIRECTIONS; i++)
4745 int x1 = x + xy[i][0];
4746 int y1 = y + xy[i][1];
4748 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4750 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4751 MovDir[x][y] = direction[0][i];
4753 MovDir[x][y] = direction[1][i];
4762 MovDir[x][y] = 1 << RND(4);
4764 if (element != EL_BUG &&
4765 element != EL_SPACESHIP &&
4766 element != EL_BD_BUTTERFLY &&
4767 element != EL_BD_FIREFLY)
4770 for (i = 0; i < NUM_DIRECTIONS; i++)
4772 int x1 = x + xy[i][0];
4773 int y1 = y + xy[i][1];
4775 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4777 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4779 MovDir[x][y] = direction[0][i];
4782 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4783 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4785 MovDir[x][y] = direction[1][i];
4794 GfxDir[x][y] = MovDir[x][y];
4797 void InitAmoebaNr(int x, int y)
4800 int group_nr = AmoebaNeighbourNr(x, y);
4804 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4806 if (AmoebaCnt[i] == 0)
4814 AmoebaNr[x][y] = group_nr;
4815 AmoebaCnt[group_nr]++;
4816 AmoebaCnt2[group_nr]++;
4819 static void LevelSolved_SetFinalGameValues(void)
4821 game.time_final = (game.no_level_time_limit ? TimePlayed : TimeLeft);
4822 game.score_time_final = (level.use_step_counter ? TimePlayed :
4823 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4825 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4826 game_em.lev->score :
4827 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4831 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4832 MM_HEALTH(game_mm.laser_overload_value) :
4835 game.LevelSolved_CountingTime = game.time_final;
4836 game.LevelSolved_CountingScore = game.score_final;
4837 game.LevelSolved_CountingHealth = game.health_final;
4840 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4842 game.LevelSolved_CountingTime = time;
4843 game.LevelSolved_CountingScore = score;
4844 game.LevelSolved_CountingHealth = health;
4846 game_panel_controls[GAME_PANEL_TIME].value = time;
4847 game_panel_controls[GAME_PANEL_SCORE].value = score;
4848 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4850 DisplayGameControlValues();
4853 static void LevelSolved(void)
4855 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4856 game.players_still_needed > 0)
4859 game.LevelSolved = TRUE;
4860 game.GameOver = TRUE;
4864 // needed here to display correct panel values while player walks into exit
4865 LevelSolved_SetFinalGameValues();
4870 static int time_count_steps;
4871 static int time, time_final;
4872 static float score, score_final; // needed for time score < 10 for 10 seconds
4873 static int health, health_final;
4874 static int game_over_delay_1 = 0;
4875 static int game_over_delay_2 = 0;
4876 static int game_over_delay_3 = 0;
4877 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4878 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4880 if (!game.LevelSolved_GameWon)
4884 // do not start end game actions before the player stops moving (to exit)
4885 if (local_player->active && local_player->MovPos)
4888 // calculate final game values after player finished walking into exit
4889 LevelSolved_SetFinalGameValues();
4891 game.LevelSolved_GameWon = TRUE;
4892 game.LevelSolved_SaveTape = tape.recording;
4893 game.LevelSolved_SaveScore = !tape.playing;
4897 LevelStats_incSolved(level_nr);
4899 SaveLevelSetup_SeriesInfo();
4902 if (tape.auto_play) // tape might already be stopped here
4903 tape.auto_play_level_solved = TRUE;
4907 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4908 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4909 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4911 time = time_final = game.time_final;
4912 score = score_final = game.score_final;
4913 health = health_final = game.health_final;
4915 // update game panel values before (delayed) counting of score (if any)
4916 LevelSolved_DisplayFinalGameValues(time, score, health);
4918 // if level has time score defined, calculate new final game values
4921 int time_final_max = 999;
4922 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4923 int time_frames = 0;
4924 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4925 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4930 time_frames = time_frames_left;
4932 else if (game.no_level_time_limit && TimePlayed < time_final_max)
4934 time_final = time_final_max;
4935 time_frames = time_frames_final_max - time_frames_played;
4938 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4940 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4942 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4945 score_final += health * time_score;
4948 game.score_final = score_final;
4949 game.health_final = health_final;
4952 // if not counting score after game, immediately update game panel values
4953 if (level_editor_test_game || !setup.count_score_after_game)
4956 score = score_final;
4958 LevelSolved_DisplayFinalGameValues(time, score, health);
4961 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4963 // check if last player has left the level
4964 if (game.exit_x >= 0 &&
4967 int x = game.exit_x;
4968 int y = game.exit_y;
4969 int element = Tile[x][y];
4971 // close exit door after last player
4972 if ((game.all_players_gone &&
4973 (element == EL_EXIT_OPEN ||
4974 element == EL_SP_EXIT_OPEN ||
4975 element == EL_STEEL_EXIT_OPEN)) ||
4976 element == EL_EM_EXIT_OPEN ||
4977 element == EL_EM_STEEL_EXIT_OPEN)
4981 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4982 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4983 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4984 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4985 EL_EM_STEEL_EXIT_CLOSING);
4987 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4990 // player disappears
4991 DrawLevelField(x, y);
4994 for (i = 0; i < MAX_PLAYERS; i++)
4996 struct PlayerInfo *player = &stored_player[i];
4998 if (player->present)
5000 RemovePlayer(player);
5002 // player disappears
5003 DrawLevelField(player->jx, player->jy);
5008 PlaySound(SND_GAME_WINNING);
5011 if (setup.count_score_after_game)
5013 if (time != time_final)
5015 if (game_over_delay_1 > 0)
5017 game_over_delay_1--;
5022 int time_to_go = ABS(time_final - time);
5023 int time_count_dir = (time < time_final ? +1 : -1);
5025 if (time_to_go < time_count_steps)
5026 time_count_steps = 1;
5028 time += time_count_steps * time_count_dir;
5029 score += time_count_steps * time_score;
5031 // set final score to correct rounding differences after counting score
5032 if (time == time_final)
5033 score = score_final;
5035 LevelSolved_DisplayFinalGameValues(time, score, health);
5037 if (time == time_final)
5038 StopSound(SND_GAME_LEVELTIME_BONUS);
5039 else if (setup.sound_loops)
5040 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5042 PlaySound(SND_GAME_LEVELTIME_BONUS);
5047 if (health != health_final)
5049 if (game_over_delay_2 > 0)
5051 game_over_delay_2--;
5056 int health_count_dir = (health < health_final ? +1 : -1);
5058 health += health_count_dir;
5059 score += time_score;
5061 LevelSolved_DisplayFinalGameValues(time, score, health);
5063 if (health == health_final)
5064 StopSound(SND_GAME_LEVELTIME_BONUS);
5065 else if (setup.sound_loops)
5066 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5068 PlaySound(SND_GAME_LEVELTIME_BONUS);
5074 game.panel.active = FALSE;
5076 if (game_over_delay_3 > 0)
5078 game_over_delay_3--;
5088 // used instead of "level_nr" (needed for network games)
5089 int last_level_nr = levelset.level_nr;
5090 boolean tape_saved = FALSE;
5092 game.LevelSolved_GameEnd = TRUE;
5094 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5096 // make sure that request dialog to save tape does not open door again
5097 if (!global.use_envelope_request)
5098 CloseDoor(DOOR_CLOSE_1);
5101 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5103 // set unique basename for score tape (also saved in high score table)
5104 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5107 // if no tape is to be saved, close both doors simultaneously
5108 CloseDoor(DOOR_CLOSE_ALL);
5110 if (level_editor_test_game || score_info_tape_play)
5112 SetGameStatus(GAME_MODE_MAIN);
5119 if (!game.LevelSolved_SaveScore)
5121 SetGameStatus(GAME_MODE_MAIN);
5128 if (level_nr == leveldir_current->handicap_level)
5130 leveldir_current->handicap_level++;
5132 SaveLevelSetup_SeriesInfo();
5135 // save score and score tape before potentially erasing tape below
5136 NewHighScore(last_level_nr, tape_saved);
5138 if (setup.increment_levels &&
5139 level_nr < leveldir_current->last_level &&
5142 level_nr++; // advance to next level
5143 TapeErase(); // start with empty tape
5145 if (setup.auto_play_next_level)
5147 scores.continue_playing = TRUE;
5148 scores.next_level_nr = level_nr;
5150 LoadLevel(level_nr);
5152 SaveLevelSetup_SeriesInfo();
5156 if (scores.last_added >= 0 && setup.show_scores_after_game)
5158 SetGameStatus(GAME_MODE_SCORES);
5160 DrawHallOfFame(last_level_nr);
5162 else if (scores.continue_playing)
5164 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5168 SetGameStatus(GAME_MODE_MAIN);
5174 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5175 boolean one_score_entry_per_name)
5179 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5182 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5184 struct ScoreEntry *entry = &list->entry[i];
5185 boolean score_is_better = (new_entry->score > entry->score);
5186 boolean score_is_equal = (new_entry->score == entry->score);
5187 boolean time_is_better = (new_entry->time < entry->time);
5188 boolean time_is_equal = (new_entry->time == entry->time);
5189 boolean better_by_score = (score_is_better ||
5190 (score_is_equal && time_is_better));
5191 boolean better_by_time = (time_is_better ||
5192 (time_is_equal && score_is_better));
5193 boolean is_better = (level.rate_time_over_score ? better_by_time :
5195 boolean entry_is_empty = (entry->score == 0 &&
5198 // prevent adding server score entries if also existing in local score file
5199 // (special case: historic score entries have an empty tape basename entry)
5200 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5201 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5203 // add fields from server score entry not stored in local score entry
5204 // (currently, this means setting platform, version and country fields;
5205 // in rare cases, this may also correct an invalid score value, as
5206 // historic scores might have been truncated to 16-bit values locally)
5207 *entry = *new_entry;
5212 if (is_better || entry_is_empty)
5214 // player has made it to the hall of fame
5216 if (i < MAX_SCORE_ENTRIES - 1)
5218 int m = MAX_SCORE_ENTRIES - 1;
5221 if (one_score_entry_per_name)
5223 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5224 if (strEqual(list->entry[l].name, new_entry->name))
5227 if (m == i) // player's new highscore overwrites his old one
5231 for (l = m; l > i; l--)
5232 list->entry[l] = list->entry[l - 1];
5237 *entry = *new_entry;
5241 else if (one_score_entry_per_name &&
5242 strEqual(entry->name, new_entry->name))
5244 // player already in high score list with better score or time
5250 // special case: new score is beyond the last high score list position
5251 return MAX_SCORE_ENTRIES;
5254 void NewHighScore(int level_nr, boolean tape_saved)
5256 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5257 boolean one_per_name = FALSE;
5259 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5260 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5262 new_entry.score = game.score_final;
5263 new_entry.time = game.score_time_final;
5265 LoadScore(level_nr);
5267 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5269 if (scores.last_added >= MAX_SCORE_ENTRIES)
5271 scores.last_added = MAX_SCORE_ENTRIES - 1;
5272 scores.force_last_added = TRUE;
5274 scores.entry[scores.last_added] = new_entry;
5276 // store last added local score entry (before merging server scores)
5277 scores.last_added_local = scores.last_added;
5282 if (scores.last_added < 0)
5285 SaveScore(level_nr);
5287 // store last added local score entry (before merging server scores)
5288 scores.last_added_local = scores.last_added;
5290 if (!game.LevelSolved_SaveTape)
5293 SaveScoreTape(level_nr);
5295 if (setup.ask_for_using_api_server)
5297 setup.use_api_server =
5298 Request("Upload your score and tape to the high score server?", REQ_ASK);
5300 if (!setup.use_api_server)
5301 Request("Not using high score server! Use setup menu to enable again!",
5304 runtime.use_api_server = setup.use_api_server;
5306 // after asking for using API server once, do not ask again
5307 setup.ask_for_using_api_server = FALSE;
5309 SaveSetup_ServerSetup();
5312 SaveServerScore(level_nr, tape_saved);
5315 void MergeServerScore(void)
5317 struct ScoreEntry last_added_entry;
5318 boolean one_per_name = FALSE;
5321 if (scores.last_added >= 0)
5322 last_added_entry = scores.entry[scores.last_added];
5324 for (i = 0; i < server_scores.num_entries; i++)
5326 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5328 if (pos >= 0 && pos <= scores.last_added)
5329 scores.last_added++;
5332 if (scores.last_added >= MAX_SCORE_ENTRIES)
5334 scores.last_added = MAX_SCORE_ENTRIES - 1;
5335 scores.force_last_added = TRUE;
5337 scores.entry[scores.last_added] = last_added_entry;
5341 static int getElementMoveStepsizeExt(int x, int y, int direction)
5343 int element = Tile[x][y];
5344 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5345 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5346 int horiz_move = (dx != 0);
5347 int sign = (horiz_move ? dx : dy);
5348 int step = sign * element_info[element].move_stepsize;
5350 // special values for move stepsize for spring and things on conveyor belt
5353 if (CAN_FALL(element) &&
5354 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5355 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5356 else if (element == EL_SPRING)
5357 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5363 static int getElementMoveStepsize(int x, int y)
5365 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5368 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5370 if (player->GfxAction != action || player->GfxDir != dir)
5372 player->GfxAction = action;
5373 player->GfxDir = dir;
5375 player->StepFrame = 0;
5379 static void ResetGfxFrame(int x, int y)
5381 // profiling showed that "autotest" spends 10~20% of its time in this function
5382 if (DrawingDeactivatedField())
5385 int element = Tile[x][y];
5386 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5388 if (graphic_info[graphic].anim_global_sync)
5389 GfxFrame[x][y] = FrameCounter;
5390 else if (graphic_info[graphic].anim_global_anim_sync)
5391 GfxFrame[x][y] = getGlobalAnimSyncFrame();
5392 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5393 GfxFrame[x][y] = CustomValue[x][y];
5394 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5395 GfxFrame[x][y] = element_info[element].collect_score;
5396 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5397 GfxFrame[x][y] = ChangeDelay[x][y];
5400 static void ResetGfxAnimation(int x, int y)
5402 GfxAction[x][y] = ACTION_DEFAULT;
5403 GfxDir[x][y] = MovDir[x][y];
5406 ResetGfxFrame(x, y);
5409 static void ResetRandomAnimationValue(int x, int y)
5411 GfxRandom[x][y] = INIT_GFX_RANDOM();
5414 static void InitMovingField(int x, int y, int direction)
5416 int element = Tile[x][y];
5417 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5418 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5421 boolean is_moving_before, is_moving_after;
5423 // check if element was/is moving or being moved before/after mode change
5424 is_moving_before = (WasJustMoving[x][y] != 0);
5425 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5427 // reset animation only for moving elements which change direction of moving
5428 // or which just started or stopped moving
5429 // (else CEs with property "can move" / "not moving" are reset each frame)
5430 if (is_moving_before != is_moving_after ||
5431 direction != MovDir[x][y])
5432 ResetGfxAnimation(x, y);
5434 MovDir[x][y] = direction;
5435 GfxDir[x][y] = direction;
5437 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5438 direction == MV_DOWN && CAN_FALL(element) ?
5439 ACTION_FALLING : ACTION_MOVING);
5441 // this is needed for CEs with property "can move" / "not moving"
5443 if (is_moving_after)
5445 if (Tile[newx][newy] == EL_EMPTY)
5446 Tile[newx][newy] = EL_BLOCKED;
5448 MovDir[newx][newy] = MovDir[x][y];
5450 CustomValue[newx][newy] = CustomValue[x][y];
5452 GfxFrame[newx][newy] = GfxFrame[x][y];
5453 GfxRandom[newx][newy] = GfxRandom[x][y];
5454 GfxAction[newx][newy] = GfxAction[x][y];
5455 GfxDir[newx][newy] = GfxDir[x][y];
5459 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5461 int direction = MovDir[x][y];
5462 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5463 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5469 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5471 int direction = MovDir[x][y];
5472 int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0);
5473 int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0);
5475 *comes_from_x = oldx;
5476 *comes_from_y = oldy;
5479 static int MovingOrBlocked2Element(int x, int y)
5481 int element = Tile[x][y];
5483 if (element == EL_BLOCKED)
5487 Blocked2Moving(x, y, &oldx, &oldy);
5489 return Tile[oldx][oldy];
5495 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5497 // like MovingOrBlocked2Element(), but if element is moving
5498 // and (x, y) is the field the moving element is just leaving,
5499 // return EL_BLOCKED instead of the element value
5500 int element = Tile[x][y];
5502 if (IS_MOVING(x, y))
5504 if (element == EL_BLOCKED)
5508 Blocked2Moving(x, y, &oldx, &oldy);
5509 return Tile[oldx][oldy];
5518 static void RemoveField(int x, int y)
5520 Tile[x][y] = EL_EMPTY;
5526 CustomValue[x][y] = 0;
5529 ChangeDelay[x][y] = 0;
5530 ChangePage[x][y] = -1;
5531 Pushed[x][y] = FALSE;
5533 GfxElement[x][y] = EL_UNDEFINED;
5534 GfxAction[x][y] = ACTION_DEFAULT;
5535 GfxDir[x][y] = MV_NONE;
5538 static void RemoveMovingField(int x, int y)
5540 int oldx = x, oldy = y, newx = x, newy = y;
5541 int element = Tile[x][y];
5542 int next_element = EL_UNDEFINED;
5544 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5547 if (IS_MOVING(x, y))
5549 Moving2Blocked(x, y, &newx, &newy);
5551 if (Tile[newx][newy] != EL_BLOCKED)
5553 // element is moving, but target field is not free (blocked), but
5554 // already occupied by something different (example: acid pool);
5555 // in this case, only remove the moving field, but not the target
5557 RemoveField(oldx, oldy);
5559 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5561 TEST_DrawLevelField(oldx, oldy);
5566 else if (element == EL_BLOCKED)
5568 Blocked2Moving(x, y, &oldx, &oldy);
5569 if (!IS_MOVING(oldx, oldy))
5573 if (element == EL_BLOCKED &&
5574 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5575 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5576 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5577 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5578 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5579 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5580 next_element = get_next_element(Tile[oldx][oldy]);
5582 RemoveField(oldx, oldy);
5583 RemoveField(newx, newy);
5585 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5587 if (next_element != EL_UNDEFINED)
5588 Tile[oldx][oldy] = next_element;
5590 TEST_DrawLevelField(oldx, oldy);
5591 TEST_DrawLevelField(newx, newy);
5594 void DrawDynamite(int x, int y)
5596 int sx = SCREENX(x), sy = SCREENY(y);
5597 int graphic = el2img(Tile[x][y]);
5600 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5603 if (IS_WALKABLE_INSIDE(Back[x][y]))
5607 DrawLevelElement(x, y, Back[x][y]);
5608 else if (Store[x][y])
5609 DrawLevelElement(x, y, Store[x][y]);
5610 else if (game.use_masked_elements)
5611 DrawLevelElement(x, y, EL_EMPTY);
5613 frame = getGraphicAnimationFrameXY(graphic, x, y);
5615 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5616 DrawGraphicThruMask(sx, sy, graphic, frame);
5618 DrawGraphic(sx, sy, graphic, frame);
5621 static void CheckDynamite(int x, int y)
5623 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5627 if (MovDelay[x][y] != 0)
5630 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5636 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5641 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5643 boolean num_checked_players = 0;
5646 for (i = 0; i < MAX_PLAYERS; i++)
5648 if (stored_player[i].active)
5650 int sx = stored_player[i].jx;
5651 int sy = stored_player[i].jy;
5653 if (num_checked_players == 0)
5660 *sx1 = MIN(*sx1, sx);
5661 *sy1 = MIN(*sy1, sy);
5662 *sx2 = MAX(*sx2, sx);
5663 *sy2 = MAX(*sy2, sy);
5666 num_checked_players++;
5671 static boolean checkIfAllPlayersFitToScreen_RND(void)
5673 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5675 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5677 return (sx2 - sx1 < SCR_FIELDX &&
5678 sy2 - sy1 < SCR_FIELDY);
5681 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5683 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5685 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5687 *sx = (sx1 + sx2) / 2;
5688 *sy = (sy1 + sy2) / 2;
5691 static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
5692 boolean center_screen, boolean quick_relocation)
5694 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5695 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5696 boolean no_delay = (tape.warp_forward);
5697 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5698 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5699 int new_scroll_x, new_scroll_y;
5701 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5703 // case 1: quick relocation inside visible screen (without scrolling)
5710 if (!level.shifted_relocation || center_screen)
5712 // relocation _with_ centering of screen
5714 new_scroll_x = SCROLL_POSITION_X(x);
5715 new_scroll_y = SCROLL_POSITION_Y(y);
5719 // relocation _without_ centering of screen
5721 // apply distance between old and new player position to scroll position
5722 int shifted_scroll_x = scroll_x + (x - old_x);
5723 int shifted_scroll_y = scroll_y + (y - old_y);
5725 // make sure that shifted scroll position does not scroll beyond screen
5726 new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
5727 new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
5729 // special case for teleporting from one end of the playfield to the other
5730 // (this kludge prevents the destination area to be shifted by half a tile
5731 // against the source destination for even screen width or screen height;
5732 // probably most useful when used with high "game.forced_scroll_delay_value"
5733 // in combination with "game.forced_scroll_x" and "game.forced_scroll_y")
5734 if (quick_relocation)
5736 if (EVEN(SCR_FIELDX))
5738 // relocate (teleport) between left and right border (half or full)
5739 if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1)
5740 new_scroll_x = SBX_Right;
5741 else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right)
5742 new_scroll_x = SBX_Right - 1;
5743 else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1)
5744 new_scroll_x = SBX_Left;
5745 else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left)
5746 new_scroll_x = SBX_Left + 1;
5749 if (EVEN(SCR_FIELDY))
5751 // relocate (teleport) between top and bottom border (half or full)
5752 if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1)
5753 new_scroll_y = SBY_Lower;
5754 else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower)
5755 new_scroll_y = SBY_Lower - 1;
5756 else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1)
5757 new_scroll_y = SBY_Upper;
5758 else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper)
5759 new_scroll_y = SBY_Upper + 1;
5764 if (quick_relocation)
5766 // case 2: quick relocation (redraw without visible scrolling)
5768 scroll_x = new_scroll_x;
5769 scroll_y = new_scroll_y;
5776 // case 3: visible relocation (with scrolling to new position)
5778 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5780 SetVideoFrameDelay(wait_delay_value);
5782 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5784 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5785 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5787 if (dx == 0 && dy == 0) // no scrolling needed at all
5793 // set values for horizontal/vertical screen scrolling (half tile size)
5794 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5795 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5796 int pos_x = dx * TILEX / 2;
5797 int pos_y = dy * TILEY / 2;
5798 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5799 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5801 ScrollLevel(dx, dy);
5804 // scroll in two steps of half tile size to make things smoother
5805 BlitScreenToBitmapExt_RND(window, fx, fy);
5807 // scroll second step to align at full tile size
5808 BlitScreenToBitmap(window);
5814 SetVideoFrameDelay(frame_delay_value_old);
5817 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5819 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5820 int player_nr = GET_PLAYER_NR(el_player);
5821 struct PlayerInfo *player = &stored_player[player_nr];
5822 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5823 boolean no_delay = (tape.warp_forward);
5824 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5825 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5826 int old_jx = player->jx;
5827 int old_jy = player->jy;
5828 int old_element = Tile[old_jx][old_jy];
5829 int element = Tile[jx][jy];
5830 boolean player_relocated = (old_jx != jx || old_jy != jy);
5832 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5833 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5834 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5835 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5836 int leave_side_horiz = move_dir_horiz;
5837 int leave_side_vert = move_dir_vert;
5838 int enter_side = enter_side_horiz | enter_side_vert;
5839 int leave_side = leave_side_horiz | leave_side_vert;
5841 if (player->buried) // do not reanimate dead player
5844 if (!player_relocated) // no need to relocate the player
5847 if (IS_PLAYER(jx, jy)) // player already placed at new position
5849 RemoveField(jx, jy); // temporarily remove newly placed player
5850 DrawLevelField(jx, jy);
5853 if (player->present)
5855 while (player->MovPos)
5857 ScrollPlayer(player, SCROLL_GO_ON);
5858 ScrollScreen(NULL, SCROLL_GO_ON);
5860 AdvanceFrameAndPlayerCounters(player->index_nr);
5864 BackToFront_WithFrameDelay(wait_delay_value);
5867 DrawPlayer(player); // needed here only to cleanup last field
5868 DrawLevelField(player->jx, player->jy); // remove player graphic
5870 player->is_moving = FALSE;
5873 if (IS_CUSTOM_ELEMENT(old_element))
5874 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5876 player->index_bit, leave_side);
5878 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5880 player->index_bit, leave_side);
5882 Tile[jx][jy] = el_player;
5883 InitPlayerField(jx, jy, el_player, TRUE);
5885 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5886 possible that the relocation target field did not contain a player element,
5887 but a walkable element, to which the new player was relocated -- in this
5888 case, restore that (already initialized!) element on the player field */
5889 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5891 Tile[jx][jy] = element; // restore previously existing element
5894 // only visually relocate centered player
5895 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy,
5896 FALSE, level.instant_relocation);
5898 TestIfPlayerTouchesBadThing(jx, jy);
5899 TestIfPlayerTouchesCustomElement(jx, jy);
5901 if (IS_CUSTOM_ELEMENT(element))
5902 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5903 player->index_bit, enter_side);
5905 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5906 player->index_bit, enter_side);
5908 if (player->is_switching)
5910 /* ensure that relocation while still switching an element does not cause
5911 a new element to be treated as also switched directly after relocation
5912 (this is important for teleporter switches that teleport the player to
5913 a place where another teleporter switch is in the same direction, which
5914 would then incorrectly be treated as immediately switched before the
5915 direction key that caused the switch was released) */
5917 player->switch_x += jx - old_jx;
5918 player->switch_y += jy - old_jy;
5922 static void Explode(int ex, int ey, int phase, int mode)
5928 if (game.explosions_delayed)
5930 ExplodeField[ex][ey] = mode;
5934 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5936 int center_element = Tile[ex][ey];
5937 int ce_value = CustomValue[ex][ey];
5938 int ce_score = element_info[center_element].collect_score;
5939 int artwork_element, explosion_element; // set these values later
5941 // remove things displayed in background while burning dynamite
5942 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5945 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5947 // put moving element to center field (and let it explode there)
5948 center_element = MovingOrBlocked2Element(ex, ey);
5949 RemoveMovingField(ex, ey);
5950 Tile[ex][ey] = center_element;
5953 // now "center_element" is finally determined -- set related values now
5954 artwork_element = center_element; // for custom player artwork
5955 explosion_element = center_element; // for custom player artwork
5957 if (IS_PLAYER(ex, ey))
5959 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5961 artwork_element = stored_player[player_nr].artwork_element;
5963 if (level.use_explosion_element[player_nr])
5965 explosion_element = level.explosion_element[player_nr];
5966 artwork_element = explosion_element;
5970 if (mode == EX_TYPE_NORMAL ||
5971 mode == EX_TYPE_CENTER ||
5972 mode == EX_TYPE_CROSS)
5973 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5975 last_phase = element_info[explosion_element].explosion_delay + 1;
5977 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5979 int xx = x - ex + 1;
5980 int yy = y - ey + 1;
5983 if (!IN_LEV_FIELD(x, y) ||
5984 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5985 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5988 element = Tile[x][y];
5990 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5992 element = MovingOrBlocked2Element(x, y);
5994 if (!IS_EXPLOSION_PROOF(element))
5995 RemoveMovingField(x, y);
5998 // indestructible elements can only explode in center (but not flames)
5999 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
6000 mode == EX_TYPE_BORDER)) ||
6001 element == EL_FLAMES)
6004 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
6005 behaviour, for example when touching a yamyam that explodes to rocks
6006 with active deadly shield, a rock is created under the player !!! */
6007 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
6009 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
6010 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
6011 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
6013 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
6016 if (IS_ACTIVE_BOMB(element))
6018 // re-activate things under the bomb like gate or penguin
6019 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
6026 // save walkable background elements while explosion on same tile
6027 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
6028 (x != ex || y != ey || mode == EX_TYPE_BORDER))
6029 Back[x][y] = element;
6031 // ignite explodable elements reached by other explosion
6032 if (element == EL_EXPLOSION)
6033 element = Store2[x][y];
6035 if (AmoebaNr[x][y] &&
6036 (element == EL_AMOEBA_FULL ||
6037 element == EL_BD_AMOEBA ||
6038 element == EL_AMOEBA_GROWING))
6040 AmoebaCnt[AmoebaNr[x][y]]--;
6041 AmoebaCnt2[AmoebaNr[x][y]]--;
6046 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
6048 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
6050 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
6052 if (PLAYERINFO(ex, ey)->use_murphy)
6053 Store[x][y] = EL_EMPTY;
6056 // !!! check this case -- currently needed for rnd_rado_negundo_v,
6057 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
6058 else if (IS_PLAYER_ELEMENT(center_element))
6059 Store[x][y] = EL_EMPTY;
6060 else if (center_element == EL_YAMYAM)
6061 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
6062 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
6063 Store[x][y] = element_info[center_element].content.e[xx][yy];
6065 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
6066 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
6067 // otherwise) -- FIX THIS !!!
6068 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
6069 Store[x][y] = element_info[element].content.e[1][1];
6071 else if (!CAN_EXPLODE(element))
6072 Store[x][y] = element_info[element].content.e[1][1];
6075 Store[x][y] = EL_EMPTY;
6077 if (IS_CUSTOM_ELEMENT(center_element))
6078 Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value :
6079 Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score :
6080 Store[x][y] >= EL_PREV_CE_8 &&
6081 Store[x][y] <= EL_NEXT_CE_8 ?
6082 RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) :
6085 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
6086 center_element == EL_AMOEBA_TO_DIAMOND)
6087 Store2[x][y] = element;
6089 Tile[x][y] = EL_EXPLOSION;
6090 GfxElement[x][y] = artwork_element;
6092 ExplodePhase[x][y] = 1;
6093 ExplodeDelay[x][y] = last_phase;
6098 if (center_element == EL_YAMYAM)
6099 game.yamyam_content_nr =
6100 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
6112 GfxFrame[x][y] = 0; // restart explosion animation
6114 last_phase = ExplodeDelay[x][y];
6116 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
6118 // this can happen if the player leaves an explosion just in time
6119 if (GfxElement[x][y] == EL_UNDEFINED)
6120 GfxElement[x][y] = EL_EMPTY;
6122 border_element = Store2[x][y];
6123 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6124 border_element = StorePlayer[x][y];
6126 if (phase == element_info[border_element].ignition_delay ||
6127 phase == last_phase)
6129 boolean border_explosion = FALSE;
6131 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6132 !PLAYER_EXPLOSION_PROTECTED(x, y))
6134 KillPlayerUnlessExplosionProtected(x, y);
6135 border_explosion = TRUE;
6137 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6139 Tile[x][y] = Store2[x][y];
6142 border_explosion = TRUE;
6144 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6146 AmoebaToDiamond(x, y);
6148 border_explosion = TRUE;
6151 // if an element just explodes due to another explosion (chain-reaction),
6152 // do not immediately end the new explosion when it was the last frame of
6153 // the explosion (as it would be done in the following "if"-statement!)
6154 if (border_explosion && phase == last_phase)
6158 // this can happen if the player was just killed by an explosion
6159 if (GfxElement[x][y] == EL_UNDEFINED)
6160 GfxElement[x][y] = EL_EMPTY;
6162 if (phase == last_phase)
6166 element = Tile[x][y] = Store[x][y];
6167 Store[x][y] = Store2[x][y] = 0;
6168 GfxElement[x][y] = EL_UNDEFINED;
6170 // player can escape from explosions and might therefore be still alive
6171 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6172 element <= EL_PLAYER_IS_EXPLODING_4)
6174 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6175 int explosion_element = EL_PLAYER_1 + player_nr;
6176 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6177 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6179 if (level.use_explosion_element[player_nr])
6180 explosion_element = level.explosion_element[player_nr];
6182 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6183 element_info[explosion_element].content.e[xx][yy]);
6186 // restore probably existing indestructible background element
6187 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6188 element = Tile[x][y] = Back[x][y];
6191 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6192 GfxDir[x][y] = MV_NONE;
6193 ChangeDelay[x][y] = 0;
6194 ChangePage[x][y] = -1;
6196 CustomValue[x][y] = 0;
6198 InitField_WithBug2(x, y, FALSE);
6200 TEST_DrawLevelField(x, y);
6202 TestIfElementTouchesCustomElement(x, y);
6204 if (GFX_CRUMBLED(element))
6205 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6207 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6208 StorePlayer[x][y] = 0;
6210 if (IS_PLAYER_ELEMENT(element))
6211 RelocatePlayer(x, y, element);
6213 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6215 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6216 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6219 TEST_DrawLevelFieldCrumbled(x, y);
6221 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6223 DrawLevelElement(x, y, Back[x][y]);
6224 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6226 else if (IS_WALKABLE_UNDER(Back[x][y]))
6228 DrawLevelGraphic(x, y, graphic, frame);
6229 DrawLevelElementThruMask(x, y, Back[x][y]);
6231 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6232 DrawLevelGraphic(x, y, graphic, frame);
6236 static void DynaExplode(int ex, int ey)
6239 int dynabomb_element = Tile[ex][ey];
6240 int dynabomb_size = 1;
6241 boolean dynabomb_xl = FALSE;
6242 struct PlayerInfo *player;
6243 struct XY *xy = xy_topdown;
6245 if (IS_ACTIVE_BOMB(dynabomb_element))
6247 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6248 dynabomb_size = player->dynabomb_size;
6249 dynabomb_xl = player->dynabomb_xl;
6250 player->dynabombs_left++;
6253 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6255 for (i = 0; i < NUM_DIRECTIONS; i++)
6257 for (j = 1; j <= dynabomb_size; j++)
6259 int x = ex + j * xy[i].x;
6260 int y = ey + j * xy[i].y;
6263 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6266 element = Tile[x][y];
6268 // do not restart explosions of fields with active bombs
6269 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6272 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6274 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6275 !IS_DIGGABLE(element) && !dynabomb_xl)
6281 void Bang(int x, int y)
6283 int element = MovingOrBlocked2Element(x, y);
6284 int explosion_type = EX_TYPE_NORMAL;
6286 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6288 struct PlayerInfo *player = PLAYERINFO(x, y);
6290 element = Tile[x][y] = player->initial_element;
6292 if (level.use_explosion_element[player->index_nr])
6294 int explosion_element = level.explosion_element[player->index_nr];
6296 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6297 explosion_type = EX_TYPE_CROSS;
6298 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6299 explosion_type = EX_TYPE_CENTER;
6307 case EL_BD_BUTTERFLY:
6310 case EL_DARK_YAMYAM:
6314 RaiseScoreElement(element);
6317 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6318 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6319 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6320 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6321 case EL_DYNABOMB_INCREASE_NUMBER:
6322 case EL_DYNABOMB_INCREASE_SIZE:
6323 case EL_DYNABOMB_INCREASE_POWER:
6324 explosion_type = EX_TYPE_DYNA;
6327 case EL_DC_LANDMINE:
6328 explosion_type = EX_TYPE_CENTER;
6333 case EL_LAMP_ACTIVE:
6334 case EL_AMOEBA_TO_DIAMOND:
6335 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6336 explosion_type = EX_TYPE_CENTER;
6340 if (element_info[element].explosion_type == EXPLODES_CROSS)
6341 explosion_type = EX_TYPE_CROSS;
6342 else if (element_info[element].explosion_type == EXPLODES_1X1)
6343 explosion_type = EX_TYPE_CENTER;
6347 if (explosion_type == EX_TYPE_DYNA)
6350 Explode(x, y, EX_PHASE_START, explosion_type);
6352 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6355 static void SplashAcid(int x, int y)
6357 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6358 (!IN_LEV_FIELD(x - 1, y - 2) ||
6359 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6360 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6362 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6363 (!IN_LEV_FIELD(x + 1, y - 2) ||
6364 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6365 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6367 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6370 static void InitBeltMovement(void)
6372 static int belt_base_element[4] =
6374 EL_CONVEYOR_BELT_1_LEFT,
6375 EL_CONVEYOR_BELT_2_LEFT,
6376 EL_CONVEYOR_BELT_3_LEFT,
6377 EL_CONVEYOR_BELT_4_LEFT
6379 static int belt_base_active_element[4] =
6381 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6382 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6383 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6384 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6389 // set frame order for belt animation graphic according to belt direction
6390 for (i = 0; i < NUM_BELTS; i++)
6394 for (j = 0; j < NUM_BELT_PARTS; j++)
6396 int element = belt_base_active_element[belt_nr] + j;
6397 int graphic_1 = el2img(element);
6398 int graphic_2 = el2panelimg(element);
6400 if (game.belt_dir[i] == MV_LEFT)
6402 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6403 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6407 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6408 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6413 SCAN_PLAYFIELD(x, y)
6415 int element = Tile[x][y];
6417 for (i = 0; i < NUM_BELTS; i++)
6419 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6421 int e_belt_nr = getBeltNrFromBeltElement(element);
6424 if (e_belt_nr == belt_nr)
6426 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6428 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6435 static void ToggleBeltSwitch(int x, int y)
6437 static int belt_base_element[4] =
6439 EL_CONVEYOR_BELT_1_LEFT,
6440 EL_CONVEYOR_BELT_2_LEFT,
6441 EL_CONVEYOR_BELT_3_LEFT,
6442 EL_CONVEYOR_BELT_4_LEFT
6444 static int belt_base_active_element[4] =
6446 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6447 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6448 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6449 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6451 static int belt_base_switch_element[4] =
6453 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6454 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6455 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6456 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6458 static int belt_move_dir[4] =
6466 int element = Tile[x][y];
6467 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6468 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6469 int belt_dir = belt_move_dir[belt_dir_nr];
6472 if (!IS_BELT_SWITCH(element))
6475 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6476 game.belt_dir[belt_nr] = belt_dir;
6478 if (belt_dir_nr == 3)
6481 // set frame order for belt animation graphic according to belt direction
6482 for (i = 0; i < NUM_BELT_PARTS; i++)
6484 int element = belt_base_active_element[belt_nr] + i;
6485 int graphic_1 = el2img(element);
6486 int graphic_2 = el2panelimg(element);
6488 if (belt_dir == MV_LEFT)
6490 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6491 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6495 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6496 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6500 SCAN_PLAYFIELD(xx, yy)
6502 int element = Tile[xx][yy];
6504 if (IS_BELT_SWITCH(element))
6506 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6508 if (e_belt_nr == belt_nr)
6510 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6511 TEST_DrawLevelField(xx, yy);
6514 else if (IS_BELT(element) && belt_dir != MV_NONE)
6516 int e_belt_nr = getBeltNrFromBeltElement(element);
6518 if (e_belt_nr == belt_nr)
6520 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6522 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6523 TEST_DrawLevelField(xx, yy);
6526 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6528 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6530 if (e_belt_nr == belt_nr)
6532 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6534 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6535 TEST_DrawLevelField(xx, yy);
6541 static void ToggleSwitchgateSwitch(void)
6545 game.switchgate_pos = !game.switchgate_pos;
6547 SCAN_PLAYFIELD(xx, yy)
6549 int element = Tile[xx][yy];
6551 if (element == EL_SWITCHGATE_SWITCH_UP)
6553 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6554 TEST_DrawLevelField(xx, yy);
6556 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6558 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6559 TEST_DrawLevelField(xx, yy);
6561 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6563 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6564 TEST_DrawLevelField(xx, yy);
6566 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6568 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6569 TEST_DrawLevelField(xx, yy);
6571 else if (element == EL_SWITCHGATE_OPEN ||
6572 element == EL_SWITCHGATE_OPENING)
6574 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6576 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6578 else if (element == EL_SWITCHGATE_CLOSED ||
6579 element == EL_SWITCHGATE_CLOSING)
6581 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6583 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6588 static int getInvisibleActiveFromInvisibleElement(int element)
6590 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6591 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6592 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6596 static int getInvisibleFromInvisibleActiveElement(int element)
6598 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6599 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6600 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6604 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6608 SCAN_PLAYFIELD(x, y)
6610 int element = Tile[x][y];
6612 if (element == EL_LIGHT_SWITCH &&
6613 game.light_time_left > 0)
6615 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6616 TEST_DrawLevelField(x, y);
6618 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6619 game.light_time_left == 0)
6621 Tile[x][y] = EL_LIGHT_SWITCH;
6622 TEST_DrawLevelField(x, y);
6624 else if (element == EL_EMC_DRIPPER &&
6625 game.light_time_left > 0)
6627 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6628 TEST_DrawLevelField(x, y);
6630 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6631 game.light_time_left == 0)
6633 Tile[x][y] = EL_EMC_DRIPPER;
6634 TEST_DrawLevelField(x, y);
6636 else if (element == EL_INVISIBLE_STEELWALL ||
6637 element == EL_INVISIBLE_WALL ||
6638 element == EL_INVISIBLE_SAND)
6640 if (game.light_time_left > 0)
6641 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6643 TEST_DrawLevelField(x, y);
6645 // uncrumble neighbour fields, if needed
6646 if (element == EL_INVISIBLE_SAND)
6647 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6649 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6650 element == EL_INVISIBLE_WALL_ACTIVE ||
6651 element == EL_INVISIBLE_SAND_ACTIVE)
6653 if (game.light_time_left == 0)
6654 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6656 TEST_DrawLevelField(x, y);
6658 // re-crumble neighbour fields, if needed
6659 if (element == EL_INVISIBLE_SAND)
6660 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6665 static void RedrawAllInvisibleElementsForLenses(void)
6669 SCAN_PLAYFIELD(x, y)
6671 int element = Tile[x][y];
6673 if (element == EL_EMC_DRIPPER &&
6674 game.lenses_time_left > 0)
6676 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6677 TEST_DrawLevelField(x, y);
6679 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6680 game.lenses_time_left == 0)
6682 Tile[x][y] = EL_EMC_DRIPPER;
6683 TEST_DrawLevelField(x, y);
6685 else if (element == EL_INVISIBLE_STEELWALL ||
6686 element == EL_INVISIBLE_WALL ||
6687 element == EL_INVISIBLE_SAND)
6689 if (game.lenses_time_left > 0)
6690 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6692 TEST_DrawLevelField(x, y);
6694 // uncrumble neighbour fields, if needed
6695 if (element == EL_INVISIBLE_SAND)
6696 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6698 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6699 element == EL_INVISIBLE_WALL_ACTIVE ||
6700 element == EL_INVISIBLE_SAND_ACTIVE)
6702 if (game.lenses_time_left == 0)
6703 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6705 TEST_DrawLevelField(x, y);
6707 // re-crumble neighbour fields, if needed
6708 if (element == EL_INVISIBLE_SAND)
6709 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6714 static void RedrawAllInvisibleElementsForMagnifier(void)
6718 SCAN_PLAYFIELD(x, y)
6720 int element = Tile[x][y];
6722 if (element == EL_EMC_FAKE_GRASS &&
6723 game.magnify_time_left > 0)
6725 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6726 TEST_DrawLevelField(x, y);
6728 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6729 game.magnify_time_left == 0)
6731 Tile[x][y] = EL_EMC_FAKE_GRASS;
6732 TEST_DrawLevelField(x, y);
6734 else if (IS_GATE_GRAY(element) &&
6735 game.magnify_time_left > 0)
6737 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6738 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6739 IS_EM_GATE_GRAY(element) ?
6740 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6741 IS_EMC_GATE_GRAY(element) ?
6742 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6743 IS_DC_GATE_GRAY(element) ?
6744 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6746 TEST_DrawLevelField(x, y);
6748 else if (IS_GATE_GRAY_ACTIVE(element) &&
6749 game.magnify_time_left == 0)
6751 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6752 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6753 IS_EM_GATE_GRAY_ACTIVE(element) ?
6754 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6755 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6756 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6757 IS_DC_GATE_GRAY_ACTIVE(element) ?
6758 EL_DC_GATE_WHITE_GRAY :
6760 TEST_DrawLevelField(x, y);
6765 static void ToggleLightSwitch(int x, int y)
6767 int element = Tile[x][y];
6769 game.light_time_left =
6770 (element == EL_LIGHT_SWITCH ?
6771 level.time_light * FRAMES_PER_SECOND : 0);
6773 RedrawAllLightSwitchesAndInvisibleElements();
6776 static void ActivateTimegateSwitch(int x, int y)
6780 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6782 SCAN_PLAYFIELD(xx, yy)
6784 int element = Tile[xx][yy];
6786 if (element == EL_TIMEGATE_CLOSED ||
6787 element == EL_TIMEGATE_CLOSING)
6789 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6790 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6794 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6796 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6797 TEST_DrawLevelField(xx, yy);
6803 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6804 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6807 static void Impact(int x, int y)
6809 boolean last_line = (y == lev_fieldy - 1);
6810 boolean object_hit = FALSE;
6811 boolean impact = (last_line || object_hit);
6812 int element = Tile[x][y];
6813 int smashed = EL_STEELWALL;
6815 if (!last_line) // check if element below was hit
6817 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6820 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6821 MovDir[x][y + 1] != MV_DOWN ||
6822 MovPos[x][y + 1] <= TILEY / 2));
6824 // do not smash moving elements that left the smashed field in time
6825 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6826 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6829 #if USE_QUICKSAND_IMPACT_BUGFIX
6830 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6832 RemoveMovingField(x, y + 1);
6833 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6834 Tile[x][y + 2] = EL_ROCK;
6835 TEST_DrawLevelField(x, y + 2);
6840 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6842 RemoveMovingField(x, y + 1);
6843 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6844 Tile[x][y + 2] = EL_ROCK;
6845 TEST_DrawLevelField(x, y + 2);
6852 smashed = MovingOrBlocked2Element(x, y + 1);
6854 impact = (last_line || object_hit);
6857 if (!last_line && smashed == EL_ACID) // element falls into acid
6859 SplashAcid(x, y + 1);
6863 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6864 // only reset graphic animation if graphic really changes after impact
6866 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6868 ResetGfxAnimation(x, y);
6869 TEST_DrawLevelField(x, y);
6872 if (impact && CAN_EXPLODE_IMPACT(element))
6877 else if (impact && element == EL_PEARL &&
6878 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6880 ResetGfxAnimation(x, y);
6882 Tile[x][y] = EL_PEARL_BREAKING;
6883 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6886 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6888 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6893 if (impact && element == EL_AMOEBA_DROP)
6895 if (object_hit && IS_PLAYER(x, y + 1))
6896 KillPlayerUnlessEnemyProtected(x, y + 1);
6897 else if (object_hit && smashed == EL_PENGUIN)
6901 Tile[x][y] = EL_AMOEBA_GROWING;
6902 Store[x][y] = EL_AMOEBA_WET;
6904 ResetRandomAnimationValue(x, y);
6909 if (object_hit) // check which object was hit
6911 if ((CAN_PASS_MAGIC_WALL(element) &&
6912 (smashed == EL_MAGIC_WALL ||
6913 smashed == EL_BD_MAGIC_WALL)) ||
6914 (CAN_PASS_DC_MAGIC_WALL(element) &&
6915 smashed == EL_DC_MAGIC_WALL))
6918 int activated_magic_wall =
6919 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6920 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6921 EL_DC_MAGIC_WALL_ACTIVE);
6923 // activate magic wall / mill
6924 SCAN_PLAYFIELD(xx, yy)
6926 if (Tile[xx][yy] == smashed)
6927 Tile[xx][yy] = activated_magic_wall;
6930 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6931 game.magic_wall_active = TRUE;
6933 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6934 SND_MAGIC_WALL_ACTIVATING :
6935 smashed == EL_BD_MAGIC_WALL ?
6936 SND_BD_MAGIC_WALL_ACTIVATING :
6937 SND_DC_MAGIC_WALL_ACTIVATING));
6940 if (IS_PLAYER(x, y + 1))
6942 if (CAN_SMASH_PLAYER(element))
6944 KillPlayerUnlessEnemyProtected(x, y + 1);
6948 else if (smashed == EL_PENGUIN)
6950 if (CAN_SMASH_PLAYER(element))
6956 else if (element == EL_BD_DIAMOND)
6958 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6964 else if (((element == EL_SP_INFOTRON ||
6965 element == EL_SP_ZONK) &&
6966 (smashed == EL_SP_SNIKSNAK ||
6967 smashed == EL_SP_ELECTRON ||
6968 smashed == EL_SP_DISK_ORANGE)) ||
6969 (element == EL_SP_INFOTRON &&
6970 smashed == EL_SP_DISK_YELLOW))
6975 else if (CAN_SMASH_EVERYTHING(element))
6977 if (IS_CLASSIC_ENEMY(smashed) ||
6978 CAN_EXPLODE_SMASHED(smashed))
6983 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6985 if (smashed == EL_LAMP ||
6986 smashed == EL_LAMP_ACTIVE)
6991 else if (smashed == EL_NUT)
6993 Tile[x][y + 1] = EL_NUT_BREAKING;
6994 PlayLevelSound(x, y, SND_NUT_BREAKING);
6995 RaiseScoreElement(EL_NUT);
6998 else if (smashed == EL_PEARL)
7000 ResetGfxAnimation(x, y);
7002 Tile[x][y + 1] = EL_PEARL_BREAKING;
7003 PlayLevelSound(x, y, SND_PEARL_BREAKING);
7006 else if (smashed == EL_DIAMOND)
7008 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
7009 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
7012 else if (IS_BELT_SWITCH(smashed))
7014 ToggleBeltSwitch(x, y + 1);
7016 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
7017 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
7018 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
7019 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
7021 ToggleSwitchgateSwitch();
7023 else if (smashed == EL_LIGHT_SWITCH ||
7024 smashed == EL_LIGHT_SWITCH_ACTIVE)
7026 ToggleLightSwitch(x, y + 1);
7030 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7032 CheckElementChangeBySide(x, y + 1, smashed, element,
7033 CE_SWITCHED, CH_SIDE_TOP);
7034 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
7040 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7045 // play sound of magic wall / mill
7047 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7048 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
7049 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
7051 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7052 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
7053 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7054 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
7055 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7056 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
7061 // play sound of object that hits the ground
7062 if (last_line || object_hit)
7063 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
7066 static void TurnRoundExt(int x, int y)
7078 { 0, 0 }, { 0, 0 }, { 0, 0 },
7083 int left, right, back;
7087 { MV_DOWN, MV_UP, MV_RIGHT },
7088 { MV_UP, MV_DOWN, MV_LEFT },
7090 { MV_LEFT, MV_RIGHT, MV_DOWN },
7094 { MV_RIGHT, MV_LEFT, MV_UP }
7097 int element = Tile[x][y];
7098 int move_pattern = element_info[element].move_pattern;
7100 int old_move_dir = MovDir[x][y];
7101 int left_dir = turn[old_move_dir].left;
7102 int right_dir = turn[old_move_dir].right;
7103 int back_dir = turn[old_move_dir].back;
7105 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
7106 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
7107 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
7108 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
7110 int left_x = x + left_dx, left_y = y + left_dy;
7111 int right_x = x + right_dx, right_y = y + right_dy;
7112 int move_x = x + move_dx, move_y = y + move_dy;
7116 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
7118 TestIfBadThingTouchesOtherBadThing(x, y);
7120 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
7121 MovDir[x][y] = right_dir;
7122 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7123 MovDir[x][y] = left_dir;
7125 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7127 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7130 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7132 TestIfBadThingTouchesOtherBadThing(x, y);
7134 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7135 MovDir[x][y] = left_dir;
7136 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7137 MovDir[x][y] = right_dir;
7139 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7141 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7144 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7146 TestIfBadThingTouchesOtherBadThing(x, y);
7148 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7149 MovDir[x][y] = left_dir;
7150 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7151 MovDir[x][y] = right_dir;
7153 if (MovDir[x][y] != old_move_dir)
7156 else if (element == EL_YAMYAM)
7158 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7159 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7161 if (can_turn_left && can_turn_right)
7162 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7163 else if (can_turn_left)
7164 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7165 else if (can_turn_right)
7166 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7168 MovDir[x][y] = back_dir;
7170 MovDelay[x][y] = 16 + 16 * RND(3);
7172 else if (element == EL_DARK_YAMYAM)
7174 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7176 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
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_PACMAN)
7192 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7193 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7195 if (can_turn_left && can_turn_right)
7196 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7197 else if (can_turn_left)
7198 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7199 else if (can_turn_right)
7200 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7202 MovDir[x][y] = back_dir;
7204 MovDelay[x][y] = 6 + RND(40);
7206 else if (element == EL_PIG)
7208 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7209 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7210 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7211 boolean should_turn_left, should_turn_right, should_move_on;
7213 int rnd = RND(rnd_value);
7215 should_turn_left = (can_turn_left &&
7217 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7218 y + back_dy + left_dy)));
7219 should_turn_right = (can_turn_right &&
7221 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7222 y + back_dy + right_dy)));
7223 should_move_on = (can_move_on &&
7226 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7227 y + move_dy + left_dy) ||
7228 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7229 y + move_dy + right_dy)));
7231 if (should_turn_left || should_turn_right || should_move_on)
7233 if (should_turn_left && should_turn_right && should_move_on)
7234 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7235 rnd < 2 * rnd_value / 3 ? right_dir :
7237 else if (should_turn_left && should_turn_right)
7238 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7239 else if (should_turn_left && should_move_on)
7240 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7241 else if (should_turn_right && should_move_on)
7242 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7243 else if (should_turn_left)
7244 MovDir[x][y] = left_dir;
7245 else if (should_turn_right)
7246 MovDir[x][y] = right_dir;
7247 else if (should_move_on)
7248 MovDir[x][y] = old_move_dir;
7250 else if (can_move_on && rnd > rnd_value / 8)
7251 MovDir[x][y] = old_move_dir;
7252 else if (can_turn_left && can_turn_right)
7253 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7254 else if (can_turn_left && rnd > rnd_value / 8)
7255 MovDir[x][y] = left_dir;
7256 else if (can_turn_right && rnd > rnd_value/8)
7257 MovDir[x][y] = right_dir;
7259 MovDir[x][y] = back_dir;
7261 xx = x + move_xy[MovDir[x][y]].dx;
7262 yy = y + move_xy[MovDir[x][y]].dy;
7264 if (!IN_LEV_FIELD(xx, yy) ||
7265 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7266 MovDir[x][y] = old_move_dir;
7270 else if (element == EL_DRAGON)
7272 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7273 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7274 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7276 int rnd = RND(rnd_value);
7278 if (can_move_on && rnd > rnd_value / 8)
7279 MovDir[x][y] = old_move_dir;
7280 else if (can_turn_left && can_turn_right)
7281 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7282 else if (can_turn_left && rnd > rnd_value / 8)
7283 MovDir[x][y] = left_dir;
7284 else if (can_turn_right && rnd > rnd_value / 8)
7285 MovDir[x][y] = right_dir;
7287 MovDir[x][y] = back_dir;
7289 xx = x + move_xy[MovDir[x][y]].dx;
7290 yy = y + move_xy[MovDir[x][y]].dy;
7292 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7293 MovDir[x][y] = old_move_dir;
7297 else if (element == EL_MOLE)
7299 boolean can_move_on =
7300 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7301 IS_AMOEBOID(Tile[move_x][move_y]) ||
7302 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7305 boolean can_turn_left =
7306 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7307 IS_AMOEBOID(Tile[left_x][left_y])));
7309 boolean can_turn_right =
7310 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7311 IS_AMOEBOID(Tile[right_x][right_y])));
7313 if (can_turn_left && can_turn_right)
7314 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7315 else if (can_turn_left)
7316 MovDir[x][y] = left_dir;
7318 MovDir[x][y] = right_dir;
7321 if (MovDir[x][y] != old_move_dir)
7324 else if (element == EL_BALLOON)
7326 MovDir[x][y] = game.wind_direction;
7329 else if (element == EL_SPRING)
7331 if (MovDir[x][y] & MV_HORIZONTAL)
7333 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7334 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7336 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7337 ResetGfxAnimation(move_x, move_y);
7338 TEST_DrawLevelField(move_x, move_y);
7340 MovDir[x][y] = back_dir;
7342 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7343 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7344 MovDir[x][y] = MV_NONE;
7349 else if (element == EL_ROBOT ||
7350 element == EL_SATELLITE ||
7351 element == EL_PENGUIN ||
7352 element == EL_EMC_ANDROID)
7354 int attr_x = -1, attr_y = -1;
7356 if (game.all_players_gone)
7358 attr_x = game.exit_x;
7359 attr_y = game.exit_y;
7365 for (i = 0; i < MAX_PLAYERS; i++)
7367 struct PlayerInfo *player = &stored_player[i];
7368 int jx = player->jx, jy = player->jy;
7370 if (!player->active)
7374 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7382 if (element == EL_ROBOT &&
7383 game.robot_wheel_x >= 0 &&
7384 game.robot_wheel_y >= 0 &&
7385 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7386 game.engine_version < VERSION_IDENT(3,1,0,0)))
7388 attr_x = game.robot_wheel_x;
7389 attr_y = game.robot_wheel_y;
7392 if (element == EL_PENGUIN)
7395 struct XY *xy = xy_topdown;
7397 for (i = 0; i < NUM_DIRECTIONS; i++)
7399 int ex = x + xy[i].x;
7400 int ey = y + xy[i].y;
7402 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7403 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7404 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7405 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7414 MovDir[x][y] = MV_NONE;
7416 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7417 else if (attr_x > x)
7418 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7420 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7421 else if (attr_y > y)
7422 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7424 if (element == EL_ROBOT)
7428 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7429 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7430 Moving2Blocked(x, y, &newx, &newy);
7432 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7433 MovDelay[x][y] = 8 + 8 * !RND(3);
7435 MovDelay[x][y] = 16;
7437 else if (element == EL_PENGUIN)
7443 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7445 boolean first_horiz = RND(2);
7446 int new_move_dir = MovDir[x][y];
7449 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7450 Moving2Blocked(x, y, &newx, &newy);
7452 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7456 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7457 Moving2Blocked(x, y, &newx, &newy);
7459 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7462 MovDir[x][y] = old_move_dir;
7466 else if (element == EL_SATELLITE)
7472 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7474 boolean first_horiz = RND(2);
7475 int new_move_dir = MovDir[x][y];
7478 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7479 Moving2Blocked(x, y, &newx, &newy);
7481 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7485 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7486 Moving2Blocked(x, y, &newx, &newy);
7488 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7491 MovDir[x][y] = old_move_dir;
7495 else if (element == EL_EMC_ANDROID)
7497 static int check_pos[16] =
7499 -1, // 0 => (invalid)
7502 -1, // 3 => (invalid)
7504 0, // 5 => MV_LEFT | MV_UP
7505 2, // 6 => MV_RIGHT | MV_UP
7506 -1, // 7 => (invalid)
7508 6, // 9 => MV_LEFT | MV_DOWN
7509 4, // 10 => MV_RIGHT | MV_DOWN
7510 -1, // 11 => (invalid)
7511 -1, // 12 => (invalid)
7512 -1, // 13 => (invalid)
7513 -1, // 14 => (invalid)
7514 -1, // 15 => (invalid)
7522 { -1, -1, MV_LEFT | MV_UP },
7524 { +1, -1, MV_RIGHT | MV_UP },
7525 { +1, 0, MV_RIGHT },
7526 { +1, +1, MV_RIGHT | MV_DOWN },
7528 { -1, +1, MV_LEFT | MV_DOWN },
7531 int start_pos, check_order;
7532 boolean can_clone = FALSE;
7535 // check if there is any free field around current position
7536 for (i = 0; i < 8; i++)
7538 int newx = x + check_xy[i].dx;
7539 int newy = y + check_xy[i].dy;
7541 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7549 if (can_clone) // randomly find an element to clone
7553 start_pos = check_pos[RND(8)];
7554 check_order = (RND(2) ? -1 : +1);
7556 for (i = 0; i < 8; i++)
7558 int pos_raw = start_pos + i * check_order;
7559 int pos = (pos_raw + 8) % 8;
7560 int newx = x + check_xy[pos].dx;
7561 int newy = y + check_xy[pos].dy;
7563 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7565 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7566 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7568 Store[x][y] = Tile[newx][newy];
7577 if (can_clone) // randomly find a direction to move
7581 start_pos = check_pos[RND(8)];
7582 check_order = (RND(2) ? -1 : +1);
7584 for (i = 0; i < 8; i++)
7586 int pos_raw = start_pos + i * check_order;
7587 int pos = (pos_raw + 8) % 8;
7588 int newx = x + check_xy[pos].dx;
7589 int newy = y + check_xy[pos].dy;
7590 int new_move_dir = check_xy[pos].dir;
7592 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7594 MovDir[x][y] = new_move_dir;
7595 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7604 if (can_clone) // cloning and moving successful
7607 // cannot clone -- try to move towards player
7609 start_pos = check_pos[MovDir[x][y] & 0x0f];
7610 check_order = (RND(2) ? -1 : +1);
7612 for (i = 0; i < 3; i++)
7614 // first check start_pos, then previous/next or (next/previous) pos
7615 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7616 int pos = (pos_raw + 8) % 8;
7617 int newx = x + check_xy[pos].dx;
7618 int newy = y + check_xy[pos].dy;
7619 int new_move_dir = check_xy[pos].dir;
7621 if (IS_PLAYER(newx, newy))
7624 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7626 MovDir[x][y] = new_move_dir;
7627 MovDelay[x][y] = level.android_move_time * 8 + 1;
7634 else if (move_pattern == MV_TURNING_LEFT ||
7635 move_pattern == MV_TURNING_RIGHT ||
7636 move_pattern == MV_TURNING_LEFT_RIGHT ||
7637 move_pattern == MV_TURNING_RIGHT_LEFT ||
7638 move_pattern == MV_TURNING_RANDOM ||
7639 move_pattern == MV_ALL_DIRECTIONS)
7641 boolean can_turn_left =
7642 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7643 boolean can_turn_right =
7644 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y);
7646 if (element_info[element].move_stepsize == 0) // "not moving"
7649 if (move_pattern == MV_TURNING_LEFT)
7650 MovDir[x][y] = left_dir;
7651 else if (move_pattern == MV_TURNING_RIGHT)
7652 MovDir[x][y] = right_dir;
7653 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7654 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7655 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7656 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7657 else if (move_pattern == MV_TURNING_RANDOM)
7658 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7659 can_turn_right && !can_turn_left ? right_dir :
7660 RND(2) ? left_dir : right_dir);
7661 else if (can_turn_left && can_turn_right)
7662 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7663 else if (can_turn_left)
7664 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7665 else if (can_turn_right)
7666 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7668 MovDir[x][y] = back_dir;
7670 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7672 else if (move_pattern == MV_HORIZONTAL ||
7673 move_pattern == MV_VERTICAL)
7675 if (move_pattern & old_move_dir)
7676 MovDir[x][y] = back_dir;
7677 else if (move_pattern == MV_HORIZONTAL)
7678 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7679 else if (move_pattern == MV_VERTICAL)
7680 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7682 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7684 else if (move_pattern & MV_ANY_DIRECTION)
7686 MovDir[x][y] = move_pattern;
7687 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7689 else if (move_pattern & MV_WIND_DIRECTION)
7691 MovDir[x][y] = game.wind_direction;
7692 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7694 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7696 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7697 MovDir[x][y] = left_dir;
7698 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7699 MovDir[x][y] = right_dir;
7701 if (MovDir[x][y] != old_move_dir)
7702 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7704 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7706 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7707 MovDir[x][y] = right_dir;
7708 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7709 MovDir[x][y] = left_dir;
7711 if (MovDir[x][y] != old_move_dir)
7712 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7714 else if (move_pattern == MV_TOWARDS_PLAYER ||
7715 move_pattern == MV_AWAY_FROM_PLAYER)
7717 int attr_x = -1, attr_y = -1;
7719 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7721 if (game.all_players_gone)
7723 attr_x = game.exit_x;
7724 attr_y = game.exit_y;
7730 for (i = 0; i < MAX_PLAYERS; i++)
7732 struct PlayerInfo *player = &stored_player[i];
7733 int jx = player->jx, jy = player->jy;
7735 if (!player->active)
7739 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7747 MovDir[x][y] = MV_NONE;
7749 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7750 else if (attr_x > x)
7751 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7753 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7754 else if (attr_y > y)
7755 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7757 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7759 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7761 boolean first_horiz = RND(2);
7762 int new_move_dir = MovDir[x][y];
7764 if (element_info[element].move_stepsize == 0) // "not moving"
7766 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7767 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7773 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7774 Moving2Blocked(x, y, &newx, &newy);
7776 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7780 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7781 Moving2Blocked(x, y, &newx, &newy);
7783 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7786 MovDir[x][y] = old_move_dir;
7789 else if (move_pattern == MV_WHEN_PUSHED ||
7790 move_pattern == MV_WHEN_DROPPED)
7792 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7793 MovDir[x][y] = MV_NONE;
7797 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7799 struct XY *test_xy = xy_topdown;
7800 static int test_dir[4] =
7807 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7808 int move_preference = -1000000; // start with very low preference
7809 int new_move_dir = MV_NONE;
7810 int start_test = RND(4);
7813 for (i = 0; i < NUM_DIRECTIONS; i++)
7815 int j = (start_test + i) % 4;
7816 int move_dir = test_dir[j];
7817 int move_dir_preference;
7819 xx = x + test_xy[j].x;
7820 yy = y + test_xy[j].y;
7822 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7823 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7825 new_move_dir = move_dir;
7830 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7833 move_dir_preference = -1 * RunnerVisit[xx][yy];
7834 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7835 move_dir_preference = PlayerVisit[xx][yy];
7837 if (move_dir_preference > move_preference)
7839 // prefer field that has not been visited for the longest time
7840 move_preference = move_dir_preference;
7841 new_move_dir = move_dir;
7843 else if (move_dir_preference == move_preference &&
7844 move_dir == old_move_dir)
7846 // prefer last direction when all directions are preferred equally
7847 move_preference = move_dir_preference;
7848 new_move_dir = move_dir;
7852 MovDir[x][y] = new_move_dir;
7853 if (old_move_dir != new_move_dir)
7854 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7858 static void TurnRound(int x, int y)
7860 int direction = MovDir[x][y];
7864 GfxDir[x][y] = MovDir[x][y];
7866 if (direction != MovDir[x][y])
7870 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7872 ResetGfxFrame(x, y);
7875 static boolean JustBeingPushed(int x, int y)
7879 for (i = 0; i < MAX_PLAYERS; i++)
7881 struct PlayerInfo *player = &stored_player[i];
7883 if (player->active && player->is_pushing && player->MovPos)
7885 int next_jx = player->jx + (player->jx - player->last_jx);
7886 int next_jy = player->jy + (player->jy - player->last_jy);
7888 if (x == next_jx && y == next_jy)
7896 static void StartMoving(int x, int y)
7898 boolean started_moving = FALSE; // some elements can fall _and_ move
7899 int element = Tile[x][y];
7904 if (MovDelay[x][y] == 0)
7905 GfxAction[x][y] = ACTION_DEFAULT;
7907 if (CAN_FALL(element) && y < lev_fieldy - 1)
7909 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7910 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7911 if (JustBeingPushed(x, y))
7914 if (element == EL_QUICKSAND_FULL)
7916 if (IS_FREE(x, y + 1))
7918 InitMovingField(x, y, MV_DOWN);
7919 started_moving = TRUE;
7921 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7922 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7923 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7924 Store[x][y] = EL_ROCK;
7926 Store[x][y] = EL_ROCK;
7929 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7931 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7933 if (!MovDelay[x][y])
7935 MovDelay[x][y] = TILEY + 1;
7937 ResetGfxAnimation(x, y);
7938 ResetGfxAnimation(x, y + 1);
7943 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7944 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7951 Tile[x][y] = EL_QUICKSAND_EMPTY;
7952 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7953 Store[x][y + 1] = Store[x][y];
7956 PlayLevelSoundAction(x, y, ACTION_FILLING);
7958 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7960 if (!MovDelay[x][y])
7962 MovDelay[x][y] = TILEY + 1;
7964 ResetGfxAnimation(x, y);
7965 ResetGfxAnimation(x, y + 1);
7970 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7971 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7978 Tile[x][y] = EL_QUICKSAND_EMPTY;
7979 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7980 Store[x][y + 1] = Store[x][y];
7983 PlayLevelSoundAction(x, y, ACTION_FILLING);
7986 else if (element == EL_QUICKSAND_FAST_FULL)
7988 if (IS_FREE(x, y + 1))
7990 InitMovingField(x, y, MV_DOWN);
7991 started_moving = TRUE;
7993 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7994 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7995 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7996 Store[x][y] = EL_ROCK;
7998 Store[x][y] = EL_ROCK;
8001 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
8003 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8005 if (!MovDelay[x][y])
8007 MovDelay[x][y] = TILEY + 1;
8009 ResetGfxAnimation(x, y);
8010 ResetGfxAnimation(x, y + 1);
8015 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8016 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8023 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8024 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8025 Store[x][y + 1] = Store[x][y];
8028 PlayLevelSoundAction(x, y, ACTION_FILLING);
8030 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8032 if (!MovDelay[x][y])
8034 MovDelay[x][y] = TILEY + 1;
8036 ResetGfxAnimation(x, y);
8037 ResetGfxAnimation(x, y + 1);
8042 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8043 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8050 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8051 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8052 Store[x][y + 1] = Store[x][y];
8055 PlayLevelSoundAction(x, y, ACTION_FILLING);
8058 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8059 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8061 InitMovingField(x, y, MV_DOWN);
8062 started_moving = TRUE;
8064 Tile[x][y] = EL_QUICKSAND_FILLING;
8065 Store[x][y] = element;
8067 PlayLevelSoundAction(x, y, ACTION_FILLING);
8069 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8070 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8072 InitMovingField(x, y, MV_DOWN);
8073 started_moving = TRUE;
8075 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
8076 Store[x][y] = element;
8078 PlayLevelSoundAction(x, y, ACTION_FILLING);
8080 else if (element == EL_MAGIC_WALL_FULL)
8082 if (IS_FREE(x, y + 1))
8084 InitMovingField(x, y, MV_DOWN);
8085 started_moving = TRUE;
8087 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
8088 Store[x][y] = EL_CHANGED(Store[x][y]);
8090 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
8092 if (!MovDelay[x][y])
8093 MovDelay[x][y] = TILEY / 4 + 1;
8102 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
8103 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
8104 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8108 else if (element == EL_BD_MAGIC_WALL_FULL)
8110 if (IS_FREE(x, y + 1))
8112 InitMovingField(x, y, MV_DOWN);
8113 started_moving = TRUE;
8115 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8116 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8118 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8120 if (!MovDelay[x][y])
8121 MovDelay[x][y] = TILEY / 4 + 1;
8130 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8131 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8132 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8136 else if (element == EL_DC_MAGIC_WALL_FULL)
8138 if (IS_FREE(x, y + 1))
8140 InitMovingField(x, y, MV_DOWN);
8141 started_moving = TRUE;
8143 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8144 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8146 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8148 if (!MovDelay[x][y])
8149 MovDelay[x][y] = TILEY / 4 + 1;
8158 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8159 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8160 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8164 else if ((CAN_PASS_MAGIC_WALL(element) &&
8165 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8166 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8167 (CAN_PASS_DC_MAGIC_WALL(element) &&
8168 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8171 InitMovingField(x, y, MV_DOWN);
8172 started_moving = TRUE;
8175 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8176 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8177 EL_DC_MAGIC_WALL_FILLING);
8178 Store[x][y] = element;
8180 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8182 SplashAcid(x, y + 1);
8184 InitMovingField(x, y, MV_DOWN);
8185 started_moving = TRUE;
8187 Store[x][y] = EL_ACID;
8190 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8191 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8192 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8193 CAN_FALL(element) && WasJustFalling[x][y] &&
8194 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8196 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8197 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8198 (Tile[x][y + 1] == EL_BLOCKED)))
8200 /* this is needed for a special case not covered by calling "Impact()"
8201 from "ContinueMoving()": if an element moves to a tile directly below
8202 another element which was just falling on that tile (which was empty
8203 in the previous frame), the falling element above would just stop
8204 instead of smashing the element below (in previous version, the above
8205 element was just checked for "moving" instead of "falling", resulting
8206 in incorrect smashes caused by horizontal movement of the above
8207 element; also, the case of the player being the element to smash was
8208 simply not covered here... :-/ ) */
8210 CheckCollision[x][y] = 0;
8211 CheckImpact[x][y] = 0;
8215 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8217 if (MovDir[x][y] == MV_NONE)
8219 InitMovingField(x, y, MV_DOWN);
8220 started_moving = TRUE;
8223 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8225 if (WasJustFalling[x][y]) // prevent animation from being restarted
8226 MovDir[x][y] = MV_DOWN;
8228 InitMovingField(x, y, MV_DOWN);
8229 started_moving = TRUE;
8231 else if (element == EL_AMOEBA_DROP)
8233 Tile[x][y] = EL_AMOEBA_GROWING;
8234 Store[x][y] = EL_AMOEBA_WET;
8236 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8237 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8238 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8239 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8241 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8242 (IS_FREE(x - 1, y + 1) ||
8243 Tile[x - 1][y + 1] == EL_ACID));
8244 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8245 (IS_FREE(x + 1, y + 1) ||
8246 Tile[x + 1][y + 1] == EL_ACID));
8247 boolean can_fall_any = (can_fall_left || can_fall_right);
8248 boolean can_fall_both = (can_fall_left && can_fall_right);
8249 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8251 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8253 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8254 can_fall_right = FALSE;
8255 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8256 can_fall_left = FALSE;
8257 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8258 can_fall_right = FALSE;
8259 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8260 can_fall_left = FALSE;
8262 can_fall_any = (can_fall_left || can_fall_right);
8263 can_fall_both = FALSE;
8268 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8269 can_fall_right = FALSE; // slip down on left side
8271 can_fall_left = !(can_fall_right = RND(2));
8273 can_fall_both = FALSE;
8278 // if not determined otherwise, prefer left side for slipping down
8279 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8280 started_moving = TRUE;
8283 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8285 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8286 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8287 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8288 int belt_dir = game.belt_dir[belt_nr];
8290 if ((belt_dir == MV_LEFT && left_is_free) ||
8291 (belt_dir == MV_RIGHT && right_is_free))
8293 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8295 InitMovingField(x, y, belt_dir);
8296 started_moving = TRUE;
8298 Pushed[x][y] = TRUE;
8299 Pushed[nextx][y] = TRUE;
8301 GfxAction[x][y] = ACTION_DEFAULT;
8305 MovDir[x][y] = 0; // if element was moving, stop it
8310 // not "else if" because of elements that can fall and move (EL_SPRING)
8311 if (CAN_MOVE(element) && !started_moving)
8313 int move_pattern = element_info[element].move_pattern;
8316 Moving2Blocked(x, y, &newx, &newy);
8318 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8321 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8322 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8324 WasJustMoving[x][y] = 0;
8325 CheckCollision[x][y] = 0;
8327 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8329 if (Tile[x][y] != element) // element has changed
8333 if (!MovDelay[x][y]) // start new movement phase
8335 // all objects that can change their move direction after each step
8336 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8338 if (element != EL_YAMYAM &&
8339 element != EL_DARK_YAMYAM &&
8340 element != EL_PACMAN &&
8341 !(move_pattern & MV_ANY_DIRECTION) &&
8342 move_pattern != MV_TURNING_LEFT &&
8343 move_pattern != MV_TURNING_RIGHT &&
8344 move_pattern != MV_TURNING_LEFT_RIGHT &&
8345 move_pattern != MV_TURNING_RIGHT_LEFT &&
8346 move_pattern != MV_TURNING_RANDOM)
8350 if (MovDelay[x][y] && (element == EL_BUG ||
8351 element == EL_SPACESHIP ||
8352 element == EL_SP_SNIKSNAK ||
8353 element == EL_SP_ELECTRON ||
8354 element == EL_MOLE))
8355 TEST_DrawLevelField(x, y);
8359 if (MovDelay[x][y]) // wait some time before next movement
8363 if (element == EL_ROBOT ||
8364 element == EL_YAMYAM ||
8365 element == EL_DARK_YAMYAM)
8367 DrawLevelElementAnimationIfNeeded(x, y, element);
8368 PlayLevelSoundAction(x, y, ACTION_WAITING);
8370 else if (element == EL_SP_ELECTRON)
8371 DrawLevelElementAnimationIfNeeded(x, y, element);
8372 else if (element == EL_DRAGON)
8375 int dir = MovDir[x][y];
8376 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8377 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8378 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8379 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8380 dir == MV_UP ? IMG_FLAMES_1_UP :
8381 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8382 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8384 GfxAction[x][y] = ACTION_ATTACKING;
8386 if (IS_PLAYER(x, y))
8387 DrawPlayerField(x, y);
8389 TEST_DrawLevelField(x, y);
8391 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8393 for (i = 1; i <= 3; i++)
8395 int xx = x + i * dx;
8396 int yy = y + i * dy;
8397 int sx = SCREENX(xx);
8398 int sy = SCREENY(yy);
8399 int flame_graphic = graphic + (i - 1);
8401 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8406 int flamed = MovingOrBlocked2Element(xx, yy);
8408 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8411 RemoveMovingField(xx, yy);
8413 ChangeDelay[xx][yy] = 0;
8415 Tile[xx][yy] = EL_FLAMES;
8417 if (IN_SCR_FIELD(sx, sy))
8419 TEST_DrawLevelFieldCrumbled(xx, yy);
8420 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8425 if (Tile[xx][yy] == EL_FLAMES)
8426 Tile[xx][yy] = EL_EMPTY;
8427 TEST_DrawLevelField(xx, yy);
8432 if (MovDelay[x][y]) // element still has to wait some time
8434 PlayLevelSoundAction(x, y, ACTION_WAITING);
8440 // now make next step
8442 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8444 if (DONT_COLLIDE_WITH(element) &&
8445 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8446 !PLAYER_ENEMY_PROTECTED(newx, newy))
8448 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8453 else if (CAN_MOVE_INTO_ACID(element) &&
8454 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8455 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8456 (MovDir[x][y] == MV_DOWN ||
8457 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8459 SplashAcid(newx, newy);
8460 Store[x][y] = EL_ACID;
8462 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8464 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8465 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8466 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8467 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8470 TEST_DrawLevelField(x, y);
8472 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8473 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8474 DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0);
8476 game.friends_still_needed--;
8477 if (!game.friends_still_needed &&
8479 game.all_players_gone)
8484 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8486 if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING)
8487 TEST_DrawLevelField(newx, newy);
8489 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8491 else if (!IS_FREE(newx, newy))
8493 GfxAction[x][y] = ACTION_WAITING;
8495 if (IS_PLAYER(x, y))
8496 DrawPlayerField(x, y);
8498 TEST_DrawLevelField(x, y);
8503 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8505 if (IS_FOOD_PIG(Tile[newx][newy]))
8507 if (IS_MOVING(newx, newy))
8508 RemoveMovingField(newx, newy);
8511 Tile[newx][newy] = EL_EMPTY;
8512 TEST_DrawLevelField(newx, newy);
8515 PlayLevelSound(x, y, SND_PIG_DIGGING);
8517 else if (!IS_FREE(newx, newy))
8519 if (IS_PLAYER(x, y))
8520 DrawPlayerField(x, y);
8522 TEST_DrawLevelField(x, y);
8527 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8529 if (Store[x][y] != EL_EMPTY)
8531 boolean can_clone = FALSE;
8534 // check if element to clone is still there
8535 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8537 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8545 // cannot clone or target field not free anymore -- do not clone
8546 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8547 Store[x][y] = EL_EMPTY;
8550 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8552 if (IS_MV_DIAGONAL(MovDir[x][y]))
8554 int diagonal_move_dir = MovDir[x][y];
8555 int stored = Store[x][y];
8556 int change_delay = 8;
8559 // android is moving diagonally
8561 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8563 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8564 GfxElement[x][y] = EL_EMC_ANDROID;
8565 GfxAction[x][y] = ACTION_SHRINKING;
8566 GfxDir[x][y] = diagonal_move_dir;
8567 ChangeDelay[x][y] = change_delay;
8569 if (Store[x][y] == EL_EMPTY)
8570 Store[x][y] = GfxElementEmpty[x][y];
8572 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8575 DrawLevelGraphicAnimation(x, y, graphic);
8576 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8578 if (Tile[newx][newy] == EL_ACID)
8580 SplashAcid(newx, newy);
8585 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8587 Store[newx][newy] = EL_EMC_ANDROID;
8588 GfxElement[newx][newy] = EL_EMC_ANDROID;
8589 GfxAction[newx][newy] = ACTION_GROWING;
8590 GfxDir[newx][newy] = diagonal_move_dir;
8591 ChangeDelay[newx][newy] = change_delay;
8593 graphic = el_act_dir2img(GfxElement[newx][newy],
8594 GfxAction[newx][newy], GfxDir[newx][newy]);
8596 DrawLevelGraphicAnimation(newx, newy, graphic);
8597 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8603 Tile[newx][newy] = EL_EMPTY;
8604 TEST_DrawLevelField(newx, newy);
8606 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8609 else if (!IS_FREE(newx, newy))
8614 else if (IS_CUSTOM_ELEMENT(element) &&
8615 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8617 if (!DigFieldByCE(newx, newy, element))
8620 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8622 RunnerVisit[x][y] = FrameCounter;
8623 PlayerVisit[x][y] /= 8; // expire player visit path
8626 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8628 if (!IS_FREE(newx, newy))
8630 if (IS_PLAYER(x, y))
8631 DrawPlayerField(x, y);
8633 TEST_DrawLevelField(x, y);
8639 boolean wanna_flame = !RND(10);
8640 int dx = newx - x, dy = newy - y;
8641 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8642 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8643 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8644 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8645 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8646 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8649 IS_CLASSIC_ENEMY(element1) ||
8650 IS_CLASSIC_ENEMY(element2)) &&
8651 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8652 element1 != EL_FLAMES && element2 != EL_FLAMES)
8654 ResetGfxAnimation(x, y);
8655 GfxAction[x][y] = ACTION_ATTACKING;
8657 if (IS_PLAYER(x, y))
8658 DrawPlayerField(x, y);
8660 TEST_DrawLevelField(x, y);
8662 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8664 MovDelay[x][y] = 50;
8666 Tile[newx][newy] = EL_FLAMES;
8667 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8668 Tile[newx1][newy1] = EL_FLAMES;
8669 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8670 Tile[newx2][newy2] = EL_FLAMES;
8676 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8677 Tile[newx][newy] == EL_DIAMOND)
8679 if (IS_MOVING(newx, newy))
8680 RemoveMovingField(newx, newy);
8683 Tile[newx][newy] = EL_EMPTY;
8684 TEST_DrawLevelField(newx, newy);
8687 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8689 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8690 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8692 if (AmoebaNr[newx][newy])
8694 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8695 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8696 Tile[newx][newy] == EL_BD_AMOEBA)
8697 AmoebaCnt[AmoebaNr[newx][newy]]--;
8700 if (IS_MOVING(newx, newy))
8702 RemoveMovingField(newx, newy);
8706 Tile[newx][newy] = EL_EMPTY;
8707 TEST_DrawLevelField(newx, newy);
8710 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8712 else if ((element == EL_PACMAN || element == EL_MOLE)
8713 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8715 if (AmoebaNr[newx][newy])
8717 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8718 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8719 Tile[newx][newy] == EL_BD_AMOEBA)
8720 AmoebaCnt[AmoebaNr[newx][newy]]--;
8723 if (element == EL_MOLE)
8725 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8726 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8728 ResetGfxAnimation(x, y);
8729 GfxAction[x][y] = ACTION_DIGGING;
8730 TEST_DrawLevelField(x, y);
8732 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8734 return; // wait for shrinking amoeba
8736 else // element == EL_PACMAN
8738 Tile[newx][newy] = EL_EMPTY;
8739 TEST_DrawLevelField(newx, newy);
8740 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8743 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8744 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8745 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8747 // wait for shrinking amoeba to completely disappear
8750 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8752 // object was running against a wall
8756 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8757 DrawLevelElementAnimation(x, y, element);
8759 if (DONT_TOUCH(element))
8760 TestIfBadThingTouchesPlayer(x, y);
8765 InitMovingField(x, y, MovDir[x][y]);
8767 PlayLevelSoundAction(x, y, ACTION_MOVING);
8771 ContinueMoving(x, y);
8774 void ContinueMoving(int x, int y)
8776 int element = Tile[x][y];
8777 struct ElementInfo *ei = &element_info[element];
8778 int direction = MovDir[x][y];
8779 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8780 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8781 int newx = x + dx, newy = y + dy;
8782 int stored = Store[x][y];
8783 int stored_new = Store[newx][newy];
8784 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8785 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8786 boolean last_line = (newy == lev_fieldy - 1);
8787 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8789 if (pushed_by_player) // special case: moving object pushed by player
8791 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos));
8793 else if (use_step_delay) // special case: moving object has step delay
8795 if (!MovDelay[x][y])
8796 MovPos[x][y] += getElementMoveStepsize(x, y);
8801 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8805 TEST_DrawLevelField(x, y);
8807 return; // element is still waiting
8810 else // normal case: generically moving object
8812 MovPos[x][y] += getElementMoveStepsize(x, y);
8815 if (ABS(MovPos[x][y]) < TILEX)
8817 TEST_DrawLevelField(x, y);
8819 return; // element is still moving
8822 // element reached destination field
8824 Tile[x][y] = EL_EMPTY;
8825 Tile[newx][newy] = element;
8826 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8828 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8830 element = Tile[newx][newy] = EL_ACID;
8832 else if (element == EL_MOLE)
8834 Tile[x][y] = EL_SAND;
8836 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8838 else if (element == EL_QUICKSAND_FILLING)
8840 element = Tile[newx][newy] = get_next_element(element);
8841 Store[newx][newy] = Store[x][y];
8843 else if (element == EL_QUICKSAND_EMPTYING)
8845 Tile[x][y] = get_next_element(element);
8846 element = Tile[newx][newy] = Store[x][y];
8848 else if (element == EL_QUICKSAND_FAST_FILLING)
8850 element = Tile[newx][newy] = get_next_element(element);
8851 Store[newx][newy] = Store[x][y];
8853 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8855 Tile[x][y] = get_next_element(element);
8856 element = Tile[newx][newy] = Store[x][y];
8858 else if (element == EL_MAGIC_WALL_FILLING)
8860 element = Tile[newx][newy] = get_next_element(element);
8861 if (!game.magic_wall_active)
8862 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8863 Store[newx][newy] = Store[x][y];
8865 else if (element == EL_MAGIC_WALL_EMPTYING)
8867 Tile[x][y] = get_next_element(element);
8868 if (!game.magic_wall_active)
8869 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8870 element = Tile[newx][newy] = Store[x][y];
8872 InitField(newx, newy, FALSE);
8874 else if (element == EL_BD_MAGIC_WALL_FILLING)
8876 element = Tile[newx][newy] = get_next_element(element);
8877 if (!game.magic_wall_active)
8878 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8879 Store[newx][newy] = Store[x][y];
8881 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8883 Tile[x][y] = get_next_element(element);
8884 if (!game.magic_wall_active)
8885 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8886 element = Tile[newx][newy] = Store[x][y];
8888 InitField(newx, newy, FALSE);
8890 else if (element == EL_DC_MAGIC_WALL_FILLING)
8892 element = Tile[newx][newy] = get_next_element(element);
8893 if (!game.magic_wall_active)
8894 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8895 Store[newx][newy] = Store[x][y];
8897 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8899 Tile[x][y] = get_next_element(element);
8900 if (!game.magic_wall_active)
8901 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8902 element = Tile[newx][newy] = Store[x][y];
8904 InitField(newx, newy, FALSE);
8906 else if (element == EL_AMOEBA_DROPPING)
8908 Tile[x][y] = get_next_element(element);
8909 element = Tile[newx][newy] = Store[x][y];
8911 else if (element == EL_SOKOBAN_OBJECT)
8914 Tile[x][y] = Back[x][y];
8916 if (Back[newx][newy])
8917 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8919 Back[x][y] = Back[newx][newy] = 0;
8922 Store[x][y] = EL_EMPTY;
8927 MovDelay[newx][newy] = 0;
8929 if (CAN_CHANGE_OR_HAS_ACTION(element))
8931 // copy element change control values to new field
8932 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8933 ChangePage[newx][newy] = ChangePage[x][y];
8934 ChangeCount[newx][newy] = ChangeCount[x][y];
8935 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8938 CustomValue[newx][newy] = CustomValue[x][y];
8940 ChangeDelay[x][y] = 0;
8941 ChangePage[x][y] = -1;
8942 ChangeCount[x][y] = 0;
8943 ChangeEvent[x][y] = -1;
8945 CustomValue[x][y] = 0;
8947 // copy animation control values to new field
8948 GfxFrame[newx][newy] = GfxFrame[x][y];
8949 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8950 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8951 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8953 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8955 // some elements can leave other elements behind after moving
8956 if (ei->move_leave_element != EL_EMPTY &&
8957 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8958 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8960 int move_leave_element = ei->move_leave_element;
8962 // this makes it possible to leave the removed element again
8963 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8964 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8966 Tile[x][y] = move_leave_element;
8968 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8969 MovDir[x][y] = direction;
8971 InitField(x, y, FALSE);
8973 if (GFX_CRUMBLED(Tile[x][y]))
8974 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8976 if (IS_PLAYER_ELEMENT(move_leave_element))
8977 RelocatePlayer(x, y, move_leave_element);
8980 // do this after checking for left-behind element
8981 ResetGfxAnimation(x, y); // reset animation values for old field
8983 if (!CAN_MOVE(element) ||
8984 (CAN_FALL(element) && direction == MV_DOWN &&
8985 (element == EL_SPRING ||
8986 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8987 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8988 GfxDir[x][y] = MovDir[newx][newy] = 0;
8990 TEST_DrawLevelField(x, y);
8991 TEST_DrawLevelField(newx, newy);
8993 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8995 // prevent pushed element from moving on in pushed direction
8996 if (pushed_by_player && CAN_MOVE(element) &&
8997 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8998 !(element_info[element].move_pattern & direction))
8999 TurnRound(newx, newy);
9001 // prevent elements on conveyor belt from moving on in last direction
9002 if (pushed_by_conveyor && CAN_FALL(element) &&
9003 direction & MV_HORIZONTAL)
9004 MovDir[newx][newy] = 0;
9006 if (!pushed_by_player)
9008 int nextx = newx + dx, nexty = newy + dy;
9009 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
9011 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
9013 if (CAN_FALL(element) && direction == MV_DOWN)
9014 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
9016 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
9017 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
9019 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
9020 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
9023 if (DONT_TOUCH(element)) // object may be nasty to player or others
9025 TestIfBadThingTouchesPlayer(newx, newy);
9026 TestIfBadThingTouchesFriend(newx, newy);
9028 if (!IS_CUSTOM_ELEMENT(element))
9029 TestIfBadThingTouchesOtherBadThing(newx, newy);
9031 else if (element == EL_PENGUIN)
9032 TestIfFriendTouchesBadThing(newx, newy);
9034 if (DONT_GET_HIT_BY(element))
9036 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
9039 // give the player one last chance (one more frame) to move away
9040 if (CAN_FALL(element) && direction == MV_DOWN &&
9041 (last_line || (!IS_FREE(x, newy + 1) &&
9042 (!IS_PLAYER(x, newy + 1) ||
9043 game.engine_version < VERSION_IDENT(3,1,1,0)))))
9046 if (pushed_by_player && !game.use_change_when_pushing_bug)
9048 int push_side = MV_DIR_OPPOSITE(direction);
9049 struct PlayerInfo *player = PLAYERINFO(x, y);
9051 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
9052 player->index_bit, push_side);
9053 CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X,
9054 player->index_bit, push_side);
9057 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
9058 MovDelay[newx][newy] = 1;
9060 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
9062 TestIfElementTouchesCustomElement(x, y); // empty or new element
9063 TestIfElementHitsCustomElement(newx, newy, direction);
9064 TestIfPlayerTouchesCustomElement(newx, newy);
9065 TestIfElementTouchesCustomElement(newx, newy);
9067 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
9068 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
9069 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
9070 MV_DIR_OPPOSITE(direction));
9073 int AmoebaNeighbourNr(int ax, int ay)
9076 int element = Tile[ax][ay];
9078 struct XY *xy = xy_topdown;
9080 for (i = 0; i < NUM_DIRECTIONS; i++)
9082 int x = ax + xy[i].x;
9083 int y = ay + xy[i].y;
9085 if (!IN_LEV_FIELD(x, y))
9088 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
9089 group_nr = AmoebaNr[x][y];
9095 static void AmoebaMerge(int ax, int ay)
9097 int i, x, y, xx, yy;
9098 int new_group_nr = AmoebaNr[ax][ay];
9099 struct XY *xy = xy_topdown;
9101 if (new_group_nr == 0)
9104 for (i = 0; i < NUM_DIRECTIONS; i++)
9109 if (!IN_LEV_FIELD(x, y))
9112 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9113 Tile[x][y] == EL_BD_AMOEBA ||
9114 Tile[x][y] == EL_AMOEBA_DEAD) &&
9115 AmoebaNr[x][y] != new_group_nr)
9117 int old_group_nr = AmoebaNr[x][y];
9119 if (old_group_nr == 0)
9122 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9123 AmoebaCnt[old_group_nr] = 0;
9124 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9125 AmoebaCnt2[old_group_nr] = 0;
9127 SCAN_PLAYFIELD(xx, yy)
9129 if (AmoebaNr[xx][yy] == old_group_nr)
9130 AmoebaNr[xx][yy] = new_group_nr;
9136 void AmoebaToDiamond(int ax, int ay)
9140 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9142 int group_nr = AmoebaNr[ax][ay];
9147 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9148 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9154 SCAN_PLAYFIELD(x, y)
9156 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9159 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9163 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9164 SND_AMOEBA_TURNING_TO_GEM :
9165 SND_AMOEBA_TURNING_TO_ROCK));
9170 struct XY *xy = xy_topdown;
9172 for (i = 0; i < NUM_DIRECTIONS; i++)
9177 if (!IN_LEV_FIELD(x, y))
9180 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9182 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9183 SND_AMOEBA_TURNING_TO_GEM :
9184 SND_AMOEBA_TURNING_TO_ROCK));
9191 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9194 int group_nr = AmoebaNr[ax][ay];
9195 boolean done = FALSE;
9200 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9201 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9207 SCAN_PLAYFIELD(x, y)
9209 if (AmoebaNr[x][y] == group_nr &&
9210 (Tile[x][y] == EL_AMOEBA_DEAD ||
9211 Tile[x][y] == EL_BD_AMOEBA ||
9212 Tile[x][y] == EL_AMOEBA_GROWING))
9215 Tile[x][y] = new_element;
9216 InitField(x, y, FALSE);
9217 TEST_DrawLevelField(x, y);
9223 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9224 SND_BD_AMOEBA_TURNING_TO_ROCK :
9225 SND_BD_AMOEBA_TURNING_TO_GEM));
9228 static void AmoebaGrowing(int x, int y)
9230 static DelayCounter sound_delay = { 0 };
9232 if (!MovDelay[x][y]) // start new growing cycle
9236 if (DelayReached(&sound_delay))
9238 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9239 sound_delay.value = 30;
9243 if (MovDelay[x][y]) // wait some time before growing bigger
9246 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9248 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9249 6 - MovDelay[x][y]);
9251 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9254 if (!MovDelay[x][y])
9256 Tile[x][y] = Store[x][y];
9258 TEST_DrawLevelField(x, y);
9263 static void AmoebaShrinking(int x, int y)
9265 static DelayCounter sound_delay = { 0 };
9267 if (!MovDelay[x][y]) // start new shrinking cycle
9271 if (DelayReached(&sound_delay))
9272 sound_delay.value = 30;
9275 if (MovDelay[x][y]) // wait some time before shrinking
9278 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9280 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9281 6 - MovDelay[x][y]);
9283 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9286 if (!MovDelay[x][y])
9288 Tile[x][y] = EL_EMPTY;
9289 TEST_DrawLevelField(x, y);
9291 // don't let mole enter this field in this cycle;
9292 // (give priority to objects falling to this field from above)
9298 static void AmoebaReproduce(int ax, int ay)
9301 int element = Tile[ax][ay];
9302 int graphic = el2img(element);
9303 int newax = ax, neway = ay;
9304 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9305 struct XY *xy = xy_topdown;
9307 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9309 Tile[ax][ay] = EL_AMOEBA_DEAD;
9310 TEST_DrawLevelField(ax, ay);
9314 if (IS_ANIMATED(graphic))
9315 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9317 if (!MovDelay[ax][ay]) // start making new amoeba field
9318 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9320 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9323 if (MovDelay[ax][ay])
9327 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9330 int x = ax + xy[start].x;
9331 int y = ay + xy[start].y;
9333 if (!IN_LEV_FIELD(x, y))
9336 if (IS_FREE(x, y) ||
9337 CAN_GROW_INTO(Tile[x][y]) ||
9338 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9339 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9345 if (newax == ax && neway == ay)
9348 else // normal or "filled" (BD style) amoeba
9351 boolean waiting_for_player = FALSE;
9353 for (i = 0; i < NUM_DIRECTIONS; i++)
9355 int j = (start + i) % 4;
9356 int x = ax + xy[j].x;
9357 int y = ay + xy[j].y;
9359 if (!IN_LEV_FIELD(x, y))
9362 if (IS_FREE(x, y) ||
9363 CAN_GROW_INTO(Tile[x][y]) ||
9364 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9365 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9371 else if (IS_PLAYER(x, y))
9372 waiting_for_player = TRUE;
9375 if (newax == ax && neway == ay) // amoeba cannot grow
9377 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9379 Tile[ax][ay] = EL_AMOEBA_DEAD;
9380 TEST_DrawLevelField(ax, ay);
9381 AmoebaCnt[AmoebaNr[ax][ay]]--;
9383 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9385 if (element == EL_AMOEBA_FULL)
9386 AmoebaToDiamond(ax, ay);
9387 else if (element == EL_BD_AMOEBA)
9388 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9393 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9395 // amoeba gets larger by growing in some direction
9397 int new_group_nr = AmoebaNr[ax][ay];
9400 if (new_group_nr == 0)
9402 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9404 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9410 AmoebaNr[newax][neway] = new_group_nr;
9411 AmoebaCnt[new_group_nr]++;
9412 AmoebaCnt2[new_group_nr]++;
9414 // if amoeba touches other amoeba(s) after growing, unify them
9415 AmoebaMerge(newax, neway);
9417 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9419 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9425 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9426 (neway == lev_fieldy - 1 && newax != ax))
9428 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9429 Store[newax][neway] = element;
9431 else if (neway == ay || element == EL_EMC_DRIPPER)
9433 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9435 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9439 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9440 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9441 Store[ax][ay] = EL_AMOEBA_DROP;
9442 ContinueMoving(ax, ay);
9446 TEST_DrawLevelField(newax, neway);
9449 static void Life(int ax, int ay)
9453 int element = Tile[ax][ay];
9454 int graphic = el2img(element);
9455 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9457 boolean changed = FALSE;
9459 if (IS_ANIMATED(graphic))
9460 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9465 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9466 MovDelay[ax][ay] = life_time;
9468 if (MovDelay[ax][ay]) // wait some time before next cycle
9471 if (MovDelay[ax][ay])
9475 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9477 int xx = ax + x1, yy = ay + y1;
9478 int old_element = Tile[xx][yy];
9479 int num_neighbours = 0;
9481 if (!IN_LEV_FIELD(xx, yy))
9484 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9486 int x = xx + x2, y = yy + y2;
9488 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9491 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9492 boolean is_neighbour = FALSE;
9494 if (level.use_life_bugs)
9496 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9497 (IS_FREE(x, y) && Stop[x][y]));
9500 (Last[x][y] == element || is_player_cell);
9506 boolean is_free = FALSE;
9508 if (level.use_life_bugs)
9509 is_free = (IS_FREE(xx, yy));
9511 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9513 if (xx == ax && yy == ay) // field in the middle
9515 if (num_neighbours < life_parameter[0] ||
9516 num_neighbours > life_parameter[1])
9518 Tile[xx][yy] = EL_EMPTY;
9519 if (Tile[xx][yy] != old_element)
9520 TEST_DrawLevelField(xx, yy);
9521 Stop[xx][yy] = TRUE;
9525 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9526 { // free border field
9527 if (num_neighbours >= life_parameter[2] &&
9528 num_neighbours <= life_parameter[3])
9530 Tile[xx][yy] = element;
9531 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9532 if (Tile[xx][yy] != old_element)
9533 TEST_DrawLevelField(xx, yy);
9534 Stop[xx][yy] = TRUE;
9541 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9542 SND_GAME_OF_LIFE_GROWING);
9545 static void InitRobotWheel(int x, int y)
9547 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9550 static void RunRobotWheel(int x, int y)
9552 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9555 static void StopRobotWheel(int x, int y)
9557 if (game.robot_wheel_x == x &&
9558 game.robot_wheel_y == y)
9560 game.robot_wheel_x = -1;
9561 game.robot_wheel_y = -1;
9562 game.robot_wheel_active = FALSE;
9566 static void InitTimegateWheel(int x, int y)
9568 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9571 static void RunTimegateWheel(int x, int y)
9573 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9576 static void InitMagicBallDelay(int x, int y)
9578 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9581 static void ActivateMagicBall(int bx, int by)
9585 if (level.ball_random)
9587 int pos_border = RND(8); // select one of the eight border elements
9588 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9589 int xx = pos_content % 3;
9590 int yy = pos_content / 3;
9595 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9596 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9600 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9602 int xx = x - bx + 1;
9603 int yy = y - by + 1;
9605 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9606 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9610 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9613 static void CheckExit(int x, int y)
9615 if (game.gems_still_needed > 0 ||
9616 game.sokoban_fields_still_needed > 0 ||
9617 game.sokoban_objects_still_needed > 0 ||
9618 game.lights_still_needed > 0)
9620 int element = Tile[x][y];
9621 int graphic = el2img(element);
9623 if (IS_ANIMATED(graphic))
9624 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9629 // do not re-open exit door closed after last player
9630 if (game.all_players_gone)
9633 Tile[x][y] = EL_EXIT_OPENING;
9635 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9638 static void CheckExitEM(int x, int y)
9640 if (game.gems_still_needed > 0 ||
9641 game.sokoban_fields_still_needed > 0 ||
9642 game.sokoban_objects_still_needed > 0 ||
9643 game.lights_still_needed > 0)
9645 int element = Tile[x][y];
9646 int graphic = el2img(element);
9648 if (IS_ANIMATED(graphic))
9649 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9654 // do not re-open exit door closed after last player
9655 if (game.all_players_gone)
9658 Tile[x][y] = EL_EM_EXIT_OPENING;
9660 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9663 static void CheckExitSteel(int x, int y)
9665 if (game.gems_still_needed > 0 ||
9666 game.sokoban_fields_still_needed > 0 ||
9667 game.sokoban_objects_still_needed > 0 ||
9668 game.lights_still_needed > 0)
9670 int element = Tile[x][y];
9671 int graphic = el2img(element);
9673 if (IS_ANIMATED(graphic))
9674 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9679 // do not re-open exit door closed after last player
9680 if (game.all_players_gone)
9683 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9685 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9688 static void CheckExitSteelEM(int x, int y)
9690 if (game.gems_still_needed > 0 ||
9691 game.sokoban_fields_still_needed > 0 ||
9692 game.sokoban_objects_still_needed > 0 ||
9693 game.lights_still_needed > 0)
9695 int element = Tile[x][y];
9696 int graphic = el2img(element);
9698 if (IS_ANIMATED(graphic))
9699 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9704 // do not re-open exit door closed after last player
9705 if (game.all_players_gone)
9708 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9710 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9713 static void CheckExitSP(int x, int y)
9715 if (game.gems_still_needed > 0)
9717 int element = Tile[x][y];
9718 int graphic = el2img(element);
9720 if (IS_ANIMATED(graphic))
9721 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9726 // do not re-open exit door closed after last player
9727 if (game.all_players_gone)
9730 Tile[x][y] = EL_SP_EXIT_OPENING;
9732 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9735 static void CloseAllOpenTimegates(void)
9739 SCAN_PLAYFIELD(x, y)
9741 int element = Tile[x][y];
9743 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9745 Tile[x][y] = EL_TIMEGATE_CLOSING;
9747 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9752 static void DrawTwinkleOnField(int x, int y)
9754 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9757 if (Tile[x][y] == EL_BD_DIAMOND)
9760 if (MovDelay[x][y] == 0) // next animation frame
9761 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9763 if (MovDelay[x][y] != 0) // wait some time before next frame
9767 DrawLevelElementAnimation(x, y, Tile[x][y]);
9769 if (MovDelay[x][y] != 0)
9771 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9772 10 - MovDelay[x][y]);
9774 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9779 static void WallGrowing(int x, int y)
9783 if (!MovDelay[x][y]) // next animation frame
9784 MovDelay[x][y] = 3 * delay;
9786 if (MovDelay[x][y]) // wait some time before next frame
9790 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9792 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9793 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9795 DrawLevelGraphic(x, y, graphic, frame);
9798 if (!MovDelay[x][y])
9800 if (MovDir[x][y] == MV_LEFT)
9802 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9803 TEST_DrawLevelField(x - 1, y);
9805 else if (MovDir[x][y] == MV_RIGHT)
9807 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9808 TEST_DrawLevelField(x + 1, y);
9810 else if (MovDir[x][y] == MV_UP)
9812 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9813 TEST_DrawLevelField(x, y - 1);
9817 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9818 TEST_DrawLevelField(x, y + 1);
9821 Tile[x][y] = Store[x][y];
9823 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9824 TEST_DrawLevelField(x, y);
9829 static void CheckWallGrowing(int ax, int ay)
9831 int element = Tile[ax][ay];
9832 int graphic = el2img(element);
9833 boolean free_top = FALSE;
9834 boolean free_bottom = FALSE;
9835 boolean free_left = FALSE;
9836 boolean free_right = FALSE;
9837 boolean stop_top = FALSE;
9838 boolean stop_bottom = FALSE;
9839 boolean stop_left = FALSE;
9840 boolean stop_right = FALSE;
9841 boolean new_wall = FALSE;
9843 boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9844 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9845 element == EL_EXPANDABLE_STEELWALL_ANY);
9847 boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9848 element == EL_EXPANDABLE_WALL_ANY ||
9849 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9850 element == EL_EXPANDABLE_STEELWALL_ANY);
9852 boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9853 element == EL_EXPANDABLE_WALL_ANY ||
9854 element == EL_EXPANDABLE_WALL ||
9855 element == EL_BD_EXPANDABLE_WALL ||
9856 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9857 element == EL_EXPANDABLE_STEELWALL_ANY);
9859 boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9860 element == EL_EXPANDABLE_STEELWALL_VERTICAL);
9862 boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9863 element == EL_EXPANDABLE_WALL ||
9864 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL);
9866 int wall_growing = (is_steelwall ?
9867 EL_EXPANDABLE_STEELWALL_GROWING :
9868 EL_EXPANDABLE_WALL_GROWING);
9870 int gfx_wall_growing_up = (is_steelwall ?
9871 IMG_EXPANDABLE_STEELWALL_GROWING_UP :
9872 IMG_EXPANDABLE_WALL_GROWING_UP);
9873 int gfx_wall_growing_down = (is_steelwall ?
9874 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN :
9875 IMG_EXPANDABLE_WALL_GROWING_DOWN);
9876 int gfx_wall_growing_left = (is_steelwall ?
9877 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT :
9878 IMG_EXPANDABLE_WALL_GROWING_LEFT);
9879 int gfx_wall_growing_right = (is_steelwall ?
9880 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT :
9881 IMG_EXPANDABLE_WALL_GROWING_RIGHT);
9883 if (IS_ANIMATED(graphic))
9884 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9886 if (!MovDelay[ax][ay]) // start building new wall
9887 MovDelay[ax][ay] = 6;
9889 if (MovDelay[ax][ay]) // wait some time before building new wall
9892 if (MovDelay[ax][ay])
9896 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9898 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9900 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9902 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9909 Tile[ax][ay - 1] = wall_growing;
9910 Store[ax][ay - 1] = element;
9911 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9913 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9914 DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0);
9921 Tile[ax][ay + 1] = wall_growing;
9922 Store[ax][ay + 1] = element;
9923 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9925 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9926 DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0);
9932 if (grow_horizontal)
9936 Tile[ax - 1][ay] = wall_growing;
9937 Store[ax - 1][ay] = element;
9938 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9940 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9941 DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0);
9948 Tile[ax + 1][ay] = wall_growing;
9949 Store[ax + 1][ay] = element;
9950 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
9952 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
9953 DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0);
9959 if (element == EL_EXPANDABLE_WALL && (free_left || free_right))
9960 TEST_DrawLevelField(ax, ay);
9962 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
9964 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
9966 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
9968 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
9971 if (((stop_top && stop_bottom) || stop_horizontal) &&
9972 ((stop_left && stop_right) || stop_vertical))
9973 Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL);
9976 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9979 static void CheckForDragon(int x, int y)
9982 boolean dragon_found = FALSE;
9983 struct XY *xy = xy_topdown;
9985 for (i = 0; i < NUM_DIRECTIONS; i++)
9987 for (j = 0; j < 4; j++)
9989 int xx = x + j * xy[i].x;
9990 int yy = y + j * xy[i].y;
9992 if (IN_LEV_FIELD(xx, yy) &&
9993 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9995 if (Tile[xx][yy] == EL_DRAGON)
9996 dragon_found = TRUE;
10005 for (i = 0; i < NUM_DIRECTIONS; i++)
10007 for (j = 0; j < 3; j++)
10009 int xx = x + j * xy[i].x;
10010 int yy = y + j * xy[i].y;
10012 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
10014 Tile[xx][yy] = EL_EMPTY;
10015 TEST_DrawLevelField(xx, yy);
10024 static void InitBuggyBase(int x, int y)
10026 int element = Tile[x][y];
10027 int activating_delay = FRAMES_PER_SECOND / 4;
10029 ChangeDelay[x][y] =
10030 (element == EL_SP_BUGGY_BASE ?
10031 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10032 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10034 element == EL_SP_BUGGY_BASE_ACTIVE ?
10035 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10038 static void WarnBuggyBase(int x, int y)
10041 struct XY *xy = xy_topdown;
10043 for (i = 0; i < NUM_DIRECTIONS; i++)
10045 int xx = x + xy[i].x;
10046 int yy = y + xy[i].y;
10048 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10050 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10057 static void InitTrap(int x, int y)
10059 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10062 static void ActivateTrap(int x, int y)
10064 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10067 static void ChangeActiveTrap(int x, int y)
10069 int graphic = IMG_TRAP_ACTIVE;
10071 // if new animation frame was drawn, correct crumbled sand border
10072 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10073 TEST_DrawLevelFieldCrumbled(x, y);
10076 static int getSpecialActionElement(int element, int number, int base_element)
10078 return (element != EL_EMPTY ? element :
10079 number != -1 ? base_element + number - 1 :
10083 static int getModifiedActionNumber(int value_old, int operator, int operand,
10084 int value_min, int value_max)
10086 int value_new = (operator == CA_MODE_SET ? operand :
10087 operator == CA_MODE_ADD ? value_old + operand :
10088 operator == CA_MODE_SUBTRACT ? value_old - operand :
10089 operator == CA_MODE_MULTIPLY ? value_old * operand :
10090 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10091 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10094 return (value_new < value_min ? value_min :
10095 value_new > value_max ? value_max :
10099 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10101 struct ElementInfo *ei = &element_info[element];
10102 struct ElementChangeInfo *change = &ei->change_page[page];
10103 int target_element = change->target_element;
10104 int action_type = change->action_type;
10105 int action_mode = change->action_mode;
10106 int action_arg = change->action_arg;
10107 int action_element = change->action_element;
10110 if (!change->has_action)
10113 // ---------- determine action paramater values -----------------------------
10115 int level_time_value =
10116 (level.time > 0 ? TimeLeft :
10119 int action_arg_element_raw =
10120 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10121 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10122 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10123 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10124 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10125 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10126 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10128 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10130 int action_arg_direction =
10131 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10132 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10133 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10134 change->actual_trigger_side :
10135 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10136 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10139 int action_arg_number_min =
10140 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10143 int action_arg_number_max =
10144 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10145 action_type == CA_SET_LEVEL_GEMS ? 999 :
10146 action_type == CA_SET_LEVEL_TIME ? 9999 :
10147 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10148 action_type == CA_SET_CE_VALUE ? 9999 :
10149 action_type == CA_SET_CE_SCORE ? 9999 :
10152 int action_arg_number_reset =
10153 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10154 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10155 action_type == CA_SET_LEVEL_TIME ? level.time :
10156 action_type == CA_SET_LEVEL_SCORE ? 0 :
10157 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10158 action_type == CA_SET_CE_SCORE ? 0 :
10161 int action_arg_number =
10162 (action_arg <= CA_ARG_MAX ? action_arg :
10163 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10164 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10165 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10166 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10167 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10168 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10169 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10170 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10171 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10172 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10173 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10174 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10175 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10176 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10177 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10178 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10179 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10180 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10181 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10182 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10183 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10186 int action_arg_number_old =
10187 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10188 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10189 action_type == CA_SET_LEVEL_SCORE ? game.score :
10190 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10191 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10194 int action_arg_number_new =
10195 getModifiedActionNumber(action_arg_number_old,
10196 action_mode, action_arg_number,
10197 action_arg_number_min, action_arg_number_max);
10199 int trigger_player_bits =
10200 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10201 change->actual_trigger_player_bits : change->trigger_player);
10203 int action_arg_player_bits =
10204 (action_arg >= CA_ARG_PLAYER_1 &&
10205 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10206 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10207 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10210 // ---------- execute action -----------------------------------------------
10212 switch (action_type)
10219 // ---------- level actions ----------------------------------------------
10221 case CA_RESTART_LEVEL:
10223 game.restart_level = TRUE;
10228 case CA_SHOW_ENVELOPE:
10230 int element = getSpecialActionElement(action_arg_element,
10231 action_arg_number, EL_ENVELOPE_1);
10233 if (IS_ENVELOPE(element))
10234 local_player->show_envelope = element;
10239 case CA_SET_LEVEL_TIME:
10241 if (level.time > 0) // only modify limited time value
10243 TimeLeft = action_arg_number_new;
10245 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10247 DisplayGameControlValues();
10249 if (!TimeLeft && game.time_limit)
10250 for (i = 0; i < MAX_PLAYERS; i++)
10251 KillPlayer(&stored_player[i]);
10257 case CA_SET_LEVEL_SCORE:
10259 game.score = action_arg_number_new;
10261 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10263 DisplayGameControlValues();
10268 case CA_SET_LEVEL_GEMS:
10270 game.gems_still_needed = action_arg_number_new;
10272 game.snapshot.collected_item = TRUE;
10274 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10276 DisplayGameControlValues();
10281 case CA_SET_LEVEL_WIND:
10283 game.wind_direction = action_arg_direction;
10288 case CA_SET_LEVEL_RANDOM_SEED:
10290 // ensure that setting a new random seed while playing is predictable
10291 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10296 // ---------- player actions ---------------------------------------------
10298 case CA_MOVE_PLAYER:
10299 case CA_MOVE_PLAYER_NEW:
10301 // automatically move to the next field in specified direction
10302 for (i = 0; i < MAX_PLAYERS; i++)
10303 if (trigger_player_bits & (1 << i))
10304 if (action_type == CA_MOVE_PLAYER ||
10305 stored_player[i].MovPos == 0)
10306 stored_player[i].programmed_action = action_arg_direction;
10311 case CA_EXIT_PLAYER:
10313 for (i = 0; i < MAX_PLAYERS; i++)
10314 if (action_arg_player_bits & (1 << i))
10315 ExitPlayer(&stored_player[i]);
10317 if (game.players_still_needed == 0)
10323 case CA_KILL_PLAYER:
10325 for (i = 0; i < MAX_PLAYERS; i++)
10326 if (action_arg_player_bits & (1 << i))
10327 KillPlayer(&stored_player[i]);
10332 case CA_SET_PLAYER_KEYS:
10334 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10335 int element = getSpecialActionElement(action_arg_element,
10336 action_arg_number, EL_KEY_1);
10338 if (IS_KEY(element))
10340 for (i = 0; i < MAX_PLAYERS; i++)
10342 if (trigger_player_bits & (1 << i))
10344 stored_player[i].key[KEY_NR(element)] = key_state;
10346 DrawGameDoorValues();
10354 case CA_SET_PLAYER_SPEED:
10356 for (i = 0; i < MAX_PLAYERS; i++)
10358 if (trigger_player_bits & (1 << i))
10360 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10362 if (action_arg == CA_ARG_SPEED_FASTER &&
10363 stored_player[i].cannot_move)
10365 action_arg_number = STEPSIZE_VERY_SLOW;
10367 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10368 action_arg == CA_ARG_SPEED_FASTER)
10370 action_arg_number = 2;
10371 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10374 else if (action_arg == CA_ARG_NUMBER_RESET)
10376 action_arg_number = level.initial_player_stepsize[i];
10380 getModifiedActionNumber(move_stepsize,
10383 action_arg_number_min,
10384 action_arg_number_max);
10386 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10393 case CA_SET_PLAYER_SHIELD:
10395 for (i = 0; i < MAX_PLAYERS; i++)
10397 if (trigger_player_bits & (1 << i))
10399 if (action_arg == CA_ARG_SHIELD_OFF)
10401 stored_player[i].shield_normal_time_left = 0;
10402 stored_player[i].shield_deadly_time_left = 0;
10404 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10406 stored_player[i].shield_normal_time_left = 999999;
10408 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10410 stored_player[i].shield_normal_time_left = 999999;
10411 stored_player[i].shield_deadly_time_left = 999999;
10419 case CA_SET_PLAYER_GRAVITY:
10421 for (i = 0; i < MAX_PLAYERS; i++)
10423 if (trigger_player_bits & (1 << i))
10425 stored_player[i].gravity =
10426 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10427 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10428 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10429 stored_player[i].gravity);
10436 case CA_SET_PLAYER_ARTWORK:
10438 for (i = 0; i < MAX_PLAYERS; i++)
10440 if (trigger_player_bits & (1 << i))
10442 int artwork_element = action_arg_element;
10444 if (action_arg == CA_ARG_ELEMENT_RESET)
10446 (level.use_artwork_element[i] ? level.artwork_element[i] :
10447 stored_player[i].element_nr);
10449 if (stored_player[i].artwork_element != artwork_element)
10450 stored_player[i].Frame = 0;
10452 stored_player[i].artwork_element = artwork_element;
10454 SetPlayerWaiting(&stored_player[i], FALSE);
10456 // set number of special actions for bored and sleeping animation
10457 stored_player[i].num_special_action_bored =
10458 get_num_special_action(artwork_element,
10459 ACTION_BORING_1, ACTION_BORING_LAST);
10460 stored_player[i].num_special_action_sleeping =
10461 get_num_special_action(artwork_element,
10462 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10469 case CA_SET_PLAYER_INVENTORY:
10471 for (i = 0; i < MAX_PLAYERS; i++)
10473 struct PlayerInfo *player = &stored_player[i];
10476 if (trigger_player_bits & (1 << i))
10478 int inventory_element = action_arg_element;
10480 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10481 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10482 action_arg == CA_ARG_ELEMENT_ACTION)
10484 int element = inventory_element;
10485 int collect_count = element_info[element].collect_count_initial;
10487 if (!IS_CUSTOM_ELEMENT(element))
10490 if (collect_count == 0)
10491 player->inventory_infinite_element = element;
10493 for (k = 0; k < collect_count; k++)
10494 if (player->inventory_size < MAX_INVENTORY_SIZE)
10495 player->inventory_element[player->inventory_size++] =
10498 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10499 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10500 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10502 if (player->inventory_infinite_element != EL_UNDEFINED &&
10503 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10504 action_arg_element_raw))
10505 player->inventory_infinite_element = EL_UNDEFINED;
10507 for (k = 0, j = 0; j < player->inventory_size; j++)
10509 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10510 action_arg_element_raw))
10511 player->inventory_element[k++] = player->inventory_element[j];
10514 player->inventory_size = k;
10516 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10518 if (player->inventory_size > 0)
10520 for (j = 0; j < player->inventory_size - 1; j++)
10521 player->inventory_element[j] = player->inventory_element[j + 1];
10523 player->inventory_size--;
10526 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10528 if (player->inventory_size > 0)
10529 player->inventory_size--;
10531 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10533 player->inventory_infinite_element = EL_UNDEFINED;
10534 player->inventory_size = 0;
10536 else if (action_arg == CA_ARG_INVENTORY_RESET)
10538 player->inventory_infinite_element = EL_UNDEFINED;
10539 player->inventory_size = 0;
10541 if (level.use_initial_inventory[i])
10543 for (j = 0; j < level.initial_inventory_size[i]; j++)
10545 int element = level.initial_inventory_content[i][j];
10546 int collect_count = element_info[element].collect_count_initial;
10548 if (!IS_CUSTOM_ELEMENT(element))
10551 if (collect_count == 0)
10552 player->inventory_infinite_element = element;
10554 for (k = 0; k < collect_count; k++)
10555 if (player->inventory_size < MAX_INVENTORY_SIZE)
10556 player->inventory_element[player->inventory_size++] =
10567 // ---------- CE actions -------------------------------------------------
10569 case CA_SET_CE_VALUE:
10571 int last_ce_value = CustomValue[x][y];
10573 CustomValue[x][y] = action_arg_number_new;
10575 if (CustomValue[x][y] != last_ce_value)
10577 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10578 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10580 if (CustomValue[x][y] == 0)
10582 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10583 ChangeCount[x][y] = 0; // allow at least one more change
10585 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10586 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10593 case CA_SET_CE_SCORE:
10595 int last_ce_score = ei->collect_score;
10597 ei->collect_score = action_arg_number_new;
10599 if (ei->collect_score != last_ce_score)
10601 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10602 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10604 if (ei->collect_score == 0)
10608 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10609 ChangeCount[x][y] = 0; // allow at least one more change
10611 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10612 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10615 This is a very special case that seems to be a mixture between
10616 CheckElementChange() and CheckTriggeredElementChange(): while
10617 the first one only affects single elements that are triggered
10618 directly, the second one affects multiple elements in the playfield
10619 that are triggered indirectly by another element. This is a third
10620 case: Changing the CE score always affects multiple identical CEs,
10621 so every affected CE must be checked, not only the single CE for
10622 which the CE score was changed in the first place (as every instance
10623 of that CE shares the same CE score, and therefore also can change)!
10625 SCAN_PLAYFIELD(xx, yy)
10627 if (Tile[xx][yy] == element)
10628 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10629 CE_SCORE_GETS_ZERO);
10637 case CA_SET_CE_ARTWORK:
10639 int artwork_element = action_arg_element;
10640 boolean reset_frame = FALSE;
10643 if (action_arg == CA_ARG_ELEMENT_RESET)
10644 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10647 if (ei->gfx_element != artwork_element)
10648 reset_frame = TRUE;
10650 ei->gfx_element = artwork_element;
10652 SCAN_PLAYFIELD(xx, yy)
10654 if (Tile[xx][yy] == element)
10658 ResetGfxAnimation(xx, yy);
10659 ResetRandomAnimationValue(xx, yy);
10662 TEST_DrawLevelField(xx, yy);
10669 // ---------- engine actions ---------------------------------------------
10671 case CA_SET_ENGINE_SCAN_MODE:
10673 InitPlayfieldScanMode(action_arg);
10683 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10685 int old_element = Tile[x][y];
10686 int new_element = GetElementFromGroupElement(element);
10687 int previous_move_direction = MovDir[x][y];
10688 int last_ce_value = CustomValue[x][y];
10689 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10690 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10691 boolean add_player_onto_element = (new_element_is_player &&
10692 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10693 IS_WALKABLE(old_element));
10695 if (!add_player_onto_element)
10697 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10698 RemoveMovingField(x, y);
10702 Tile[x][y] = new_element;
10704 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10705 MovDir[x][y] = previous_move_direction;
10707 if (element_info[new_element].use_last_ce_value)
10708 CustomValue[x][y] = last_ce_value;
10710 InitField_WithBug1(x, y, FALSE);
10712 new_element = Tile[x][y]; // element may have changed
10714 ResetGfxAnimation(x, y);
10715 ResetRandomAnimationValue(x, y);
10717 TEST_DrawLevelField(x, y);
10719 if (GFX_CRUMBLED(new_element))
10720 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10722 if (old_element == EL_EXPLOSION)
10724 Store[x][y] = Store2[x][y] = 0;
10726 // check if new element replaces an exploding player, requiring cleanup
10727 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
10728 StorePlayer[x][y] = 0;
10731 // check if element under the player changes from accessible to unaccessible
10732 // (needed for special case of dropping element which then changes)
10733 // (must be checked after creating new element for walkable group elements)
10734 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10735 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10737 KillPlayer(PLAYERINFO(x, y));
10743 // "ChangeCount" not set yet to allow "entered by player" change one time
10744 if (new_element_is_player)
10745 RelocatePlayer(x, y, new_element);
10748 ChangeCount[x][y]++; // count number of changes in the same frame
10750 TestIfBadThingTouchesPlayer(x, y);
10751 TestIfPlayerTouchesCustomElement(x, y);
10752 TestIfElementTouchesCustomElement(x, y);
10755 static void CreateField(int x, int y, int element)
10757 CreateFieldExt(x, y, element, FALSE);
10760 static void CreateElementFromChange(int x, int y, int element)
10762 element = GET_VALID_RUNTIME_ELEMENT(element);
10764 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10766 int old_element = Tile[x][y];
10768 // prevent changed element from moving in same engine frame
10769 // unless both old and new element can either fall or move
10770 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10771 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10775 CreateFieldExt(x, y, element, TRUE);
10778 static boolean ChangeElement(int x, int y, int element, int page)
10780 struct ElementInfo *ei = &element_info[element];
10781 struct ElementChangeInfo *change = &ei->change_page[page];
10782 int ce_value = CustomValue[x][y];
10783 int ce_score = ei->collect_score;
10784 int target_element;
10785 int old_element = Tile[x][y];
10787 // always use default change event to prevent running into a loop
10788 if (ChangeEvent[x][y] == -1)
10789 ChangeEvent[x][y] = CE_DELAY;
10791 if (ChangeEvent[x][y] == CE_DELAY)
10793 // reset actual trigger element, trigger player and action element
10794 change->actual_trigger_element = EL_EMPTY;
10795 change->actual_trigger_player = EL_EMPTY;
10796 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10797 change->actual_trigger_side = CH_SIDE_NONE;
10798 change->actual_trigger_ce_value = 0;
10799 change->actual_trigger_ce_score = 0;
10800 change->actual_trigger_x = -1;
10801 change->actual_trigger_y = -1;
10804 // do not change elements more than a specified maximum number of changes
10805 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10808 ChangeCount[x][y]++; // count number of changes in the same frame
10810 if (ei->has_anim_event)
10811 HandleGlobalAnimEventByElementChange(element, page, x, y,
10812 change->actual_trigger_x,
10813 change->actual_trigger_y);
10815 if (change->explode)
10822 if (change->use_target_content)
10824 boolean complete_replace = TRUE;
10825 boolean can_replace[3][3];
10828 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10831 boolean is_walkable;
10832 boolean is_diggable;
10833 boolean is_collectible;
10834 boolean is_removable;
10835 boolean is_destructible;
10836 int ex = x + xx - 1;
10837 int ey = y + yy - 1;
10838 int content_element = change->target_content.e[xx][yy];
10841 can_replace[xx][yy] = TRUE;
10843 if (ex == x && ey == y) // do not check changing element itself
10846 if (content_element == EL_EMPTY_SPACE)
10848 can_replace[xx][yy] = FALSE; // do not replace border with space
10853 if (!IN_LEV_FIELD(ex, ey))
10855 can_replace[xx][yy] = FALSE;
10856 complete_replace = FALSE;
10863 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10864 e = MovingOrBlocked2Element(ex, ey);
10866 is_empty = (IS_FREE(ex, ey) ||
10867 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10869 is_walkable = (is_empty || IS_WALKABLE(e));
10870 is_diggable = (is_empty || IS_DIGGABLE(e));
10871 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10872 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10873 is_removable = (is_diggable || is_collectible);
10875 can_replace[xx][yy] =
10876 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10877 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10878 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10879 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10880 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10881 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10882 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10884 if (!can_replace[xx][yy])
10885 complete_replace = FALSE;
10888 if (!change->only_if_complete || complete_replace)
10890 boolean something_has_changed = FALSE;
10892 if (change->only_if_complete && change->use_random_replace &&
10893 RND(100) < change->random_percentage)
10896 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10898 int ex = x + xx - 1;
10899 int ey = y + yy - 1;
10900 int content_element;
10902 if (can_replace[xx][yy] && (!change->use_random_replace ||
10903 RND(100) < change->random_percentage))
10905 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10906 RemoveMovingField(ex, ey);
10908 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10910 content_element = change->target_content.e[xx][yy];
10911 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10912 ce_value, ce_score);
10914 CreateElementFromChange(ex, ey, target_element);
10916 something_has_changed = TRUE;
10918 // for symmetry reasons, freeze newly created border elements
10919 if (ex != x || ey != y)
10920 Stop[ex][ey] = TRUE; // no more moving in this frame
10924 if (something_has_changed)
10926 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10927 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10933 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10934 ce_value, ce_score);
10936 if (element == EL_DIAGONAL_GROWING ||
10937 element == EL_DIAGONAL_SHRINKING)
10939 target_element = Store[x][y];
10941 Store[x][y] = EL_EMPTY;
10944 // special case: element changes to player (and may be kept if walkable)
10945 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10946 CreateElementFromChange(x, y, EL_EMPTY);
10948 CreateElementFromChange(x, y, target_element);
10950 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10951 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10954 // this uses direct change before indirect change
10955 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10960 static void HandleElementChange(int x, int y, int page)
10962 int element = MovingOrBlocked2Element(x, y);
10963 struct ElementInfo *ei = &element_info[element];
10964 struct ElementChangeInfo *change = &ei->change_page[page];
10965 boolean handle_action_before_change = FALSE;
10968 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10969 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10971 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10972 x, y, element, element_info[element].token_name);
10973 Debug("game:playing:HandleElementChange", "This should never happen!");
10977 // this can happen with classic bombs on walkable, changing elements
10978 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10983 if (ChangeDelay[x][y] == 0) // initialize element change
10985 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10987 if (change->can_change)
10989 // !!! not clear why graphic animation should be reset at all here !!!
10990 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10991 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10994 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10996 When using an animation frame delay of 1 (this only happens with
10997 "sp_zonk.moving.left/right" in the classic graphics), the default
10998 (non-moving) animation shows wrong animation frames (while the
10999 moving animation, like "sp_zonk.moving.left/right", is correct,
11000 so this graphical bug never shows up with the classic graphics).
11001 For an animation with 4 frames, this causes wrong frames 0,0,1,2
11002 be drawn instead of the correct frames 0,1,2,3. This is caused by
11003 "GfxFrame[][]" being reset *twice* (in two successive frames) after
11004 an element change: First when the change delay ("ChangeDelay[][]")
11005 counter has reached zero after decrementing, then a second time in
11006 the next frame (after "GfxFrame[][]" was already incremented) when
11007 "ChangeDelay[][]" is reset to the initial delay value again.
11009 This causes frame 0 to be drawn twice, while the last frame won't
11010 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
11012 As some animations may already be cleverly designed around this bug
11013 (at least the "Snake Bite" snake tail animation does this), it cannot
11014 simply be fixed here without breaking such existing animations.
11015 Unfortunately, it cannot easily be detected if a graphics set was
11016 designed "before" or "after" the bug was fixed. As a workaround,
11017 a new graphics set option "game.graphics_engine_version" was added
11018 to be able to specify the game's major release version for which the
11019 graphics set was designed, which can then be used to decide if the
11020 bugfix should be used (version 4 and above) or not (version 3 or
11021 below, or if no version was specified at all, as with old sets).
11023 (The wrong/fixed animation frames can be tested with the test level set
11024 "test_gfxframe" and level "000", which contains a specially prepared
11025 custom element at level position (x/y) == (11/9) which uses the zonk
11026 animation mentioned above. Using "game.graphics_engine_version: 4"
11027 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
11028 This can also be seen from the debug output for this test element.)
11031 // when a custom element is about to change (for example by change delay),
11032 // do not reset graphic animation when the custom element is moving
11033 if (game.graphics_engine_version < 4 &&
11036 ResetGfxAnimation(x, y);
11037 ResetRandomAnimationValue(x, y);
11040 if (change->pre_change_function)
11041 change->pre_change_function(x, y);
11045 ChangeDelay[x][y]--;
11047 if (ChangeDelay[x][y] != 0) // continue element change
11049 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11051 // also needed if CE can not change, but has CE delay with CE action
11052 if (IS_ANIMATED(graphic))
11053 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11055 if (change->can_change)
11057 if (change->change_function)
11058 change->change_function(x, y);
11061 else // finish element change
11063 if (ChangePage[x][y] != -1) // remember page from delayed change
11065 page = ChangePage[x][y];
11066 ChangePage[x][y] = -1;
11068 change = &ei->change_page[page];
11071 if (IS_MOVING(x, y)) // never change a running system ;-)
11073 ChangeDelay[x][y] = 1; // try change after next move step
11074 ChangePage[x][y] = page; // remember page to use for change
11079 // special case: set new level random seed before changing element
11080 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11081 handle_action_before_change = TRUE;
11083 if (change->has_action && handle_action_before_change)
11084 ExecuteCustomElementAction(x, y, element, page);
11086 if (change->can_change)
11088 if (ChangeElement(x, y, element, page))
11090 if (change->post_change_function)
11091 change->post_change_function(x, y);
11095 if (change->has_action && !handle_action_before_change)
11096 ExecuteCustomElementAction(x, y, element, page);
11100 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11101 int trigger_element,
11103 int trigger_player,
11107 boolean change_done_any = FALSE;
11108 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11111 if (!(trigger_events[trigger_element][trigger_event]))
11114 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11116 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11118 int element = EL_CUSTOM_START + i;
11119 boolean change_done = FALSE;
11122 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11123 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11126 for (p = 0; p < element_info[element].num_change_pages; p++)
11128 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11130 if (change->can_change_or_has_action &&
11131 change->has_event[trigger_event] &&
11132 change->trigger_side & trigger_side &&
11133 change->trigger_player & trigger_player &&
11134 change->trigger_page & trigger_page_bits &&
11135 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11137 change->actual_trigger_element = trigger_element;
11138 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11139 change->actual_trigger_player_bits = trigger_player;
11140 change->actual_trigger_side = trigger_side;
11141 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11142 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11143 change->actual_trigger_x = trigger_x;
11144 change->actual_trigger_y = trigger_y;
11146 if ((change->can_change && !change_done) || change->has_action)
11150 SCAN_PLAYFIELD(x, y)
11152 if (Tile[x][y] == element)
11154 if (change->can_change && !change_done)
11156 // if element already changed in this frame, not only prevent
11157 // another element change (checked in ChangeElement()), but
11158 // also prevent additional element actions for this element
11160 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11161 !level.use_action_after_change_bug)
11164 ChangeDelay[x][y] = 1;
11165 ChangeEvent[x][y] = trigger_event;
11167 HandleElementChange(x, y, p);
11169 else if (change->has_action)
11171 // if element already changed in this frame, not only prevent
11172 // another element change (checked in ChangeElement()), but
11173 // also prevent additional element actions for this element
11175 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11176 !level.use_action_after_change_bug)
11179 ExecuteCustomElementAction(x, y, element, p);
11180 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11185 if (change->can_change)
11187 change_done = TRUE;
11188 change_done_any = TRUE;
11195 RECURSION_LOOP_DETECTION_END();
11197 return change_done_any;
11200 static boolean CheckElementChangeExt(int x, int y,
11202 int trigger_element,
11204 int trigger_player,
11207 boolean change_done = FALSE;
11210 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11211 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11214 if (Tile[x][y] == EL_BLOCKED)
11216 Blocked2Moving(x, y, &x, &y);
11217 element = Tile[x][y];
11220 // check if element has already changed or is about to change after moving
11221 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11222 Tile[x][y] != element) ||
11224 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11225 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11226 ChangePage[x][y] != -1)))
11229 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11231 for (p = 0; p < element_info[element].num_change_pages; p++)
11233 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11235 /* check trigger element for all events where the element that is checked
11236 for changing interacts with a directly adjacent element -- this is
11237 different to element changes that affect other elements to change on the
11238 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11239 boolean check_trigger_element =
11240 (trigger_event == CE_NEXT_TO_X ||
11241 trigger_event == CE_TOUCHING_X ||
11242 trigger_event == CE_HITTING_X ||
11243 trigger_event == CE_HIT_BY_X ||
11244 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11246 if (change->can_change_or_has_action &&
11247 change->has_event[trigger_event] &&
11248 change->trigger_side & trigger_side &&
11249 change->trigger_player & trigger_player &&
11250 (!check_trigger_element ||
11251 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11253 change->actual_trigger_element = trigger_element;
11254 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11255 change->actual_trigger_player_bits = trigger_player;
11256 change->actual_trigger_side = trigger_side;
11257 change->actual_trigger_ce_value = CustomValue[x][y];
11258 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11259 change->actual_trigger_x = x;
11260 change->actual_trigger_y = y;
11262 // special case: trigger element not at (x,y) position for some events
11263 if (check_trigger_element)
11275 { 0, 0 }, { 0, 0 }, { 0, 0 },
11279 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11280 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11282 change->actual_trigger_ce_value = CustomValue[xx][yy];
11283 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11284 change->actual_trigger_x = xx;
11285 change->actual_trigger_y = yy;
11288 if (change->can_change && !change_done)
11290 ChangeDelay[x][y] = 1;
11291 ChangeEvent[x][y] = trigger_event;
11293 HandleElementChange(x, y, p);
11295 change_done = TRUE;
11297 else if (change->has_action)
11299 ExecuteCustomElementAction(x, y, element, p);
11300 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11305 RECURSION_LOOP_DETECTION_END();
11307 return change_done;
11310 static void PlayPlayerSound(struct PlayerInfo *player)
11312 int jx = player->jx, jy = player->jy;
11313 int sound_element = player->artwork_element;
11314 int last_action = player->last_action_waiting;
11315 int action = player->action_waiting;
11317 if (player->is_waiting)
11319 if (action != last_action)
11320 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11322 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11326 if (action != last_action)
11327 StopSound(element_info[sound_element].sound[last_action]);
11329 if (last_action == ACTION_SLEEPING)
11330 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11334 static void PlayAllPlayersSound(void)
11338 for (i = 0; i < MAX_PLAYERS; i++)
11339 if (stored_player[i].active)
11340 PlayPlayerSound(&stored_player[i]);
11343 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11345 boolean last_waiting = player->is_waiting;
11346 int move_dir = player->MovDir;
11348 player->dir_waiting = move_dir;
11349 player->last_action_waiting = player->action_waiting;
11353 if (!last_waiting) // not waiting -> waiting
11355 player->is_waiting = TRUE;
11357 player->frame_counter_bored =
11359 game.player_boring_delay_fixed +
11360 GetSimpleRandom(game.player_boring_delay_random);
11361 player->frame_counter_sleeping =
11363 game.player_sleeping_delay_fixed +
11364 GetSimpleRandom(game.player_sleeping_delay_random);
11366 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11369 if (game.player_sleeping_delay_fixed +
11370 game.player_sleeping_delay_random > 0 &&
11371 player->anim_delay_counter == 0 &&
11372 player->post_delay_counter == 0 &&
11373 FrameCounter >= player->frame_counter_sleeping)
11374 player->is_sleeping = TRUE;
11375 else if (game.player_boring_delay_fixed +
11376 game.player_boring_delay_random > 0 &&
11377 FrameCounter >= player->frame_counter_bored)
11378 player->is_bored = TRUE;
11380 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11381 player->is_bored ? ACTION_BORING :
11384 if (player->is_sleeping && player->use_murphy)
11386 // special case for sleeping Murphy when leaning against non-free tile
11388 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11389 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11390 !IS_MOVING(player->jx - 1, player->jy)))
11391 move_dir = MV_LEFT;
11392 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11393 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11394 !IS_MOVING(player->jx + 1, player->jy)))
11395 move_dir = MV_RIGHT;
11397 player->is_sleeping = FALSE;
11399 player->dir_waiting = move_dir;
11402 if (player->is_sleeping)
11404 if (player->num_special_action_sleeping > 0)
11406 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11408 int last_special_action = player->special_action_sleeping;
11409 int num_special_action = player->num_special_action_sleeping;
11410 int special_action =
11411 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11412 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11413 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11414 last_special_action + 1 : ACTION_SLEEPING);
11415 int special_graphic =
11416 el_act_dir2img(player->artwork_element, special_action, move_dir);
11418 player->anim_delay_counter =
11419 graphic_info[special_graphic].anim_delay_fixed +
11420 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11421 player->post_delay_counter =
11422 graphic_info[special_graphic].post_delay_fixed +
11423 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11425 player->special_action_sleeping = special_action;
11428 if (player->anim_delay_counter > 0)
11430 player->action_waiting = player->special_action_sleeping;
11431 player->anim_delay_counter--;
11433 else if (player->post_delay_counter > 0)
11435 player->post_delay_counter--;
11439 else if (player->is_bored)
11441 if (player->num_special_action_bored > 0)
11443 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11445 int special_action =
11446 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11447 int special_graphic =
11448 el_act_dir2img(player->artwork_element, special_action, move_dir);
11450 player->anim_delay_counter =
11451 graphic_info[special_graphic].anim_delay_fixed +
11452 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11453 player->post_delay_counter =
11454 graphic_info[special_graphic].post_delay_fixed +
11455 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11457 player->special_action_bored = special_action;
11460 if (player->anim_delay_counter > 0)
11462 player->action_waiting = player->special_action_bored;
11463 player->anim_delay_counter--;
11465 else if (player->post_delay_counter > 0)
11467 player->post_delay_counter--;
11472 else if (last_waiting) // waiting -> not waiting
11474 player->is_waiting = FALSE;
11475 player->is_bored = FALSE;
11476 player->is_sleeping = FALSE;
11478 player->frame_counter_bored = -1;
11479 player->frame_counter_sleeping = -1;
11481 player->anim_delay_counter = 0;
11482 player->post_delay_counter = 0;
11484 player->dir_waiting = player->MovDir;
11485 player->action_waiting = ACTION_DEFAULT;
11487 player->special_action_bored = ACTION_DEFAULT;
11488 player->special_action_sleeping = ACTION_DEFAULT;
11492 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11494 if ((!player->is_moving && player->was_moving) ||
11495 (player->MovPos == 0 && player->was_moving) ||
11496 (player->is_snapping && !player->was_snapping) ||
11497 (player->is_dropping && !player->was_dropping))
11499 if (!CheckSaveEngineSnapshotToList())
11502 player->was_moving = FALSE;
11503 player->was_snapping = TRUE;
11504 player->was_dropping = TRUE;
11508 if (player->is_moving)
11509 player->was_moving = TRUE;
11511 if (!player->is_snapping)
11512 player->was_snapping = FALSE;
11514 if (!player->is_dropping)
11515 player->was_dropping = FALSE;
11518 static struct MouseActionInfo mouse_action_last = { 0 };
11519 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11520 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11523 CheckSaveEngineSnapshotToList();
11525 mouse_action_last = mouse_action;
11528 static void CheckSingleStepMode(struct PlayerInfo *player)
11530 if (tape.single_step && tape.recording && !tape.pausing)
11532 // as it is called "single step mode", just return to pause mode when the
11533 // player stopped moving after one tile (or never starts moving at all)
11534 // (reverse logic needed here in case single step mode used in team mode)
11535 if (player->is_moving ||
11536 player->is_pushing ||
11537 player->is_dropping_pressed ||
11538 player->effective_mouse_action.button)
11539 game.enter_single_step_mode = FALSE;
11542 CheckSaveEngineSnapshot(player);
11545 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11547 int left = player_action & JOY_LEFT;
11548 int right = player_action & JOY_RIGHT;
11549 int up = player_action & JOY_UP;
11550 int down = player_action & JOY_DOWN;
11551 int button1 = player_action & JOY_BUTTON_1;
11552 int button2 = player_action & JOY_BUTTON_2;
11553 int dx = (left ? -1 : right ? 1 : 0);
11554 int dy = (up ? -1 : down ? 1 : 0);
11556 if (!player->active || tape.pausing)
11562 SnapField(player, dx, dy);
11566 DropElement(player);
11568 MovePlayer(player, dx, dy);
11571 CheckSingleStepMode(player);
11573 SetPlayerWaiting(player, FALSE);
11575 return player_action;
11579 // no actions for this player (no input at player's configured device)
11581 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11582 SnapField(player, 0, 0);
11583 CheckGravityMovementWhenNotMoving(player);
11585 if (player->MovPos == 0)
11586 SetPlayerWaiting(player, TRUE);
11588 if (player->MovPos == 0) // needed for tape.playing
11589 player->is_moving = FALSE;
11591 player->is_dropping = FALSE;
11592 player->is_dropping_pressed = FALSE;
11593 player->drop_pressed_delay = 0;
11595 CheckSingleStepMode(player);
11601 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11604 if (!tape.use_mouse_actions)
11607 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11608 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11609 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11612 static void SetTapeActionFromMouseAction(byte *tape_action,
11613 struct MouseActionInfo *mouse_action)
11615 if (!tape.use_mouse_actions)
11618 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11619 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11620 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11623 static void CheckLevelSolved(void)
11625 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11627 if (game_em.level_solved &&
11628 !game_em.game_over) // game won
11632 game_em.game_over = TRUE;
11634 game.all_players_gone = TRUE;
11637 if (game_em.game_over) // game lost
11638 game.all_players_gone = TRUE;
11640 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11642 if (game_sp.level_solved &&
11643 !game_sp.game_over) // game won
11647 game_sp.game_over = TRUE;
11649 game.all_players_gone = TRUE;
11652 if (game_sp.game_over) // game lost
11653 game.all_players_gone = TRUE;
11655 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11657 if (game_mm.level_solved &&
11658 !game_mm.game_over) // game won
11662 game_mm.game_over = TRUE;
11664 game.all_players_gone = TRUE;
11667 if (game_mm.game_over) // game lost
11668 game.all_players_gone = TRUE;
11672 static void PlayTimeoutSound(int seconds_left)
11674 // try to use individual "running out of time" sound for each second left
11675 int sound = SND_GAME_RUNNING_OUT_OF_TIME_0 - seconds_left;
11677 // if special sound per second not defined, use default sound
11678 if (getSoundInfoEntryFilename(sound) == NULL)
11679 sound = SND_GAME_RUNNING_OUT_OF_TIME;
11681 // if out of time, but player still alive, play special "timeout" sound, if defined
11682 if (seconds_left == 0 && !checkGameFailed())
11683 if (getSoundInfoEntryFilename(SND_GAME_TIMEOUT) != NULL)
11684 sound = SND_GAME_TIMEOUT;
11689 static void CheckLevelTime_StepCounter(void)
11699 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11700 PlayTimeoutSound(TimeLeft);
11702 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11704 DisplayGameControlValues();
11706 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11707 for (i = 0; i < MAX_PLAYERS; i++)
11708 KillPlayer(&stored_player[i]);
11710 else if (game.no_level_time_limit && !game.all_players_gone)
11712 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11714 DisplayGameControlValues();
11718 static void CheckLevelTime(void)
11722 if (TimeFrames >= FRAMES_PER_SECOND)
11726 for (i = 0; i < MAX_PLAYERS; i++)
11728 struct PlayerInfo *player = &stored_player[i];
11730 if (SHIELD_ON(player))
11732 player->shield_normal_time_left--;
11734 if (player->shield_deadly_time_left > 0)
11735 player->shield_deadly_time_left--;
11739 if (!game.LevelSolved && !level.use_step_counter)
11747 if (TimeLeft <= 10 && game.time_limit)
11748 PlayTimeoutSound(TimeLeft);
11750 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11751 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11753 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11755 if (!TimeLeft && game.time_limit)
11757 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11758 game_em.lev->killed_out_of_time = TRUE;
11760 for (i = 0; i < MAX_PLAYERS; i++)
11761 KillPlayer(&stored_player[i]);
11764 else if (game.no_level_time_limit && !game.all_players_gone)
11766 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11769 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11773 if (TapeTimeFrames >= FRAMES_PER_SECOND)
11775 TapeTimeFrames = 0;
11778 if (tape.recording || tape.playing)
11779 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11782 if (tape.recording || tape.playing)
11783 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11785 UpdateAndDisplayGameControlValues();
11788 void AdvanceFrameAndPlayerCounters(int player_nr)
11792 // advance frame counters (global frame counter and tape time frame counter)
11796 // advance time frame counter (used to control available time to solve level)
11799 // advance player counters (counters for move delay, move animation etc.)
11800 for (i = 0; i < MAX_PLAYERS; i++)
11802 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11803 int move_delay_value = stored_player[i].move_delay_value;
11804 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11806 if (!advance_player_counters) // not all players may be affected
11809 if (move_frames == 0) // less than one move per game frame
11811 int stepsize = TILEX / move_delay_value;
11812 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11813 int count = (stored_player[i].is_moving ?
11814 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11816 if (count % delay == 0)
11820 stored_player[i].Frame += move_frames;
11822 if (stored_player[i].MovPos != 0)
11823 stored_player[i].StepFrame += move_frames;
11825 if (stored_player[i].move_delay > 0)
11826 stored_player[i].move_delay--;
11828 // due to bugs in previous versions, counter must count up, not down
11829 if (stored_player[i].push_delay != -1)
11830 stored_player[i].push_delay++;
11832 if (stored_player[i].drop_delay > 0)
11833 stored_player[i].drop_delay--;
11835 if (stored_player[i].is_dropping_pressed)
11836 stored_player[i].drop_pressed_delay++;
11840 void AdvanceFrameCounter(void)
11845 void AdvanceGfxFrame(void)
11849 SCAN_PLAYFIELD(x, y)
11855 static void HandleMouseAction(struct MouseActionInfo *mouse_action,
11856 struct MouseActionInfo *mouse_action_last)
11858 if (mouse_action->button)
11860 int new_button = (mouse_action->button && mouse_action_last->button == 0);
11861 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button);
11862 int x = mouse_action->lx;
11863 int y = mouse_action->ly;
11864 int element = Tile[x][y];
11868 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
11869 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
11873 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
11874 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
11877 if (level.use_step_counter)
11879 boolean counted_click = FALSE;
11881 // element clicked that can change when clicked/pressed
11882 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
11883 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
11884 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
11885 counted_click = TRUE;
11887 // element clicked that can trigger change when clicked/pressed
11888 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
11889 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
11890 counted_click = TRUE;
11892 if (new_button && counted_click)
11893 CheckLevelTime_StepCounter();
11898 void StartGameActions(boolean init_network_game, boolean record_tape,
11901 unsigned int new_random_seed = InitRND(random_seed);
11904 TapeStartRecording(new_random_seed);
11906 if (setup.auto_pause_on_start && !tape.pausing)
11907 TapeTogglePause(TAPE_TOGGLE_MANUAL);
11909 if (init_network_game)
11911 SendToServer_LevelFile();
11912 SendToServer_StartPlaying();
11920 static void GameActionsExt(void)
11923 static unsigned int game_frame_delay = 0;
11925 unsigned int game_frame_delay_value;
11926 byte *recorded_player_action;
11927 byte summarized_player_action = 0;
11928 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11931 // detect endless loops, caused by custom element programming
11932 if (recursion_loop_detected && recursion_loop_depth == 0)
11934 char *message = getStringCat3("Internal Error! Element ",
11935 EL_NAME(recursion_loop_element),
11936 " caused endless loop! Quit the game?");
11938 Warn("element '%s' caused endless loop in game engine",
11939 EL_NAME(recursion_loop_element));
11941 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11943 recursion_loop_detected = FALSE; // if game should be continued
11950 if (game.restart_level)
11951 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11953 CheckLevelSolved();
11955 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11958 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11961 if (game_status != GAME_MODE_PLAYING) // status might have changed
11964 game_frame_delay_value =
11965 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11967 if (tape.playing && tape.warp_forward && !tape.pausing)
11968 game_frame_delay_value = 0;
11970 SetVideoFrameDelay(game_frame_delay_value);
11972 // (de)activate virtual buttons depending on current game status
11973 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11975 if (game.all_players_gone) // if no players there to be controlled anymore
11976 SetOverlayActive(FALSE);
11977 else if (!tape.playing) // if game continues after tape stopped playing
11978 SetOverlayActive(TRUE);
11983 // ---------- main game synchronization point ----------
11985 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11987 Debug("game:playing:skip", "skip == %d", skip);
11990 // ---------- main game synchronization point ----------
11992 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11996 if (network_playing && !network_player_action_received)
11998 // try to get network player actions in time
12000 // last chance to get network player actions without main loop delay
12001 HandleNetworking();
12003 // game was quit by network peer
12004 if (game_status != GAME_MODE_PLAYING)
12007 // check if network player actions still missing and game still running
12008 if (!network_player_action_received && !checkGameEnded())
12009 return; // failed to get network player actions in time
12011 // do not yet reset "network_player_action_received" (for tape.pausing)
12017 // at this point we know that we really continue executing the game
12019 network_player_action_received = FALSE;
12021 // when playing tape, read previously recorded player input from tape data
12022 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
12024 local_player->effective_mouse_action = local_player->mouse_action;
12026 if (recorded_player_action != NULL)
12027 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
12028 recorded_player_action);
12030 // TapePlayAction() may return NULL when toggling to "pause before death"
12034 if (tape.set_centered_player)
12036 game.centered_player_nr_next = tape.centered_player_nr_next;
12037 game.set_centered_player = TRUE;
12040 for (i = 0; i < MAX_PLAYERS; i++)
12042 summarized_player_action |= stored_player[i].action;
12044 if (!network_playing && (game.team_mode || tape.playing))
12045 stored_player[i].effective_action = stored_player[i].action;
12048 if (network_playing && !checkGameEnded())
12049 SendToServer_MovePlayer(summarized_player_action);
12051 // summarize all actions at local players mapped input device position
12052 // (this allows using different input devices in single player mode)
12053 if (!network.enabled && !game.team_mode)
12054 stored_player[map_player_action[local_player->index_nr]].effective_action =
12055 summarized_player_action;
12057 // summarize all actions at centered player in local team mode
12058 if (tape.recording &&
12059 setup.team_mode && !network.enabled &&
12060 setup.input_on_focus &&
12061 game.centered_player_nr != -1)
12063 for (i = 0; i < MAX_PLAYERS; i++)
12064 stored_player[map_player_action[i]].effective_action =
12065 (i == game.centered_player_nr ? summarized_player_action : 0);
12068 if (recorded_player_action != NULL)
12069 for (i = 0; i < MAX_PLAYERS; i++)
12070 stored_player[i].effective_action = recorded_player_action[i];
12072 for (i = 0; i < MAX_PLAYERS; i++)
12074 tape_action[i] = stored_player[i].effective_action;
12076 /* (this may happen in the RND game engine if a player was not present on
12077 the playfield on level start, but appeared later from a custom element */
12078 if (setup.team_mode &&
12081 !tape.player_participates[i])
12082 tape.player_participates[i] = TRUE;
12085 SetTapeActionFromMouseAction(tape_action,
12086 &local_player->effective_mouse_action);
12088 // only record actions from input devices, but not programmed actions
12089 if (tape.recording)
12090 TapeRecordAction(tape_action);
12092 // remember if game was played (especially after tape stopped playing)
12093 if (!tape.playing && summarized_player_action && !checkGameFailed())
12094 game.GamePlayed = TRUE;
12096 #if USE_NEW_PLAYER_ASSIGNMENTS
12097 // !!! also map player actions in single player mode !!!
12098 // if (game.team_mode)
12101 byte mapped_action[MAX_PLAYERS];
12103 #if DEBUG_PLAYER_ACTIONS
12104 for (i = 0; i < MAX_PLAYERS; i++)
12105 DebugContinued("", "%d, ", stored_player[i].effective_action);
12108 for (i = 0; i < MAX_PLAYERS; i++)
12109 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
12111 for (i = 0; i < MAX_PLAYERS; i++)
12112 stored_player[i].effective_action = mapped_action[i];
12114 #if DEBUG_PLAYER_ACTIONS
12115 DebugContinued("", "=> ");
12116 for (i = 0; i < MAX_PLAYERS; i++)
12117 DebugContinued("", "%d, ", stored_player[i].effective_action);
12118 DebugContinued("game:playing:player", "\n");
12121 #if DEBUG_PLAYER_ACTIONS
12124 for (i = 0; i < MAX_PLAYERS; i++)
12125 DebugContinued("", "%d, ", stored_player[i].effective_action);
12126 DebugContinued("game:playing:player", "\n");
12131 for (i = 0; i < MAX_PLAYERS; i++)
12133 // allow engine snapshot in case of changed movement attempt
12134 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12135 (stored_player[i].effective_action & KEY_MOTION))
12136 game.snapshot.changed_action = TRUE;
12138 // allow engine snapshot in case of snapping/dropping attempt
12139 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12140 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12141 game.snapshot.changed_action = TRUE;
12143 game.snapshot.last_action[i] = stored_player[i].effective_action;
12146 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12148 GameActions_EM_Main();
12150 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12152 GameActions_SP_Main();
12154 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12156 GameActions_MM_Main();
12160 GameActions_RND_Main();
12163 BlitScreenToBitmap(backbuffer);
12165 CheckLevelSolved();
12168 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12170 if (global.show_frames_per_second)
12172 static unsigned int fps_counter = 0;
12173 static int fps_frames = 0;
12174 unsigned int fps_delay_ms = Counter() - fps_counter;
12178 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12180 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12183 fps_counter = Counter();
12185 // always draw FPS to screen after FPS value was updated
12186 redraw_mask |= REDRAW_FPS;
12189 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12190 if (GetDrawDeactivationMask() == REDRAW_NONE)
12191 redraw_mask |= REDRAW_FPS;
12195 static void GameActions_CheckSaveEngineSnapshot(void)
12197 if (!game.snapshot.save_snapshot)
12200 // clear flag for saving snapshot _before_ saving snapshot
12201 game.snapshot.save_snapshot = FALSE;
12203 SaveEngineSnapshotToList();
12206 void GameActions(void)
12210 GameActions_CheckSaveEngineSnapshot();
12213 void GameActions_EM_Main(void)
12215 byte effective_action[MAX_PLAYERS];
12218 for (i = 0; i < MAX_PLAYERS; i++)
12219 effective_action[i] = stored_player[i].effective_action;
12221 GameActions_EM(effective_action);
12224 void GameActions_SP_Main(void)
12226 byte effective_action[MAX_PLAYERS];
12229 for (i = 0; i < MAX_PLAYERS; i++)
12230 effective_action[i] = stored_player[i].effective_action;
12232 GameActions_SP(effective_action);
12234 for (i = 0; i < MAX_PLAYERS; i++)
12236 if (stored_player[i].force_dropping)
12237 stored_player[i].action |= KEY_BUTTON_DROP;
12239 stored_player[i].force_dropping = FALSE;
12243 void GameActions_MM_Main(void)
12247 GameActions_MM(local_player->effective_mouse_action);
12250 void GameActions_RND_Main(void)
12255 void GameActions_RND(void)
12257 static struct MouseActionInfo mouse_action_last = { 0 };
12258 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12259 int magic_wall_x = 0, magic_wall_y = 0;
12260 int i, x, y, element, graphic, last_gfx_frame;
12262 InitPlayfieldScanModeVars();
12264 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12266 SCAN_PLAYFIELD(x, y)
12268 ChangeCount[x][y] = 0;
12269 ChangeEvent[x][y] = -1;
12273 if (game.set_centered_player)
12275 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12277 // switching to "all players" only possible if all players fit to screen
12278 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12280 game.centered_player_nr_next = game.centered_player_nr;
12281 game.set_centered_player = FALSE;
12284 // do not switch focus to non-existing (or non-active) player
12285 if (game.centered_player_nr_next >= 0 &&
12286 !stored_player[game.centered_player_nr_next].active)
12288 game.centered_player_nr_next = game.centered_player_nr;
12289 game.set_centered_player = FALSE;
12293 if (game.set_centered_player &&
12294 ScreenMovPos == 0) // screen currently aligned at tile position
12298 if (game.centered_player_nr_next == -1)
12300 setScreenCenteredToAllPlayers(&sx, &sy);
12304 sx = stored_player[game.centered_player_nr_next].jx;
12305 sy = stored_player[game.centered_player_nr_next].jy;
12308 game.centered_player_nr = game.centered_player_nr_next;
12309 game.set_centered_player = FALSE;
12311 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12312 DrawGameDoorValues();
12315 // check single step mode (set flag and clear again if any player is active)
12316 game.enter_single_step_mode =
12317 (tape.single_step && tape.recording && !tape.pausing);
12319 for (i = 0; i < MAX_PLAYERS; i++)
12321 int actual_player_action = stored_player[i].effective_action;
12324 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12325 - rnd_equinox_tetrachloride 048
12326 - rnd_equinox_tetrachloride_ii 096
12327 - rnd_emanuel_schmieg 002
12328 - doctor_sloan_ww 001, 020
12330 if (stored_player[i].MovPos == 0)
12331 CheckGravityMovement(&stored_player[i]);
12334 // overwrite programmed action with tape action
12335 if (stored_player[i].programmed_action)
12336 actual_player_action = stored_player[i].programmed_action;
12338 PlayerActions(&stored_player[i], actual_player_action);
12340 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12343 // single step pause mode may already have been toggled by "ScrollPlayer()"
12344 if (game.enter_single_step_mode && !tape.pausing)
12345 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12347 ScrollScreen(NULL, SCROLL_GO_ON);
12349 /* for backwards compatibility, the following code emulates a fixed bug that
12350 occured when pushing elements (causing elements that just made their last
12351 pushing step to already (if possible) make their first falling step in the
12352 same game frame, which is bad); this code is also needed to use the famous
12353 "spring push bug" which is used in older levels and might be wanted to be
12354 used also in newer levels, but in this case the buggy pushing code is only
12355 affecting the "spring" element and no other elements */
12357 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12359 for (i = 0; i < MAX_PLAYERS; i++)
12361 struct PlayerInfo *player = &stored_player[i];
12362 int x = player->jx;
12363 int y = player->jy;
12365 if (player->active && player->is_pushing && player->is_moving &&
12367 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12368 Tile[x][y] == EL_SPRING))
12370 ContinueMoving(x, y);
12372 // continue moving after pushing (this is actually a bug)
12373 if (!IS_MOVING(x, y))
12374 Stop[x][y] = FALSE;
12379 SCAN_PLAYFIELD(x, y)
12381 Last[x][y] = Tile[x][y];
12383 ChangeCount[x][y] = 0;
12384 ChangeEvent[x][y] = -1;
12386 // this must be handled before main playfield loop
12387 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12390 if (MovDelay[x][y] <= 0)
12394 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12397 if (MovDelay[x][y] <= 0)
12399 int element = Store[x][y];
12400 int move_direction = MovDir[x][y];
12401 int player_index_bit = Store2[x][y];
12407 TEST_DrawLevelField(x, y);
12409 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12411 if (IS_ENVELOPE(element))
12412 local_player->show_envelope = element;
12417 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12419 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12421 Debug("game:playing:GameActions_RND", "This should never happen!");
12423 ChangePage[x][y] = -1;
12427 Stop[x][y] = FALSE;
12428 if (WasJustMoving[x][y] > 0)
12429 WasJustMoving[x][y]--;
12430 if (WasJustFalling[x][y] > 0)
12431 WasJustFalling[x][y]--;
12432 if (CheckCollision[x][y] > 0)
12433 CheckCollision[x][y]--;
12434 if (CheckImpact[x][y] > 0)
12435 CheckImpact[x][y]--;
12439 /* reset finished pushing action (not done in ContinueMoving() to allow
12440 continuous pushing animation for elements with zero push delay) */
12441 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12443 ResetGfxAnimation(x, y);
12444 TEST_DrawLevelField(x, y);
12448 if (IS_BLOCKED(x, y))
12452 Blocked2Moving(x, y, &oldx, &oldy);
12453 if (!IS_MOVING(oldx, oldy))
12455 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12456 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12457 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12458 Debug("game:playing:GameActions_RND", "This should never happen!");
12464 HandleMouseAction(&mouse_action, &mouse_action_last);
12466 SCAN_PLAYFIELD(x, y)
12468 element = Tile[x][y];
12469 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12470 last_gfx_frame = GfxFrame[x][y];
12472 if (element == EL_EMPTY)
12473 graphic = el2img(GfxElementEmpty[x][y]);
12475 ResetGfxFrame(x, y);
12477 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12478 DrawLevelGraphicAnimation(x, y, graphic);
12480 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12481 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12482 ResetRandomAnimationValue(x, y);
12484 SetRandomAnimationValue(x, y);
12486 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12488 if (IS_INACTIVE(element))
12490 if (IS_ANIMATED(graphic))
12491 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12496 // this may take place after moving, so 'element' may have changed
12497 if (IS_CHANGING(x, y) &&
12498 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12500 int page = element_info[element].event_page_nr[CE_DELAY];
12502 HandleElementChange(x, y, page);
12504 element = Tile[x][y];
12505 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12508 CheckNextToConditions(x, y);
12510 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12514 element = Tile[x][y];
12515 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12517 if (IS_ANIMATED(graphic) &&
12518 !IS_MOVING(x, y) &&
12520 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12522 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12523 TEST_DrawTwinkleOnField(x, y);
12525 else if (element == EL_ACID)
12528 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12530 else if ((element == EL_EXIT_OPEN ||
12531 element == EL_EM_EXIT_OPEN ||
12532 element == EL_SP_EXIT_OPEN ||
12533 element == EL_STEEL_EXIT_OPEN ||
12534 element == EL_EM_STEEL_EXIT_OPEN ||
12535 element == EL_SP_TERMINAL ||
12536 element == EL_SP_TERMINAL_ACTIVE ||
12537 element == EL_EXTRA_TIME ||
12538 element == EL_SHIELD_NORMAL ||
12539 element == EL_SHIELD_DEADLY) &&
12540 IS_ANIMATED(graphic))
12541 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12542 else if (IS_MOVING(x, y))
12543 ContinueMoving(x, y);
12544 else if (IS_ACTIVE_BOMB(element))
12545 CheckDynamite(x, y);
12546 else if (element == EL_AMOEBA_GROWING)
12547 AmoebaGrowing(x, y);
12548 else if (element == EL_AMOEBA_SHRINKING)
12549 AmoebaShrinking(x, y);
12551 #if !USE_NEW_AMOEBA_CODE
12552 else if (IS_AMOEBALIVE(element))
12553 AmoebaReproduce(x, y);
12556 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12558 else if (element == EL_EXIT_CLOSED)
12560 else if (element == EL_EM_EXIT_CLOSED)
12562 else if (element == EL_STEEL_EXIT_CLOSED)
12563 CheckExitSteel(x, y);
12564 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12565 CheckExitSteelEM(x, y);
12566 else if (element == EL_SP_EXIT_CLOSED)
12568 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12569 element == EL_EXPANDABLE_STEELWALL_GROWING)
12571 else if (element == EL_EXPANDABLE_WALL ||
12572 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12573 element == EL_EXPANDABLE_WALL_VERTICAL ||
12574 element == EL_EXPANDABLE_WALL_ANY ||
12575 element == EL_BD_EXPANDABLE_WALL ||
12576 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12577 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12578 element == EL_EXPANDABLE_STEELWALL_ANY)
12579 CheckWallGrowing(x, y);
12580 else if (element == EL_FLAMES)
12581 CheckForDragon(x, y);
12582 else if (element == EL_EXPLOSION)
12583 ; // drawing of correct explosion animation is handled separately
12584 else if (element == EL_ELEMENT_SNAPPING ||
12585 element == EL_DIAGONAL_SHRINKING ||
12586 element == EL_DIAGONAL_GROWING)
12588 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]);
12590 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12592 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12593 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12595 if (IS_BELT_ACTIVE(element))
12596 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12598 if (game.magic_wall_active)
12600 int jx = local_player->jx, jy = local_player->jy;
12602 // play the element sound at the position nearest to the player
12603 if ((element == EL_MAGIC_WALL_FULL ||
12604 element == EL_MAGIC_WALL_ACTIVE ||
12605 element == EL_MAGIC_WALL_EMPTYING ||
12606 element == EL_BD_MAGIC_WALL_FULL ||
12607 element == EL_BD_MAGIC_WALL_ACTIVE ||
12608 element == EL_BD_MAGIC_WALL_EMPTYING ||
12609 element == EL_DC_MAGIC_WALL_FULL ||
12610 element == EL_DC_MAGIC_WALL_ACTIVE ||
12611 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12612 ABS(x - jx) + ABS(y - jy) <
12613 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12621 #if USE_NEW_AMOEBA_CODE
12622 // new experimental amoeba growth stuff
12623 if (!(FrameCounter % 8))
12625 static unsigned int random = 1684108901;
12627 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12629 x = RND(lev_fieldx);
12630 y = RND(lev_fieldy);
12631 element = Tile[x][y];
12633 if (!IS_PLAYER(x, y) &&
12634 (element == EL_EMPTY ||
12635 CAN_GROW_INTO(element) ||
12636 element == EL_QUICKSAND_EMPTY ||
12637 element == EL_QUICKSAND_FAST_EMPTY ||
12638 element == EL_ACID_SPLASH_LEFT ||
12639 element == EL_ACID_SPLASH_RIGHT))
12641 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12642 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12643 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12644 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12645 Tile[x][y] = EL_AMOEBA_DROP;
12648 random = random * 129 + 1;
12653 game.explosions_delayed = FALSE;
12655 SCAN_PLAYFIELD(x, y)
12657 element = Tile[x][y];
12659 if (ExplodeField[x][y])
12660 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12661 else if (element == EL_EXPLOSION)
12662 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12664 ExplodeField[x][y] = EX_TYPE_NONE;
12667 game.explosions_delayed = TRUE;
12669 if (game.magic_wall_active)
12671 if (!(game.magic_wall_time_left % 4))
12673 int element = Tile[magic_wall_x][magic_wall_y];
12675 if (element == EL_BD_MAGIC_WALL_FULL ||
12676 element == EL_BD_MAGIC_WALL_ACTIVE ||
12677 element == EL_BD_MAGIC_WALL_EMPTYING)
12678 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12679 else if (element == EL_DC_MAGIC_WALL_FULL ||
12680 element == EL_DC_MAGIC_WALL_ACTIVE ||
12681 element == EL_DC_MAGIC_WALL_EMPTYING)
12682 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12684 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12687 if (game.magic_wall_time_left > 0)
12689 game.magic_wall_time_left--;
12691 if (!game.magic_wall_time_left)
12693 SCAN_PLAYFIELD(x, y)
12695 element = Tile[x][y];
12697 if (element == EL_MAGIC_WALL_ACTIVE ||
12698 element == EL_MAGIC_WALL_FULL)
12700 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12701 TEST_DrawLevelField(x, y);
12703 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12704 element == EL_BD_MAGIC_WALL_FULL)
12706 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12707 TEST_DrawLevelField(x, y);
12709 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12710 element == EL_DC_MAGIC_WALL_FULL)
12712 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12713 TEST_DrawLevelField(x, y);
12717 game.magic_wall_active = FALSE;
12722 if (game.light_time_left > 0)
12724 game.light_time_left--;
12726 if (game.light_time_left == 0)
12727 RedrawAllLightSwitchesAndInvisibleElements();
12730 if (game.timegate_time_left > 0)
12732 game.timegate_time_left--;
12734 if (game.timegate_time_left == 0)
12735 CloseAllOpenTimegates();
12738 if (game.lenses_time_left > 0)
12740 game.lenses_time_left--;
12742 if (game.lenses_time_left == 0)
12743 RedrawAllInvisibleElementsForLenses();
12746 if (game.magnify_time_left > 0)
12748 game.magnify_time_left--;
12750 if (game.magnify_time_left == 0)
12751 RedrawAllInvisibleElementsForMagnifier();
12754 for (i = 0; i < MAX_PLAYERS; i++)
12756 struct PlayerInfo *player = &stored_player[i];
12758 if (SHIELD_ON(player))
12760 if (player->shield_deadly_time_left)
12761 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12762 else if (player->shield_normal_time_left)
12763 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12767 #if USE_DELAYED_GFX_REDRAW
12768 SCAN_PLAYFIELD(x, y)
12770 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12772 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12773 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12775 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12776 DrawLevelField(x, y);
12778 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12779 DrawLevelFieldCrumbled(x, y);
12781 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12782 DrawLevelFieldCrumbledNeighbours(x, y);
12784 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12785 DrawTwinkleOnField(x, y);
12788 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12793 PlayAllPlayersSound();
12795 for (i = 0; i < MAX_PLAYERS; i++)
12797 struct PlayerInfo *player = &stored_player[i];
12799 if (player->show_envelope != 0 && (!player->active ||
12800 player->MovPos == 0))
12802 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12804 player->show_envelope = 0;
12808 // use random number generator in every frame to make it less predictable
12809 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12812 mouse_action_last = mouse_action;
12815 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12817 int min_x = x, min_y = y, max_x = x, max_y = y;
12818 int scr_fieldx = getScreenFieldSizeX();
12819 int scr_fieldy = getScreenFieldSizeY();
12822 for (i = 0; i < MAX_PLAYERS; i++)
12824 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12826 if (!stored_player[i].active || &stored_player[i] == player)
12829 min_x = MIN(min_x, jx);
12830 min_y = MIN(min_y, jy);
12831 max_x = MAX(max_x, jx);
12832 max_y = MAX(max_y, jy);
12835 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12838 static boolean AllPlayersInVisibleScreen(void)
12842 for (i = 0; i < MAX_PLAYERS; i++)
12844 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12846 if (!stored_player[i].active)
12849 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12856 void ScrollLevel(int dx, int dy)
12858 int scroll_offset = 2 * TILEX_VAR;
12861 BlitBitmap(drawto_field, drawto_field,
12862 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12863 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12864 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12865 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12866 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12867 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12871 x = (dx == 1 ? BX1 : BX2);
12872 for (y = BY1; y <= BY2; y++)
12873 DrawScreenField(x, y);
12878 y = (dy == 1 ? BY1 : BY2);
12879 for (x = BX1; x <= BX2; x++)
12880 DrawScreenField(x, y);
12883 redraw_mask |= REDRAW_FIELD;
12886 static boolean canFallDown(struct PlayerInfo *player)
12888 int jx = player->jx, jy = player->jy;
12890 return (IN_LEV_FIELD(jx, jy + 1) &&
12891 (IS_FREE(jx, jy + 1) ||
12892 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12893 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12894 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12897 static boolean canPassField(int x, int y, int move_dir)
12899 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12900 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12901 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12902 int nextx = x + dx;
12903 int nexty = y + dy;
12904 int element = Tile[x][y];
12906 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12907 !CAN_MOVE(element) &&
12908 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12909 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12910 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12913 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12915 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12916 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12917 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12921 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12922 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12923 (IS_DIGGABLE(Tile[newx][newy]) ||
12924 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12925 canPassField(newx, newy, move_dir)));
12928 static void CheckGravityMovement(struct PlayerInfo *player)
12930 if (player->gravity && !player->programmed_action)
12932 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12933 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12934 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12935 int jx = player->jx, jy = player->jy;
12936 boolean player_is_moving_to_valid_field =
12937 (!player_is_snapping &&
12938 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12939 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12940 boolean player_can_fall_down = canFallDown(player);
12942 if (player_can_fall_down &&
12943 !player_is_moving_to_valid_field)
12944 player->programmed_action = MV_DOWN;
12948 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12950 return CheckGravityMovement(player);
12952 if (player->gravity && !player->programmed_action)
12954 int jx = player->jx, jy = player->jy;
12955 boolean field_under_player_is_free =
12956 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12957 boolean player_is_standing_on_valid_field =
12958 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12959 (IS_WALKABLE(Tile[jx][jy]) &&
12960 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12962 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12963 player->programmed_action = MV_DOWN;
12968 MovePlayerOneStep()
12969 -----------------------------------------------------------------------------
12970 dx, dy: direction (non-diagonal) to try to move the player to
12971 real_dx, real_dy: direction as read from input device (can be diagonal)
12974 boolean MovePlayerOneStep(struct PlayerInfo *player,
12975 int dx, int dy, int real_dx, int real_dy)
12977 int jx = player->jx, jy = player->jy;
12978 int new_jx = jx + dx, new_jy = jy + dy;
12980 boolean player_can_move = !player->cannot_move;
12982 if (!player->active || (!dx && !dy))
12983 return MP_NO_ACTION;
12985 player->MovDir = (dx < 0 ? MV_LEFT :
12986 dx > 0 ? MV_RIGHT :
12988 dy > 0 ? MV_DOWN : MV_NONE);
12990 if (!IN_LEV_FIELD(new_jx, new_jy))
12991 return MP_NO_ACTION;
12993 if (!player_can_move)
12995 if (player->MovPos == 0)
12997 player->is_moving = FALSE;
12998 player->is_digging = FALSE;
12999 player->is_collecting = FALSE;
13000 player->is_snapping = FALSE;
13001 player->is_pushing = FALSE;
13005 if (!network.enabled && game.centered_player_nr == -1 &&
13006 !AllPlayersInSight(player, new_jx, new_jy))
13007 return MP_NO_ACTION;
13009 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG);
13010 if (can_move != MP_MOVING)
13013 // check if DigField() has caused relocation of the player
13014 if (player->jx != jx || player->jy != jy)
13015 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
13017 StorePlayer[jx][jy] = 0;
13018 player->last_jx = jx;
13019 player->last_jy = jy;
13020 player->jx = new_jx;
13021 player->jy = new_jy;
13022 StorePlayer[new_jx][new_jy] = player->element_nr;
13024 if (player->move_delay_value_next != -1)
13026 player->move_delay_value = player->move_delay_value_next;
13027 player->move_delay_value_next = -1;
13031 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
13033 player->step_counter++;
13035 PlayerVisit[jx][jy] = FrameCounter;
13037 player->is_moving = TRUE;
13040 // should better be called in MovePlayer(), but this breaks some tapes
13041 ScrollPlayer(player, SCROLL_INIT);
13047 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
13049 int jx = player->jx, jy = player->jy;
13050 int old_jx = jx, old_jy = jy;
13051 int moved = MP_NO_ACTION;
13053 if (!player->active)
13058 if (player->MovPos == 0)
13060 player->is_moving = FALSE;
13061 player->is_digging = FALSE;
13062 player->is_collecting = FALSE;
13063 player->is_snapping = FALSE;
13064 player->is_pushing = FALSE;
13070 if (player->move_delay > 0)
13073 player->move_delay = -1; // set to "uninitialized" value
13075 // store if player is automatically moved to next field
13076 player->is_auto_moving = (player->programmed_action != MV_NONE);
13078 // remove the last programmed player action
13079 player->programmed_action = 0;
13081 if (player->MovPos)
13083 // should only happen if pre-1.2 tape recordings are played
13084 // this is only for backward compatibility
13086 int original_move_delay_value = player->move_delay_value;
13089 Debug("game:playing:MovePlayer",
13090 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13094 // scroll remaining steps with finest movement resolution
13095 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13097 while (player->MovPos)
13099 ScrollPlayer(player, SCROLL_GO_ON);
13100 ScrollScreen(NULL, SCROLL_GO_ON);
13102 AdvanceFrameAndPlayerCounters(player->index_nr);
13105 BackToFront_WithFrameDelay(0);
13108 player->move_delay_value = original_move_delay_value;
13111 player->is_active = FALSE;
13113 if (player->last_move_dir & MV_HORIZONTAL)
13115 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13116 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13120 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13121 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13124 if (!moved && !player->is_active)
13126 player->is_moving = FALSE;
13127 player->is_digging = FALSE;
13128 player->is_collecting = FALSE;
13129 player->is_snapping = FALSE;
13130 player->is_pushing = FALSE;
13136 if (moved & MP_MOVING && !ScreenMovPos &&
13137 (player->index_nr == game.centered_player_nr ||
13138 game.centered_player_nr == -1))
13140 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13142 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13144 // actual player has left the screen -- scroll in that direction
13145 if (jx != old_jx) // player has moved horizontally
13146 scroll_x += (jx - old_jx);
13147 else // player has moved vertically
13148 scroll_y += (jy - old_jy);
13152 int offset_raw = game.scroll_delay_value;
13154 if (jx != old_jx) // player has moved horizontally
13156 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13157 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13158 int new_scroll_x = jx - MIDPOSX + offset_x;
13160 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13161 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13162 scroll_x = new_scroll_x;
13164 // don't scroll over playfield boundaries
13165 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13167 // don't scroll more than one field at a time
13168 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13170 // don't scroll against the player's moving direction
13171 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13172 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13173 scroll_x = old_scroll_x;
13175 else // player has moved vertically
13177 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13178 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13179 int new_scroll_y = jy - MIDPOSY + offset_y;
13181 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13182 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13183 scroll_y = new_scroll_y;
13185 // don't scroll over playfield boundaries
13186 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13188 // don't scroll more than one field at a time
13189 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13191 // don't scroll against the player's moving direction
13192 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13193 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13194 scroll_y = old_scroll_y;
13198 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13200 if (!network.enabled && game.centered_player_nr == -1 &&
13201 !AllPlayersInVisibleScreen())
13203 scroll_x = old_scroll_x;
13204 scroll_y = old_scroll_y;
13208 ScrollScreen(player, SCROLL_INIT);
13209 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13214 player->StepFrame = 0;
13216 if (moved & MP_MOVING)
13218 if (old_jx != jx && old_jy == jy)
13219 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13220 else if (old_jx == jx && old_jy != jy)
13221 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13223 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13225 player->last_move_dir = player->MovDir;
13226 player->is_moving = TRUE;
13227 player->is_snapping = FALSE;
13228 player->is_switching = FALSE;
13229 player->is_dropping = FALSE;
13230 player->is_dropping_pressed = FALSE;
13231 player->drop_pressed_delay = 0;
13234 // should better be called here than above, but this breaks some tapes
13235 ScrollPlayer(player, SCROLL_INIT);
13240 CheckGravityMovementWhenNotMoving(player);
13242 player->is_moving = FALSE;
13244 /* at this point, the player is allowed to move, but cannot move right now
13245 (e.g. because of something blocking the way) -- ensure that the player
13246 is also allowed to move in the next frame (in old versions before 3.1.1,
13247 the player was forced to wait again for eight frames before next try) */
13249 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13250 player->move_delay = 0; // allow direct movement in the next frame
13253 if (player->move_delay == -1) // not yet initialized by DigField()
13254 player->move_delay = player->move_delay_value;
13256 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13258 TestIfPlayerTouchesBadThing(jx, jy);
13259 TestIfPlayerTouchesCustomElement(jx, jy);
13262 if (!player->active)
13263 RemovePlayer(player);
13268 void ScrollPlayer(struct PlayerInfo *player, int mode)
13270 int jx = player->jx, jy = player->jy;
13271 int last_jx = player->last_jx, last_jy = player->last_jy;
13272 int move_stepsize = TILEX / player->move_delay_value;
13274 if (!player->active)
13277 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13280 if (mode == SCROLL_INIT)
13282 player->actual_frame_counter.count = FrameCounter;
13283 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13285 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13286 Tile[last_jx][last_jy] == EL_EMPTY)
13288 int last_field_block_delay = 0; // start with no blocking at all
13289 int block_delay_adjustment = player->block_delay_adjustment;
13291 // if player blocks last field, add delay for exactly one move
13292 if (player->block_last_field)
13294 last_field_block_delay += player->move_delay_value;
13296 // when blocking enabled, prevent moving up despite gravity
13297 if (player->gravity && player->MovDir == MV_UP)
13298 block_delay_adjustment = -1;
13301 // add block delay adjustment (also possible when not blocking)
13302 last_field_block_delay += block_delay_adjustment;
13304 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13305 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13308 if (player->MovPos != 0) // player has not yet reached destination
13311 else if (!FrameReached(&player->actual_frame_counter))
13314 if (player->MovPos != 0)
13316 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13317 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13319 // before DrawPlayer() to draw correct player graphic for this case
13320 if (player->MovPos == 0)
13321 CheckGravityMovement(player);
13324 if (player->MovPos == 0) // player reached destination field
13326 if (player->move_delay_reset_counter > 0)
13328 player->move_delay_reset_counter--;
13330 if (player->move_delay_reset_counter == 0)
13332 // continue with normal speed after quickly moving through gate
13333 HALVE_PLAYER_SPEED(player);
13335 // be able to make the next move without delay
13336 player->move_delay = 0;
13340 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13341 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13342 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13343 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13344 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13345 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13346 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13347 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13349 ExitPlayer(player);
13351 if (game.players_still_needed == 0 &&
13352 (game.friends_still_needed == 0 ||
13353 IS_SP_ELEMENT(Tile[jx][jy])))
13357 player->last_jx = jx;
13358 player->last_jy = jy;
13360 // this breaks one level: "machine", level 000
13362 int move_direction = player->MovDir;
13363 int enter_side = MV_DIR_OPPOSITE(move_direction);
13364 int leave_side = move_direction;
13365 int old_jx = last_jx;
13366 int old_jy = last_jy;
13367 int old_element = Tile[old_jx][old_jy];
13368 int new_element = Tile[jx][jy];
13370 if (IS_CUSTOM_ELEMENT(old_element))
13371 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13373 player->index_bit, leave_side);
13375 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13376 CE_PLAYER_LEAVES_X,
13377 player->index_bit, leave_side);
13379 // needed because pushed element has not yet reached its destination,
13380 // so it would trigger a change event at its previous field location
13381 if (!player->is_pushing)
13383 if (IS_CUSTOM_ELEMENT(new_element))
13384 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13385 player->index_bit, enter_side);
13387 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13388 CE_PLAYER_ENTERS_X,
13389 player->index_bit, enter_side);
13392 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13393 CE_MOVE_OF_X, move_direction);
13396 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13398 TestIfPlayerTouchesBadThing(jx, jy);
13399 TestIfPlayerTouchesCustomElement(jx, jy);
13401 // needed because pushed element has not yet reached its destination,
13402 // so it would trigger a change event at its previous field location
13403 if (!player->is_pushing)
13404 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13406 if (level.finish_dig_collect &&
13407 (player->is_digging || player->is_collecting))
13409 int last_element = player->last_removed_element;
13410 int move_direction = player->MovDir;
13411 int enter_side = MV_DIR_OPPOSITE(move_direction);
13412 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13413 CE_PLAYER_COLLECTS_X);
13415 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13416 player->index_bit, enter_side);
13418 player->last_removed_element = EL_UNDEFINED;
13421 if (!player->active)
13422 RemovePlayer(player);
13425 if (level.use_step_counter)
13426 CheckLevelTime_StepCounter();
13428 if (tape.single_step && tape.recording && !tape.pausing &&
13429 !player->programmed_action)
13430 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13432 if (!player->programmed_action)
13433 CheckSaveEngineSnapshot(player);
13437 void ScrollScreen(struct PlayerInfo *player, int mode)
13439 static DelayCounter screen_frame_counter = { 0 };
13441 if (mode == SCROLL_INIT)
13443 // set scrolling step size according to actual player's moving speed
13444 ScrollStepSize = TILEX / player->move_delay_value;
13446 screen_frame_counter.count = FrameCounter;
13447 screen_frame_counter.value = 1;
13449 ScreenMovDir = player->MovDir;
13450 ScreenMovPos = player->MovPos;
13451 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13454 else if (!FrameReached(&screen_frame_counter))
13459 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13460 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13461 redraw_mask |= REDRAW_FIELD;
13464 ScreenMovDir = MV_NONE;
13467 void CheckNextToConditions(int x, int y)
13469 int element = Tile[x][y];
13471 if (IS_PLAYER(x, y))
13472 TestIfPlayerNextToCustomElement(x, y);
13474 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13475 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13476 TestIfElementNextToCustomElement(x, y);
13479 void TestIfPlayerNextToCustomElement(int x, int y)
13481 struct XY *xy = xy_topdown;
13482 static int trigger_sides[4][2] =
13484 // center side border side
13485 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13486 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13487 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13488 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13492 if (!IS_PLAYER(x, y))
13495 struct PlayerInfo *player = PLAYERINFO(x, y);
13497 if (player->is_moving)
13500 for (i = 0; i < NUM_DIRECTIONS; i++)
13502 int xx = x + xy[i].x;
13503 int yy = y + xy[i].y;
13504 int border_side = trigger_sides[i][1];
13505 int border_element;
13507 if (!IN_LEV_FIELD(xx, yy))
13510 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13511 continue; // center and border element not connected
13513 border_element = Tile[xx][yy];
13515 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13516 player->index_bit, border_side);
13517 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13518 CE_PLAYER_NEXT_TO_X,
13519 player->index_bit, border_side);
13521 /* use player element that is initially defined in the level playfield,
13522 not the player element that corresponds to the runtime player number
13523 (example: a level that contains EL_PLAYER_3 as the only player would
13524 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13526 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13527 CE_NEXT_TO_X, border_side);
13531 void TestIfPlayerTouchesCustomElement(int x, int y)
13533 struct XY *xy = xy_topdown;
13534 static int trigger_sides[4][2] =
13536 // center side border side
13537 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13538 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13539 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13540 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13542 static int touch_dir[4] =
13544 MV_LEFT | MV_RIGHT,
13549 int center_element = Tile[x][y]; // should always be non-moving!
13552 for (i = 0; i < NUM_DIRECTIONS; i++)
13554 int xx = x + xy[i].x;
13555 int yy = y + xy[i].y;
13556 int center_side = trigger_sides[i][0];
13557 int border_side = trigger_sides[i][1];
13558 int border_element;
13560 if (!IN_LEV_FIELD(xx, yy))
13563 if (IS_PLAYER(x, y)) // player found at center element
13565 struct PlayerInfo *player = PLAYERINFO(x, y);
13567 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13568 border_element = Tile[xx][yy]; // may be moving!
13569 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13570 border_element = Tile[xx][yy];
13571 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13572 border_element = MovingOrBlocked2Element(xx, yy);
13574 continue; // center and border element do not touch
13576 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13577 player->index_bit, border_side);
13578 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13579 CE_PLAYER_TOUCHES_X,
13580 player->index_bit, border_side);
13583 /* use player element that is initially defined in the level playfield,
13584 not the player element that corresponds to the runtime player number
13585 (example: a level that contains EL_PLAYER_3 as the only player would
13586 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13587 int player_element = PLAYERINFO(x, y)->initial_element;
13589 // as element "X" is the player here, check opposite (center) side
13590 CheckElementChangeBySide(xx, yy, border_element, player_element,
13591 CE_TOUCHING_X, center_side);
13594 else if (IS_PLAYER(xx, yy)) // player found at border element
13596 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13598 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13600 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13601 continue; // center and border element do not touch
13604 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13605 player->index_bit, center_side);
13606 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13607 CE_PLAYER_TOUCHES_X,
13608 player->index_bit, center_side);
13611 /* use player element that is initially defined in the level playfield,
13612 not the player element that corresponds to the runtime player number
13613 (example: a level that contains EL_PLAYER_3 as the only player would
13614 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13615 int player_element = PLAYERINFO(xx, yy)->initial_element;
13617 // as element "X" is the player here, check opposite (border) side
13618 CheckElementChangeBySide(x, y, center_element, player_element,
13619 CE_TOUCHING_X, border_side);
13627 void TestIfElementNextToCustomElement(int x, int y)
13629 struct XY *xy = xy_topdown;
13630 static int trigger_sides[4][2] =
13632 // center side border side
13633 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13634 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13635 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13636 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13638 int center_element = Tile[x][y]; // should always be non-moving!
13641 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13644 for (i = 0; i < NUM_DIRECTIONS; i++)
13646 int xx = x + xy[i].x;
13647 int yy = y + xy[i].y;
13648 int border_side = trigger_sides[i][1];
13649 int border_element;
13651 if (!IN_LEV_FIELD(xx, yy))
13654 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13655 continue; // center and border element not connected
13657 border_element = Tile[xx][yy];
13659 // check for change of center element (but change it only once)
13660 if (CheckElementChangeBySide(x, y, center_element, border_element,
13661 CE_NEXT_TO_X, border_side))
13666 void TestIfElementTouchesCustomElement(int x, int y)
13668 struct XY *xy = xy_topdown;
13669 static int trigger_sides[4][2] =
13671 // center side border side
13672 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13673 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13674 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13675 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13677 static int touch_dir[4] =
13679 MV_LEFT | MV_RIGHT,
13684 boolean change_center_element = FALSE;
13685 int center_element = Tile[x][y]; // should always be non-moving!
13686 int border_element_old[NUM_DIRECTIONS];
13689 for (i = 0; i < NUM_DIRECTIONS; i++)
13691 int xx = x + xy[i].x;
13692 int yy = y + xy[i].y;
13693 int border_element;
13695 border_element_old[i] = -1;
13697 if (!IN_LEV_FIELD(xx, yy))
13700 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13701 border_element = Tile[xx][yy]; // may be moving!
13702 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13703 border_element = Tile[xx][yy];
13704 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13705 border_element = MovingOrBlocked2Element(xx, yy);
13707 continue; // center and border element do not touch
13709 border_element_old[i] = border_element;
13712 for (i = 0; i < NUM_DIRECTIONS; i++)
13714 int xx = x + xy[i].x;
13715 int yy = y + xy[i].y;
13716 int center_side = trigger_sides[i][0];
13717 int border_element = border_element_old[i];
13719 if (border_element == -1)
13722 // check for change of border element
13723 CheckElementChangeBySide(xx, yy, border_element, center_element,
13724 CE_TOUCHING_X, center_side);
13726 // (center element cannot be player, so we don't have to check this here)
13729 for (i = 0; i < NUM_DIRECTIONS; i++)
13731 int xx = x + xy[i].x;
13732 int yy = y + xy[i].y;
13733 int border_side = trigger_sides[i][1];
13734 int border_element = border_element_old[i];
13736 if (border_element == -1)
13739 // check for change of center element (but change it only once)
13740 if (!change_center_element)
13741 change_center_element =
13742 CheckElementChangeBySide(x, y, center_element, border_element,
13743 CE_TOUCHING_X, border_side);
13745 if (IS_PLAYER(xx, yy))
13747 /* use player element that is initially defined in the level playfield,
13748 not the player element that corresponds to the runtime player number
13749 (example: a level that contains EL_PLAYER_3 as the only player would
13750 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13751 int player_element = PLAYERINFO(xx, yy)->initial_element;
13753 // as element "X" is the player here, check opposite (border) side
13754 CheckElementChangeBySide(x, y, center_element, player_element,
13755 CE_TOUCHING_X, border_side);
13760 void TestIfElementHitsCustomElement(int x, int y, int direction)
13762 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13763 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13764 int hitx = x + dx, hity = y + dy;
13765 int hitting_element = Tile[x][y];
13766 int touched_element;
13768 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13771 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13772 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13774 if (IN_LEV_FIELD(hitx, hity))
13776 int opposite_direction = MV_DIR_OPPOSITE(direction);
13777 int hitting_side = direction;
13778 int touched_side = opposite_direction;
13779 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13780 MovDir[hitx][hity] != direction ||
13781 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13787 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13788 CE_HITTING_X, touched_side);
13790 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13791 CE_HIT_BY_X, hitting_side);
13793 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13794 CE_HIT_BY_SOMETHING, opposite_direction);
13796 if (IS_PLAYER(hitx, hity))
13798 /* use player element that is initially defined in the level playfield,
13799 not the player element that corresponds to the runtime player number
13800 (example: a level that contains EL_PLAYER_3 as the only player would
13801 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13802 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13804 CheckElementChangeBySide(x, y, hitting_element, player_element,
13805 CE_HITTING_X, touched_side);
13810 // "hitting something" is also true when hitting the playfield border
13811 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13812 CE_HITTING_SOMETHING, direction);
13815 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13817 int i, kill_x = -1, kill_y = -1;
13819 int bad_element = -1;
13820 struct XY *test_xy = xy_topdown;
13821 static int test_dir[4] =
13829 for (i = 0; i < NUM_DIRECTIONS; i++)
13831 int test_x, test_y, test_move_dir, test_element;
13833 test_x = good_x + test_xy[i].x;
13834 test_y = good_y + test_xy[i].y;
13836 if (!IN_LEV_FIELD(test_x, test_y))
13840 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13842 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13844 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13845 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13847 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13848 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13852 bad_element = test_element;
13858 if (kill_x != -1 || kill_y != -1)
13860 if (IS_PLAYER(good_x, good_y))
13862 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13864 if (player->shield_deadly_time_left > 0 &&
13865 !IS_INDESTRUCTIBLE(bad_element))
13866 Bang(kill_x, kill_y);
13867 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13868 KillPlayer(player);
13871 Bang(good_x, good_y);
13875 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13877 int i, kill_x = -1, kill_y = -1;
13878 int bad_element = Tile[bad_x][bad_y];
13879 struct XY *test_xy = xy_topdown;
13880 static int touch_dir[4] =
13882 MV_LEFT | MV_RIGHT,
13887 static int test_dir[4] =
13895 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13898 for (i = 0; i < NUM_DIRECTIONS; i++)
13900 int test_x, test_y, test_move_dir, test_element;
13902 test_x = bad_x + test_xy[i].x;
13903 test_y = bad_y + test_xy[i].y;
13905 if (!IN_LEV_FIELD(test_x, test_y))
13909 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13911 test_element = Tile[test_x][test_y];
13913 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13914 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13916 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13917 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13919 // good thing is player or penguin that does not move away
13920 if (IS_PLAYER(test_x, test_y))
13922 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13924 if (bad_element == EL_ROBOT && player->is_moving)
13925 continue; // robot does not kill player if he is moving
13927 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13929 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13930 continue; // center and border element do not touch
13938 else if (test_element == EL_PENGUIN)
13948 if (kill_x != -1 || kill_y != -1)
13950 if (IS_PLAYER(kill_x, kill_y))
13952 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13954 if (player->shield_deadly_time_left > 0 &&
13955 !IS_INDESTRUCTIBLE(bad_element))
13956 Bang(bad_x, bad_y);
13957 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13958 KillPlayer(player);
13961 Bang(kill_x, kill_y);
13965 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13967 int bad_element = Tile[bad_x][bad_y];
13968 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13969 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13970 int test_x = bad_x + dx, test_y = bad_y + dy;
13971 int test_move_dir, test_element;
13972 int kill_x = -1, kill_y = -1;
13974 if (!IN_LEV_FIELD(test_x, test_y))
13978 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13980 test_element = Tile[test_x][test_y];
13982 if (test_move_dir != bad_move_dir)
13984 // good thing can be player or penguin that does not move away
13985 if (IS_PLAYER(test_x, test_y))
13987 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13989 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13990 player as being hit when he is moving towards the bad thing, because
13991 the "get hit by" condition would be lost after the player stops) */
13992 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13993 return; // player moves away from bad thing
13998 else if (test_element == EL_PENGUIN)
14005 if (kill_x != -1 || kill_y != -1)
14007 if (IS_PLAYER(kill_x, kill_y))
14009 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14011 if (player->shield_deadly_time_left > 0 &&
14012 !IS_INDESTRUCTIBLE(bad_element))
14013 Bang(bad_x, bad_y);
14014 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14015 KillPlayer(player);
14018 Bang(kill_x, kill_y);
14022 void TestIfPlayerTouchesBadThing(int x, int y)
14024 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14027 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
14029 TestIfGoodThingHitsBadThing(x, y, move_dir);
14032 void TestIfBadThingTouchesPlayer(int x, int y)
14034 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14037 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
14039 TestIfBadThingHitsGoodThing(x, y, move_dir);
14042 void TestIfFriendTouchesBadThing(int x, int y)
14044 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14047 void TestIfBadThingTouchesFriend(int x, int y)
14049 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14052 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
14054 int i, kill_x = bad_x, kill_y = bad_y;
14055 struct XY *xy = xy_topdown;
14057 for (i = 0; i < NUM_DIRECTIONS; i++)
14061 x = bad_x + xy[i].x;
14062 y = bad_y + xy[i].y;
14063 if (!IN_LEV_FIELD(x, y))
14066 element = Tile[x][y];
14067 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14068 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14076 if (kill_x != bad_x || kill_y != bad_y)
14077 Bang(bad_x, bad_y);
14080 void KillPlayer(struct PlayerInfo *player)
14082 int jx = player->jx, jy = player->jy;
14084 if (!player->active)
14088 Debug("game:playing:KillPlayer",
14089 "0: killed == %d, active == %d, reanimated == %d",
14090 player->killed, player->active, player->reanimated);
14093 /* the following code was introduced to prevent an infinite loop when calling
14095 -> CheckTriggeredElementChangeExt()
14096 -> ExecuteCustomElementAction()
14098 -> (infinitely repeating the above sequence of function calls)
14099 which occurs when killing the player while having a CE with the setting
14100 "kill player X when explosion of <player X>"; the solution using a new
14101 field "player->killed" was chosen for backwards compatibility, although
14102 clever use of the fields "player->active" etc. would probably also work */
14104 if (player->killed)
14108 player->killed = TRUE;
14110 // remove accessible field at the player's position
14111 RemoveField(jx, jy);
14113 // deactivate shield (else Bang()/Explode() would not work right)
14114 player->shield_normal_time_left = 0;
14115 player->shield_deadly_time_left = 0;
14118 Debug("game:playing:KillPlayer",
14119 "1: killed == %d, active == %d, reanimated == %d",
14120 player->killed, player->active, player->reanimated);
14126 Debug("game:playing:KillPlayer",
14127 "2: killed == %d, active == %d, reanimated == %d",
14128 player->killed, player->active, player->reanimated);
14131 if (player->reanimated) // killed player may have been reanimated
14132 player->killed = player->reanimated = FALSE;
14134 BuryPlayer(player);
14137 static void KillPlayerUnlessEnemyProtected(int x, int y)
14139 if (!PLAYER_ENEMY_PROTECTED(x, y))
14140 KillPlayer(PLAYERINFO(x, y));
14143 static void KillPlayerUnlessExplosionProtected(int x, int y)
14145 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14146 KillPlayer(PLAYERINFO(x, y));
14149 void BuryPlayer(struct PlayerInfo *player)
14151 int jx = player->jx, jy = player->jy;
14153 if (!player->active)
14156 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14158 RemovePlayer(player);
14160 player->buried = TRUE;
14162 if (game.all_players_gone)
14163 game.GameOver = TRUE;
14166 void RemovePlayer(struct PlayerInfo *player)
14168 int jx = player->jx, jy = player->jy;
14169 int i, found = FALSE;
14171 player->present = FALSE;
14172 player->active = FALSE;
14174 // required for some CE actions (even if the player is not active anymore)
14175 player->MovPos = 0;
14177 if (!ExplodeField[jx][jy])
14178 StorePlayer[jx][jy] = 0;
14180 if (player->is_moving)
14181 TEST_DrawLevelField(player->last_jx, player->last_jy);
14183 for (i = 0; i < MAX_PLAYERS; i++)
14184 if (stored_player[i].active)
14189 game.all_players_gone = TRUE;
14190 game.GameOver = TRUE;
14193 game.exit_x = game.robot_wheel_x = jx;
14194 game.exit_y = game.robot_wheel_y = jy;
14197 void ExitPlayer(struct PlayerInfo *player)
14199 DrawPlayer(player); // needed here only to cleanup last field
14200 RemovePlayer(player);
14202 if (game.players_still_needed > 0)
14203 game.players_still_needed--;
14206 static void SetFieldForSnapping(int x, int y, int element, int direction,
14207 int player_index_bit)
14209 struct ElementInfo *ei = &element_info[element];
14210 int direction_bit = MV_DIR_TO_BIT(direction);
14211 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14212 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14213 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14215 Tile[x][y] = EL_ELEMENT_SNAPPING;
14216 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14217 MovDir[x][y] = direction;
14218 Store[x][y] = element;
14219 Store2[x][y] = player_index_bit;
14221 ResetGfxAnimation(x, y);
14223 GfxElement[x][y] = element;
14224 GfxAction[x][y] = action;
14225 GfxDir[x][y] = direction;
14226 GfxFrame[x][y] = -1;
14229 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14230 int player_index_bit)
14232 TestIfElementTouchesCustomElement(x, y); // for empty space
14234 if (level.finish_dig_collect)
14236 int dig_side = MV_DIR_OPPOSITE(direction);
14237 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14238 CE_PLAYER_COLLECTS_X);
14240 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14241 player_index_bit, dig_side);
14242 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14243 player_index_bit, dig_side);
14248 =============================================================================
14249 checkDiagonalPushing()
14250 -----------------------------------------------------------------------------
14251 check if diagonal input device direction results in pushing of object
14252 (by checking if the alternative direction is walkable, diggable, ...)
14253 =============================================================================
14256 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14257 int x, int y, int real_dx, int real_dy)
14259 int jx, jy, dx, dy, xx, yy;
14261 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14264 // diagonal direction: check alternative direction
14269 xx = jx + (dx == 0 ? real_dx : 0);
14270 yy = jy + (dy == 0 ? real_dy : 0);
14272 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14276 =============================================================================
14278 -----------------------------------------------------------------------------
14279 x, y: field next to player (non-diagonal) to try to dig to
14280 real_dx, real_dy: direction as read from input device (can be diagonal)
14281 =============================================================================
14284 static int DigField(struct PlayerInfo *player,
14285 int oldx, int oldy, int x, int y,
14286 int real_dx, int real_dy, int mode)
14288 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14289 boolean player_was_pushing = player->is_pushing;
14290 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14291 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14292 int jx = oldx, jy = oldy;
14293 int dx = x - jx, dy = y - jy;
14294 int nextx = x + dx, nexty = y + dy;
14295 int move_direction = (dx == -1 ? MV_LEFT :
14296 dx == +1 ? MV_RIGHT :
14298 dy == +1 ? MV_DOWN : MV_NONE);
14299 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14300 int dig_side = MV_DIR_OPPOSITE(move_direction);
14301 int old_element = Tile[jx][jy];
14302 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14305 if (is_player) // function can also be called by EL_PENGUIN
14307 if (player->MovPos == 0)
14309 player->is_digging = FALSE;
14310 player->is_collecting = FALSE;
14313 if (player->MovPos == 0) // last pushing move finished
14314 player->is_pushing = FALSE;
14316 if (mode == DF_NO_PUSH) // player just stopped pushing
14318 player->is_switching = FALSE;
14319 player->push_delay = -1;
14321 return MP_NO_ACTION;
14324 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14325 old_element = Back[jx][jy];
14327 // in case of element dropped at player position, check background
14328 else if (Back[jx][jy] != EL_EMPTY &&
14329 game.engine_version >= VERSION_IDENT(2,2,0,0))
14330 old_element = Back[jx][jy];
14332 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14333 return MP_NO_ACTION; // field has no opening in this direction
14335 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction))
14336 return MP_NO_ACTION; // field has no opening in this direction
14338 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14342 Tile[jx][jy] = player->artwork_element;
14343 InitMovingField(jx, jy, MV_DOWN);
14344 Store[jx][jy] = EL_ACID;
14345 ContinueMoving(jx, jy);
14346 BuryPlayer(player);
14348 return MP_DONT_RUN_INTO;
14351 if (player_can_move && DONT_RUN_INTO(element))
14353 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14355 return MP_DONT_RUN_INTO;
14358 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14359 return MP_NO_ACTION;
14361 collect_count = element_info[element].collect_count_initial;
14363 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14364 return MP_NO_ACTION;
14366 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14367 player_can_move = player_can_move_or_snap;
14369 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14370 game.engine_version >= VERSION_IDENT(2,2,0,0))
14372 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14373 player->index_bit, dig_side);
14374 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14375 player->index_bit, dig_side);
14377 if (element == EL_DC_LANDMINE)
14380 if (Tile[x][y] != element) // field changed by snapping
14383 return MP_NO_ACTION;
14386 if (player->gravity && is_player && !player->is_auto_moving &&
14387 canFallDown(player) && move_direction != MV_DOWN &&
14388 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14389 return MP_NO_ACTION; // player cannot walk here due to gravity
14391 if (player_can_move &&
14392 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14394 int sound_element = SND_ELEMENT(element);
14395 int sound_action = ACTION_WALKING;
14397 if (IS_RND_GATE(element))
14399 if (!player->key[RND_GATE_NR(element)])
14400 return MP_NO_ACTION;
14402 else if (IS_RND_GATE_GRAY(element))
14404 if (!player->key[RND_GATE_GRAY_NR(element)])
14405 return MP_NO_ACTION;
14407 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14409 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14410 return MP_NO_ACTION;
14412 else if (element == EL_EXIT_OPEN ||
14413 element == EL_EM_EXIT_OPEN ||
14414 element == EL_EM_EXIT_OPENING ||
14415 element == EL_STEEL_EXIT_OPEN ||
14416 element == EL_EM_STEEL_EXIT_OPEN ||
14417 element == EL_EM_STEEL_EXIT_OPENING ||
14418 element == EL_SP_EXIT_OPEN ||
14419 element == EL_SP_EXIT_OPENING)
14421 sound_action = ACTION_PASSING; // player is passing exit
14423 else if (element == EL_EMPTY)
14425 sound_action = ACTION_MOVING; // nothing to walk on
14428 // play sound from background or player, whatever is available
14429 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14430 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14432 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14434 else if (player_can_move &&
14435 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14437 if (!ACCESS_FROM(element, opposite_direction))
14438 return MP_NO_ACTION; // field not accessible from this direction
14440 if (CAN_MOVE(element)) // only fixed elements can be passed!
14441 return MP_NO_ACTION;
14443 if (IS_EM_GATE(element))
14445 if (!player->key[EM_GATE_NR(element)])
14446 return MP_NO_ACTION;
14448 else if (IS_EM_GATE_GRAY(element))
14450 if (!player->key[EM_GATE_GRAY_NR(element)])
14451 return MP_NO_ACTION;
14453 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14455 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14456 return MP_NO_ACTION;
14458 else if (IS_EMC_GATE(element))
14460 if (!player->key[EMC_GATE_NR(element)])
14461 return MP_NO_ACTION;
14463 else if (IS_EMC_GATE_GRAY(element))
14465 if (!player->key[EMC_GATE_GRAY_NR(element)])
14466 return MP_NO_ACTION;
14468 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14470 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14471 return MP_NO_ACTION;
14473 else if (element == EL_DC_GATE_WHITE ||
14474 element == EL_DC_GATE_WHITE_GRAY ||
14475 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14477 if (player->num_white_keys == 0)
14478 return MP_NO_ACTION;
14480 player->num_white_keys--;
14482 else if (IS_SP_PORT(element))
14484 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14485 element == EL_SP_GRAVITY_PORT_RIGHT ||
14486 element == EL_SP_GRAVITY_PORT_UP ||
14487 element == EL_SP_GRAVITY_PORT_DOWN)
14488 player->gravity = !player->gravity;
14489 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14490 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14491 element == EL_SP_GRAVITY_ON_PORT_UP ||
14492 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14493 player->gravity = TRUE;
14494 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14495 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14496 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14497 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14498 player->gravity = FALSE;
14501 // automatically move to the next field with double speed
14502 player->programmed_action = move_direction;
14504 if (player->move_delay_reset_counter == 0)
14506 player->move_delay_reset_counter = 2; // two double speed steps
14508 DOUBLE_PLAYER_SPEED(player);
14511 PlayLevelSoundAction(x, y, ACTION_PASSING);
14513 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14517 if (mode != DF_SNAP)
14519 GfxElement[x][y] = GFX_ELEMENT(element);
14520 player->is_digging = TRUE;
14523 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14525 // use old behaviour for old levels (digging)
14526 if (!level.finish_dig_collect)
14528 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14529 player->index_bit, dig_side);
14531 // if digging triggered player relocation, finish digging tile
14532 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14533 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14536 if (mode == DF_SNAP)
14538 if (level.block_snap_field)
14539 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14541 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14543 // use old behaviour for old levels (snapping)
14544 if (!level.finish_dig_collect)
14545 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14546 player->index_bit, dig_side);
14549 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14553 if (is_player && mode != DF_SNAP)
14555 GfxElement[x][y] = element;
14556 player->is_collecting = TRUE;
14559 if (element == EL_SPEED_PILL)
14561 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14563 else if (element == EL_EXTRA_TIME && level.time > 0)
14565 TimeLeft += level.extra_time;
14567 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14569 DisplayGameControlValues();
14571 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14573 int shield_time = (element == EL_SHIELD_DEADLY ?
14574 level.shield_deadly_time :
14575 level.shield_normal_time);
14577 player->shield_normal_time_left += shield_time;
14578 if (element == EL_SHIELD_DEADLY)
14579 player->shield_deadly_time_left += shield_time;
14581 else if (element == EL_DYNAMITE ||
14582 element == EL_EM_DYNAMITE ||
14583 element == EL_SP_DISK_RED)
14585 if (player->inventory_size < MAX_INVENTORY_SIZE)
14586 player->inventory_element[player->inventory_size++] = element;
14588 DrawGameDoorValues();
14590 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14592 player->dynabomb_count++;
14593 player->dynabombs_left++;
14595 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14597 player->dynabomb_size++;
14599 else if (element == EL_DYNABOMB_INCREASE_POWER)
14601 player->dynabomb_xl = TRUE;
14603 else if (IS_KEY(element))
14605 player->key[KEY_NR(element)] = TRUE;
14607 DrawGameDoorValues();
14609 else if (element == EL_DC_KEY_WHITE)
14611 player->num_white_keys++;
14613 // display white keys?
14614 // DrawGameDoorValues();
14616 else if (IS_ENVELOPE(element))
14618 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14620 if (!wait_for_snapping)
14621 player->show_envelope = element;
14623 else if (element == EL_EMC_LENSES)
14625 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14627 RedrawAllInvisibleElementsForLenses();
14629 else if (element == EL_EMC_MAGNIFIER)
14631 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14633 RedrawAllInvisibleElementsForMagnifier();
14635 else if (IS_DROPPABLE(element) ||
14636 IS_THROWABLE(element)) // can be collected and dropped
14640 if (collect_count == 0)
14641 player->inventory_infinite_element = element;
14643 for (i = 0; i < collect_count; i++)
14644 if (player->inventory_size < MAX_INVENTORY_SIZE)
14645 player->inventory_element[player->inventory_size++] = element;
14647 DrawGameDoorValues();
14649 else if (collect_count > 0)
14651 game.gems_still_needed -= collect_count;
14652 if (game.gems_still_needed < 0)
14653 game.gems_still_needed = 0;
14655 game.snapshot.collected_item = TRUE;
14657 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14659 DisplayGameControlValues();
14662 RaiseScoreElement(element);
14663 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14665 // use old behaviour for old levels (collecting)
14666 if (!level.finish_dig_collect && is_player)
14668 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14669 player->index_bit, dig_side);
14671 // if collecting triggered player relocation, finish collecting tile
14672 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14673 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14676 if (mode == DF_SNAP)
14678 if (level.block_snap_field)
14679 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14681 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14683 // use old behaviour for old levels (snapping)
14684 if (!level.finish_dig_collect)
14685 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14686 player->index_bit, dig_side);
14689 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14691 if (mode == DF_SNAP && element != EL_BD_ROCK)
14692 return MP_NO_ACTION;
14694 if (CAN_FALL(element) && dy)
14695 return MP_NO_ACTION;
14697 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14698 !(element == EL_SPRING && level.use_spring_bug))
14699 return MP_NO_ACTION;
14701 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14702 ((move_direction & MV_VERTICAL &&
14703 ((element_info[element].move_pattern & MV_LEFT &&
14704 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14705 (element_info[element].move_pattern & MV_RIGHT &&
14706 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14707 (move_direction & MV_HORIZONTAL &&
14708 ((element_info[element].move_pattern & MV_UP &&
14709 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14710 (element_info[element].move_pattern & MV_DOWN &&
14711 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14712 return MP_NO_ACTION;
14714 // do not push elements already moving away faster than player
14715 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14716 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14717 return MP_NO_ACTION;
14719 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14721 if (player->push_delay_value == -1 || !player_was_pushing)
14722 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14724 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14726 if (player->push_delay_value == -1)
14727 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14729 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14731 if (!player->is_pushing)
14732 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14735 player->is_pushing = TRUE;
14736 player->is_active = TRUE;
14738 if (!(IN_LEV_FIELD(nextx, nexty) &&
14739 (IS_FREE(nextx, nexty) ||
14740 (IS_SB_ELEMENT(element) &&
14741 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14742 (IS_CUSTOM_ELEMENT(element) &&
14743 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14744 return MP_NO_ACTION;
14746 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14747 return MP_NO_ACTION;
14749 if (player->push_delay == -1) // new pushing; restart delay
14750 player->push_delay = 0;
14752 if (player->push_delay < player->push_delay_value &&
14753 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14754 element != EL_SPRING && element != EL_BALLOON)
14756 // make sure that there is no move delay before next try to push
14757 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14758 player->move_delay = 0;
14760 return MP_NO_ACTION;
14763 if (IS_CUSTOM_ELEMENT(element) &&
14764 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14766 if (!DigFieldByCE(nextx, nexty, element))
14767 return MP_NO_ACTION;
14770 if (IS_SB_ELEMENT(element))
14772 boolean sokoban_task_solved = FALSE;
14774 if (element == EL_SOKOBAN_FIELD_FULL)
14776 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14778 IncrementSokobanFieldsNeeded();
14779 IncrementSokobanObjectsNeeded();
14782 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14784 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14786 DecrementSokobanFieldsNeeded();
14787 DecrementSokobanObjectsNeeded();
14789 // sokoban object was pushed from empty field to sokoban field
14790 if (Back[x][y] == EL_EMPTY)
14791 sokoban_task_solved = TRUE;
14794 Tile[x][y] = EL_SOKOBAN_OBJECT;
14796 if (Back[x][y] == Back[nextx][nexty])
14797 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14798 else if (Back[x][y] != 0)
14799 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14802 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14805 if (sokoban_task_solved &&
14806 game.sokoban_fields_still_needed == 0 &&
14807 game.sokoban_objects_still_needed == 0 &&
14808 level.auto_exit_sokoban)
14810 game.players_still_needed = 0;
14814 PlaySound(SND_GAME_SOKOBAN_SOLVING);
14818 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14820 InitMovingField(x, y, move_direction);
14821 GfxAction[x][y] = ACTION_PUSHING;
14823 if (mode == DF_SNAP)
14824 ContinueMoving(x, y);
14826 MovPos[x][y] = (dx != 0 ? dx : dy);
14828 Pushed[x][y] = TRUE;
14829 Pushed[nextx][nexty] = TRUE;
14831 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14832 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14834 player->push_delay_value = -1; // get new value later
14836 // check for element change _after_ element has been pushed
14837 if (game.use_change_when_pushing_bug)
14839 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14840 player->index_bit, dig_side);
14841 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14842 player->index_bit, dig_side);
14845 else if (IS_SWITCHABLE(element))
14847 if (PLAYER_SWITCHING(player, x, y))
14849 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14850 player->index_bit, dig_side);
14855 player->is_switching = TRUE;
14856 player->switch_x = x;
14857 player->switch_y = y;
14859 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14861 if (element == EL_ROBOT_WHEEL)
14863 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14865 game.robot_wheel_x = x;
14866 game.robot_wheel_y = y;
14867 game.robot_wheel_active = TRUE;
14869 TEST_DrawLevelField(x, y);
14871 else if (element == EL_SP_TERMINAL)
14875 SCAN_PLAYFIELD(xx, yy)
14877 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14881 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14883 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14885 ResetGfxAnimation(xx, yy);
14886 TEST_DrawLevelField(xx, yy);
14890 else if (IS_BELT_SWITCH(element))
14892 ToggleBeltSwitch(x, y);
14894 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14895 element == EL_SWITCHGATE_SWITCH_DOWN ||
14896 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14897 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14899 ToggleSwitchgateSwitch();
14901 else if (element == EL_LIGHT_SWITCH ||
14902 element == EL_LIGHT_SWITCH_ACTIVE)
14904 ToggleLightSwitch(x, y);
14906 else if (element == EL_TIMEGATE_SWITCH ||
14907 element == EL_DC_TIMEGATE_SWITCH)
14909 ActivateTimegateSwitch(x, y);
14911 else if (element == EL_BALLOON_SWITCH_LEFT ||
14912 element == EL_BALLOON_SWITCH_RIGHT ||
14913 element == EL_BALLOON_SWITCH_UP ||
14914 element == EL_BALLOON_SWITCH_DOWN ||
14915 element == EL_BALLOON_SWITCH_NONE ||
14916 element == EL_BALLOON_SWITCH_ANY)
14918 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14919 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14920 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14921 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14922 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14925 else if (element == EL_LAMP)
14927 Tile[x][y] = EL_LAMP_ACTIVE;
14928 game.lights_still_needed--;
14930 ResetGfxAnimation(x, y);
14931 TEST_DrawLevelField(x, y);
14933 else if (element == EL_TIME_ORB_FULL)
14935 Tile[x][y] = EL_TIME_ORB_EMPTY;
14937 if (level.time > 0 || level.use_time_orb_bug)
14939 TimeLeft += level.time_orb_time;
14940 game.no_level_time_limit = FALSE;
14942 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14944 DisplayGameControlValues();
14947 ResetGfxAnimation(x, y);
14948 TEST_DrawLevelField(x, y);
14950 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14951 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14955 game.ball_active = !game.ball_active;
14957 SCAN_PLAYFIELD(xx, yy)
14959 int e = Tile[xx][yy];
14961 if (game.ball_active)
14963 if (e == EL_EMC_MAGIC_BALL)
14964 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14965 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14966 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14970 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14971 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14972 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14973 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14978 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14979 player->index_bit, dig_side);
14981 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14982 player->index_bit, dig_side);
14984 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14985 player->index_bit, dig_side);
14991 if (!PLAYER_SWITCHING(player, x, y))
14993 player->is_switching = TRUE;
14994 player->switch_x = x;
14995 player->switch_y = y;
14997 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14998 player->index_bit, dig_side);
14999 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15000 player->index_bit, dig_side);
15002 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
15003 player->index_bit, dig_side);
15004 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15005 player->index_bit, dig_side);
15008 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
15009 player->index_bit, dig_side);
15010 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15011 player->index_bit, dig_side);
15013 return MP_NO_ACTION;
15016 player->push_delay = -1;
15018 if (is_player) // function can also be called by EL_PENGUIN
15020 if (Tile[x][y] != element) // really digged/collected something
15022 player->is_collecting = !player->is_digging;
15023 player->is_active = TRUE;
15025 player->last_removed_element = element;
15032 static boolean DigFieldByCE(int x, int y, int digging_element)
15034 int element = Tile[x][y];
15036 if (!IS_FREE(x, y))
15038 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
15039 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
15042 // no element can dig solid indestructible elements
15043 if (IS_INDESTRUCTIBLE(element) &&
15044 !IS_DIGGABLE(element) &&
15045 !IS_COLLECTIBLE(element))
15048 if (AmoebaNr[x][y] &&
15049 (element == EL_AMOEBA_FULL ||
15050 element == EL_BD_AMOEBA ||
15051 element == EL_AMOEBA_GROWING))
15053 AmoebaCnt[AmoebaNr[x][y]]--;
15054 AmoebaCnt2[AmoebaNr[x][y]]--;
15057 if (IS_MOVING(x, y))
15058 RemoveMovingField(x, y);
15062 TEST_DrawLevelField(x, y);
15065 // if digged element was about to explode, prevent the explosion
15066 ExplodeField[x][y] = EX_TYPE_NONE;
15068 PlayLevelSoundAction(x, y, action);
15071 Store[x][y] = EL_EMPTY;
15073 // this makes it possible to leave the removed element again
15074 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15075 Store[x][y] = element;
15080 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15082 int jx = player->jx, jy = player->jy;
15083 int x = jx + dx, y = jy + dy;
15084 int snap_direction = (dx == -1 ? MV_LEFT :
15085 dx == +1 ? MV_RIGHT :
15087 dy == +1 ? MV_DOWN : MV_NONE);
15088 boolean can_continue_snapping = (level.continuous_snapping &&
15089 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15091 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15094 if (!player->active || !IN_LEV_FIELD(x, y))
15102 if (player->MovPos == 0)
15103 player->is_pushing = FALSE;
15105 player->is_snapping = FALSE;
15107 if (player->MovPos == 0)
15109 player->is_moving = FALSE;
15110 player->is_digging = FALSE;
15111 player->is_collecting = FALSE;
15117 // prevent snapping with already pressed snap key when not allowed
15118 if (player->is_snapping && !can_continue_snapping)
15121 player->MovDir = snap_direction;
15123 if (player->MovPos == 0)
15125 player->is_moving = FALSE;
15126 player->is_digging = FALSE;
15127 player->is_collecting = FALSE;
15130 player->is_dropping = FALSE;
15131 player->is_dropping_pressed = FALSE;
15132 player->drop_pressed_delay = 0;
15134 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15137 player->is_snapping = TRUE;
15138 player->is_active = TRUE;
15140 if (player->MovPos == 0)
15142 player->is_moving = FALSE;
15143 player->is_digging = FALSE;
15144 player->is_collecting = FALSE;
15147 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15148 TEST_DrawLevelField(player->last_jx, player->last_jy);
15150 TEST_DrawLevelField(x, y);
15155 static boolean DropElement(struct PlayerInfo *player)
15157 int old_element, new_element;
15158 int dropx = player->jx, dropy = player->jy;
15159 int drop_direction = player->MovDir;
15160 int drop_side = drop_direction;
15161 int drop_element = get_next_dropped_element(player);
15163 /* do not drop an element on top of another element; when holding drop key
15164 pressed without moving, dropped element must move away before the next
15165 element can be dropped (this is especially important if the next element
15166 is dynamite, which can be placed on background for historical reasons) */
15167 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15170 if (IS_THROWABLE(drop_element))
15172 dropx += GET_DX_FROM_DIR(drop_direction);
15173 dropy += GET_DY_FROM_DIR(drop_direction);
15175 if (!IN_LEV_FIELD(dropx, dropy))
15179 old_element = Tile[dropx][dropy]; // old element at dropping position
15180 new_element = drop_element; // default: no change when dropping
15182 // check if player is active, not moving and ready to drop
15183 if (!player->active || player->MovPos || player->drop_delay > 0)
15186 // check if player has anything that can be dropped
15187 if (new_element == EL_UNDEFINED)
15190 // only set if player has anything that can be dropped
15191 player->is_dropping_pressed = TRUE;
15193 // check if drop key was pressed long enough for EM style dynamite
15194 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15197 // check if anything can be dropped at the current position
15198 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15201 // collected custom elements can only be dropped on empty fields
15202 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15205 if (old_element != EL_EMPTY)
15206 Back[dropx][dropy] = old_element; // store old element on this field
15208 ResetGfxAnimation(dropx, dropy);
15209 ResetRandomAnimationValue(dropx, dropy);
15211 if (player->inventory_size > 0 ||
15212 player->inventory_infinite_element != EL_UNDEFINED)
15214 if (player->inventory_size > 0)
15216 player->inventory_size--;
15218 DrawGameDoorValues();
15220 if (new_element == EL_DYNAMITE)
15221 new_element = EL_DYNAMITE_ACTIVE;
15222 else if (new_element == EL_EM_DYNAMITE)
15223 new_element = EL_EM_DYNAMITE_ACTIVE;
15224 else if (new_element == EL_SP_DISK_RED)
15225 new_element = EL_SP_DISK_RED_ACTIVE;
15228 Tile[dropx][dropy] = new_element;
15230 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15231 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15232 el2img(Tile[dropx][dropy]), 0);
15234 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15236 // needed if previous element just changed to "empty" in the last frame
15237 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15239 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15240 player->index_bit, drop_side);
15241 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15243 player->index_bit, drop_side);
15245 TestIfElementTouchesCustomElement(dropx, dropy);
15247 else // player is dropping a dyna bomb
15249 player->dynabombs_left--;
15251 Tile[dropx][dropy] = new_element;
15253 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15254 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15255 el2img(Tile[dropx][dropy]), 0);
15257 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15260 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15261 InitField_WithBug1(dropx, dropy, FALSE);
15263 new_element = Tile[dropx][dropy]; // element might have changed
15265 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15266 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15268 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15269 MovDir[dropx][dropy] = drop_direction;
15271 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15273 // do not cause impact style collision by dropping elements that can fall
15274 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15277 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15278 player->is_dropping = TRUE;
15280 player->drop_pressed_delay = 0;
15281 player->is_dropping_pressed = FALSE;
15283 player->drop_x = dropx;
15284 player->drop_y = dropy;
15289 // ----------------------------------------------------------------------------
15290 // game sound playing functions
15291 // ----------------------------------------------------------------------------
15293 static int *loop_sound_frame = NULL;
15294 static int *loop_sound_volume = NULL;
15296 void InitPlayLevelSound(void)
15298 int num_sounds = getSoundListSize();
15300 checked_free(loop_sound_frame);
15301 checked_free(loop_sound_volume);
15303 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15304 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15307 static void PlayLevelSoundExt(int x, int y, int nr, boolean is_loop_sound)
15309 int sx = SCREENX(x), sy = SCREENY(y);
15310 int volume, stereo_position;
15311 int max_distance = 8;
15312 int type = (is_loop_sound ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15314 if ((!setup.sound_simple && !is_loop_sound) ||
15315 (!setup.sound_loops && is_loop_sound))
15318 if (!IN_LEV_FIELD(x, y) ||
15319 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15320 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15323 volume = SOUND_MAX_VOLUME;
15325 if (!IN_SCR_FIELD(sx, sy))
15327 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15328 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15330 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15333 stereo_position = (SOUND_MAX_LEFT +
15334 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15335 (SCR_FIELDX + 2 * max_distance));
15339 /* This assures that quieter loop sounds do not overwrite louder ones,
15340 while restarting sound volume comparison with each new game frame. */
15342 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15345 loop_sound_volume[nr] = volume;
15346 loop_sound_frame[nr] = FrameCounter;
15349 PlaySoundExt(nr, volume, stereo_position, type);
15352 static void PlayLevelSound(int x, int y, int nr)
15354 PlayLevelSoundExt(x, y, nr, IS_LOOP_SOUND(nr));
15357 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15359 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15360 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15361 y < LEVELY(BY1) ? LEVELY(BY1) :
15362 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15366 static void PlayLevelSoundAction(int x, int y, int action)
15368 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15371 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15373 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15375 if (sound_effect != SND_UNDEFINED)
15376 PlayLevelSound(x, y, sound_effect);
15379 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15382 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15384 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15385 PlayLevelSound(x, y, sound_effect);
15388 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15390 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15392 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15393 PlayLevelSound(x, y, sound_effect);
15396 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15398 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15400 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15401 StopSound(sound_effect);
15404 static int getLevelMusicNr(void)
15406 int level_pos = level_nr - leveldir_current->first_level;
15408 if (levelset.music[level_nr] != MUS_UNDEFINED)
15409 return levelset.music[level_nr]; // from config file
15411 return MAP_NOCONF_MUSIC(level_pos); // from music dir
15414 static void FadeLevelSounds(void)
15419 static void FadeLevelMusic(void)
15421 int music_nr = getLevelMusicNr();
15422 char *curr_music = getCurrentlyPlayingMusicFilename();
15423 char *next_music = getMusicInfoEntryFilename(music_nr);
15425 if (!strEqual(curr_music, next_music))
15429 void FadeLevelSoundsAndMusic(void)
15435 static void PlayLevelMusic(void)
15437 int music_nr = getLevelMusicNr();
15438 char *curr_music = getCurrentlyPlayingMusicFilename();
15439 char *next_music = getMusicInfoEntryFilename(music_nr);
15441 if (!strEqual(curr_music, next_music))
15442 PlayMusicLoop(music_nr);
15445 static int getSoundAction_BD(int sample)
15451 case GD_S_DIRT_BALL:
15453 case GD_S_FALLING_WALL:
15454 return ACTION_IMPACT;
15456 case GD_S_NUT_CRACK:
15457 return ACTION_BREAKING;
15459 case GD_S_EXPANDING_WALL:
15460 case GD_S_WALL_REAPPEAR:
15463 case GD_S_ACID_SPREAD:
15464 return ACTION_GROWING;
15466 case GD_S_DIAMOND_COLLECT:
15467 case GD_S_SKELETON_COLLECT:
15468 case GD_S_PNEUMATIC_COLLECT:
15469 case GD_S_BOMB_COLLECT:
15470 case GD_S_CLOCK_COLLECT:
15471 case GD_S_SWEET_COLLECT:
15472 case GD_S_KEY_COLLECT:
15473 case GD_S_DIAMOND_KEY_COLLECT:
15474 return ACTION_COLLECTING;
15476 case GD_S_BOMB_PLACE:
15477 case GD_S_REPLICATOR:
15478 return ACTION_DROPPING;
15480 case GD_S_BLADDER_MOVE:
15481 return ACTION_MOVING;
15483 case GD_S_BLADDER_SPENDER:
15484 case GD_S_BLADDER_CONVERT:
15485 case GD_S_GRAVITY_CHANGE:
15486 return ACTION_CHANGING;
15488 case GD_S_BITER_EAT:
15489 return ACTION_EATING;
15491 case GD_S_DOOR_OPEN:
15493 return ACTION_OPENING;
15495 case GD_S_WALK_EARTH:
15496 return ACTION_DIGGING;
15498 case GD_S_WALK_EMPTY:
15499 return ACTION_WALKING;
15501 case GD_S_SWITCH_BITER:
15502 case GD_S_SWITCH_CREATURES:
15503 case GD_S_SWITCH_GRAVITY:
15504 case GD_S_SWITCH_EXPANDING:
15505 case GD_S_SWITCH_CONVEYOR:
15506 case GD_S_SWITCH_REPLICATOR:
15507 case GD_S_STIRRING:
15508 return ACTION_ACTIVATING;
15510 case GD_S_BOX_PUSH:
15511 return ACTION_PUSHING;
15513 case GD_S_TELEPORTER:
15514 return ACTION_PASSING;
15516 case GD_S_EXPLOSION:
15517 case GD_S_BOMB_EXPLOSION:
15518 case GD_S_GHOST_EXPLOSION:
15519 case GD_S_VOODOO_EXPLOSION:
15520 case GD_S_NITRO_EXPLOSION:
15521 return ACTION_EXPLODING;
15525 case GD_S_AMOEBA_MAGIC:
15526 case GD_S_MAGIC_WALL:
15527 case GD_S_PNEUMATIC_HAMMER:
15529 return ACTION_ACTIVE;
15531 case GD_S_DIAMOND_RANDOM:
15532 case GD_S_DIAMOND_1:
15533 case GD_S_DIAMOND_2:
15534 case GD_S_DIAMOND_3:
15535 case GD_S_DIAMOND_4:
15536 case GD_S_DIAMOND_5:
15537 case GD_S_DIAMOND_6:
15538 case GD_S_DIAMOND_7:
15539 case GD_S_DIAMOND_8:
15540 case GD_S_TIMEOUT_0:
15541 case GD_S_TIMEOUT_1:
15542 case GD_S_TIMEOUT_2:
15543 case GD_S_TIMEOUT_3:
15544 case GD_S_TIMEOUT_4:
15545 case GD_S_TIMEOUT_5:
15546 case GD_S_TIMEOUT_6:
15547 case GD_S_TIMEOUT_7:
15548 case GD_S_TIMEOUT_8:
15549 case GD_S_TIMEOUT_9:
15550 case GD_S_TIMEOUT_10:
15551 case GD_S_BONUS_LIFE:
15552 // kludge to prevent playing as loop sound
15553 return ACTION_OTHER;
15555 case GD_S_FINISHED:
15556 return ACTION_DEFAULT;
15559 return ACTION_DEFAULT;
15563 static int getSoundEffect_BD(int element_bd, int sample)
15565 int sound_action = getSoundAction_BD(sample);
15566 int sound_effect = element_info[SND_ELEMENT(element_bd)].sound[sound_action];
15570 if (sound_action != ACTION_OTHER &&
15571 sound_action != ACTION_DEFAULT)
15572 return sound_effect;
15577 case GD_S_DIAMOND_RANDOM:
15578 nr = GetSimpleRandom(8);
15579 sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr;
15582 case GD_S_DIAMOND_1:
15583 case GD_S_DIAMOND_2:
15584 case GD_S_DIAMOND_3:
15585 case GD_S_DIAMOND_4:
15586 case GD_S_DIAMOND_5:
15587 case GD_S_DIAMOND_6:
15588 case GD_S_DIAMOND_7:
15589 case GD_S_DIAMOND_8:
15590 nr = sample - GD_S_DIAMOND_1;
15591 sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr;
15594 case GD_S_TIMEOUT_0:
15595 case GD_S_TIMEOUT_1:
15596 case GD_S_TIMEOUT_2:
15597 case GD_S_TIMEOUT_3:
15598 case GD_S_TIMEOUT_4:
15599 case GD_S_TIMEOUT_5:
15600 case GD_S_TIMEOUT_6:
15601 case GD_S_TIMEOUT_7:
15602 case GD_S_TIMEOUT_8:
15603 case GD_S_TIMEOUT_9:
15604 case GD_S_TIMEOUT_10:
15605 nr = sample - GD_S_TIMEOUT_0;
15606 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME_0 + nr;
15608 if (getSoundInfoEntryFilename(sound_effect) == NULL && sample != GD_S_TIMEOUT_0)
15609 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME;
15612 case GD_S_FINISHED:
15613 sound_effect = SND_GAME_LEVELTIME_BONUS;
15616 case GD_S_BONUS_LIFE:
15617 sound_effect = SND_GAME_HEALTH_BONUS;
15621 sound_effect = SND_UNDEFINED;
15625 return sound_effect;
15628 void PlayLevelSound_BD(int xx, int yy, int element_bd, int sample)
15630 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15631 int sound_effect = getSoundEffect_BD(element, sample);
15632 int sound_action = getSoundAction_BD(sample);
15633 boolean is_loop_sound = IS_LOOP_SOUND(sound_effect);
15635 int x = xx - offset;
15636 int y = yy - offset;
15638 if (sound_action == ACTION_OTHER)
15639 is_loop_sound = FALSE;
15641 if (sound_effect != SND_UNDEFINED)
15642 PlayLevelSoundExt(x, y, sound_effect, is_loop_sound);
15645 void StopSound_BD(int element_bd, int sample)
15647 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15648 int sound_effect = getSoundEffect_BD(element, sample);
15650 if (sound_effect != SND_UNDEFINED)
15651 StopSound(sound_effect);
15654 boolean isSoundPlaying_BD(int element_bd, int sample)
15656 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15657 int sound_effect = getSoundEffect_BD(element, sample);
15659 if (sound_effect != SND_UNDEFINED)
15660 return isSoundPlaying(sound_effect);
15665 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15667 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15669 int x = xx - offset;
15670 int y = yy - offset;
15675 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15679 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15683 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15687 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15691 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15695 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15699 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15702 case SOUND_android_clone:
15703 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15706 case SOUND_android_move:
15707 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15711 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15715 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15719 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15722 case SOUND_eater_eat:
15723 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15727 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15730 case SOUND_collect:
15731 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15734 case SOUND_diamond:
15735 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15739 // !!! CHECK THIS !!!
15741 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15743 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15747 case SOUND_wonderfall:
15748 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15752 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15756 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15760 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15764 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15768 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15772 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15776 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15780 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15783 case SOUND_exit_open:
15784 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15787 case SOUND_exit_leave:
15788 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15791 case SOUND_dynamite:
15792 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15796 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15800 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15804 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15808 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15812 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15816 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15820 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15825 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15827 int element = map_element_SP_to_RND(element_sp);
15828 int action = map_action_SP_to_RND(action_sp);
15829 int offset = (setup.sp_show_border_elements ? 0 : 1);
15830 int x = xx - offset;
15831 int y = yy - offset;
15833 PlayLevelSoundElementAction(x, y, element, action);
15836 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15838 int element = map_element_MM_to_RND(element_mm);
15839 int action = map_action_MM_to_RND(action_mm);
15841 int x = xx - offset;
15842 int y = yy - offset;
15844 if (!IS_MM_ELEMENT(element))
15845 element = EL_MM_DEFAULT;
15847 PlayLevelSoundElementAction(x, y, element, action);
15850 void PlaySound_MM(int sound_mm)
15852 int sound = map_sound_MM_to_RND(sound_mm);
15854 if (sound == SND_UNDEFINED)
15860 void PlaySoundLoop_MM(int sound_mm)
15862 int sound = map_sound_MM_to_RND(sound_mm);
15864 if (sound == SND_UNDEFINED)
15867 PlaySoundLoop(sound);
15870 void StopSound_MM(int sound_mm)
15872 int sound = map_sound_MM_to_RND(sound_mm);
15874 if (sound == SND_UNDEFINED)
15880 void RaiseScore(int value)
15882 game.score += value;
15884 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15886 DisplayGameControlValues();
15889 void RaiseScoreElement(int element)
15894 case EL_BD_DIAMOND:
15895 case EL_EMERALD_YELLOW:
15896 case EL_EMERALD_RED:
15897 case EL_EMERALD_PURPLE:
15898 case EL_SP_INFOTRON:
15899 RaiseScore(level.score[SC_EMERALD]);
15902 RaiseScore(level.score[SC_DIAMOND]);
15905 RaiseScore(level.score[SC_CRYSTAL]);
15908 RaiseScore(level.score[SC_PEARL]);
15911 case EL_BD_BUTTERFLY:
15912 case EL_SP_ELECTRON:
15913 RaiseScore(level.score[SC_BUG]);
15916 case EL_BD_FIREFLY:
15917 case EL_SP_SNIKSNAK:
15918 RaiseScore(level.score[SC_SPACESHIP]);
15921 case EL_DARK_YAMYAM:
15922 RaiseScore(level.score[SC_YAMYAM]);
15925 RaiseScore(level.score[SC_ROBOT]);
15928 RaiseScore(level.score[SC_PACMAN]);
15931 RaiseScore(level.score[SC_NUT]);
15934 case EL_EM_DYNAMITE:
15935 case EL_SP_DISK_RED:
15936 case EL_DYNABOMB_INCREASE_NUMBER:
15937 case EL_DYNABOMB_INCREASE_SIZE:
15938 case EL_DYNABOMB_INCREASE_POWER:
15939 RaiseScore(level.score[SC_DYNAMITE]);
15941 case EL_SHIELD_NORMAL:
15942 case EL_SHIELD_DEADLY:
15943 RaiseScore(level.score[SC_SHIELD]);
15945 case EL_EXTRA_TIME:
15946 RaiseScore(level.extra_time_score);
15960 case EL_DC_KEY_WHITE:
15961 RaiseScore(level.score[SC_KEY]);
15964 RaiseScore(element_info[element].collect_score);
15969 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15971 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15975 // prevent short reactivation of overlay buttons while closing door
15976 SetOverlayActive(FALSE);
15977 UnmapGameButtons();
15979 // door may still be open due to skipped or envelope style request
15980 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
15983 if (network.enabled)
15985 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15990 FadeSkipNextFadeIn();
15992 SetGameStatus(GAME_MODE_MAIN);
15997 else // continue playing the game
15999 if (tape.playing && tape.deactivate_display)
16000 TapeDeactivateDisplayOff(TRUE);
16002 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
16004 if (tape.playing && tape.deactivate_display)
16005 TapeDeactivateDisplayOn();
16009 void RequestQuitGame(boolean escape_key_pressed)
16011 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
16012 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
16013 level_editor_test_game);
16014 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
16015 quick_quit || score_info_tape_play);
16017 RequestQuitGameExt(skip_request, quick_quit,
16018 "Do you really want to quit the game?");
16021 static char *getRestartGameMessage(void)
16023 boolean play_again = hasStartedNetworkGame();
16024 static char message[MAX_OUTPUT_LINESIZE];
16025 char *game_over_text = "Game over!";
16026 char *play_again_text = " Play it again?";
16028 if (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
16029 game_mm.game_over_message != NULL)
16030 game_over_text = game_mm.game_over_message;
16032 snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text,
16033 (play_again ? play_again_text : ""));
16038 static void RequestRestartGame(void)
16040 char *message = getRestartGameMessage();
16041 boolean has_started_game = hasStartedNetworkGame();
16042 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
16043 int door_state = DOOR_CLOSE_1;
16045 if (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game)
16047 CloseDoor(door_state);
16049 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16053 // if game was invoked from level editor, also close tape recorder door
16054 if (level_editor_test_game)
16055 door_state = DOOR_CLOSE_ALL;
16057 CloseDoor(door_state);
16059 SetGameStatus(GAME_MODE_MAIN);
16065 boolean CheckRestartGame(void)
16067 static int game_over_delay = 0;
16068 int game_over_delay_value = 50;
16069 boolean game_over = checkGameFailed();
16073 game_over_delay = game_over_delay_value;
16078 if (game_over_delay > 0)
16080 if (game_over_delay == game_over_delay_value / 2)
16081 PlaySound(SND_GAME_LOSING);
16088 // do not ask to play again if request dialog is already active
16089 if (game.request_active)
16092 // do not ask to play again if request dialog already handled
16093 if (game.RestartGameRequested)
16096 // do not ask to play again if game was never actually played
16097 if (!game.GamePlayed)
16100 // do not ask to play again if this was disabled in setup menu
16101 if (!setup.ask_on_game_over)
16104 game.RestartGameRequested = TRUE;
16106 RequestRestartGame();
16111 boolean checkGameSolved(void)
16113 // set for all game engines if level was solved
16114 return game.LevelSolved_GameEnd;
16117 boolean checkGameFailed(void)
16119 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16120 return (game_em.game_over && !game_em.level_solved);
16121 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16122 return (game_sp.game_over && !game_sp.level_solved);
16123 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16124 return (game_mm.game_over && !game_mm.level_solved);
16125 else // GAME_ENGINE_TYPE_RND
16126 return (game.GameOver && !game.LevelSolved);
16129 boolean checkGameEnded(void)
16131 return (checkGameSolved() || checkGameFailed());
16135 // ----------------------------------------------------------------------------
16136 // random generator functions
16137 // ----------------------------------------------------------------------------
16139 unsigned int InitEngineRandom_RND(int seed)
16141 game.num_random_calls = 0;
16143 return InitEngineRandom(seed);
16146 unsigned int RND(int max)
16150 game.num_random_calls++;
16152 return GetEngineRandom(max);
16159 // ----------------------------------------------------------------------------
16160 // game engine snapshot handling functions
16161 // ----------------------------------------------------------------------------
16163 struct EngineSnapshotInfo
16165 // runtime values for custom element collect score
16166 int collect_score[NUM_CUSTOM_ELEMENTS];
16168 // runtime values for group element choice position
16169 int choice_pos[NUM_GROUP_ELEMENTS];
16171 // runtime values for belt position animations
16172 int belt_graphic[4][NUM_BELT_PARTS];
16173 int belt_anim_mode[4][NUM_BELT_PARTS];
16176 static struct EngineSnapshotInfo engine_snapshot_rnd;
16177 static char *snapshot_level_identifier = NULL;
16178 static int snapshot_level_nr = -1;
16180 static void SaveEngineSnapshotValues_RND(void)
16182 static int belt_base_active_element[4] =
16184 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
16185 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
16186 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
16187 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
16191 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16193 int element = EL_CUSTOM_START + i;
16195 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
16198 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16200 int element = EL_GROUP_START + i;
16202 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
16205 for (i = 0; i < 4; i++)
16207 for (j = 0; j < NUM_BELT_PARTS; j++)
16209 int element = belt_base_active_element[i] + j;
16210 int graphic = el2img(element);
16211 int anim_mode = graphic_info[graphic].anim_mode;
16213 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
16214 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
16219 static void LoadEngineSnapshotValues_RND(void)
16221 unsigned int num_random_calls = game.num_random_calls;
16224 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16226 int element = EL_CUSTOM_START + i;
16228 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
16231 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16233 int element = EL_GROUP_START + i;
16235 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
16238 for (i = 0; i < 4; i++)
16240 for (j = 0; j < NUM_BELT_PARTS; j++)
16242 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
16243 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
16245 graphic_info[graphic].anim_mode = anim_mode;
16249 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16251 InitRND(tape.random_seed);
16252 for (i = 0; i < num_random_calls; i++)
16256 if (game.num_random_calls != num_random_calls)
16258 Error("number of random calls out of sync");
16259 Error("number of random calls should be %d", num_random_calls);
16260 Error("number of random calls is %d", game.num_random_calls);
16262 Fail("this should not happen -- please debug");
16266 void FreeEngineSnapshotSingle(void)
16268 FreeSnapshotSingle();
16270 setString(&snapshot_level_identifier, NULL);
16271 snapshot_level_nr = -1;
16274 void FreeEngineSnapshotList(void)
16276 FreeSnapshotList();
16279 static ListNode *SaveEngineSnapshotBuffers(void)
16281 ListNode *buffers = NULL;
16283 // copy some special values to a structure better suited for the snapshot
16285 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16286 SaveEngineSnapshotValues_RND();
16287 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16288 SaveEngineSnapshotValues_EM();
16289 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16290 SaveEngineSnapshotValues_SP(&buffers);
16291 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16292 SaveEngineSnapshotValues_MM();
16294 // save values stored in special snapshot structure
16296 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16297 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
16298 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16299 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
16300 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16301 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
16302 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16303 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
16305 // save further RND engine values
16307 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
16308 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
16309 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
16311 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
16312 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
16313 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
16314 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16315 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTimeFrames));
16316 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16318 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16319 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16320 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16322 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16324 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16325 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16327 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16328 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16329 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16330 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16331 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16332 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16333 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16334 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16335 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16336 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16337 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16338 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16339 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16340 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16341 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16342 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16343 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16344 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16346 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16347 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16349 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16350 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16351 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16353 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16354 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16356 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16357 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16358 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16359 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16360 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16361 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16363 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16364 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16367 ListNode *node = engine_snapshot_list_rnd;
16370 while (node != NULL)
16372 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16377 Debug("game:playing:SaveEngineSnapshotBuffers",
16378 "size of engine snapshot: %d bytes", num_bytes);
16384 void SaveEngineSnapshotSingle(void)
16386 ListNode *buffers = SaveEngineSnapshotBuffers();
16388 // finally save all snapshot buffers to single snapshot
16389 SaveSnapshotSingle(buffers);
16391 // save level identification information
16392 setString(&snapshot_level_identifier, leveldir_current->identifier);
16393 snapshot_level_nr = level_nr;
16396 boolean CheckSaveEngineSnapshotToList(void)
16398 boolean save_snapshot =
16399 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16400 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16401 game.snapshot.changed_action) ||
16402 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16403 game.snapshot.collected_item));
16405 game.snapshot.changed_action = FALSE;
16406 game.snapshot.collected_item = FALSE;
16407 game.snapshot.save_snapshot = save_snapshot;
16409 return save_snapshot;
16412 void SaveEngineSnapshotToList(void)
16414 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16418 ListNode *buffers = SaveEngineSnapshotBuffers();
16420 // finally save all snapshot buffers to snapshot list
16421 SaveSnapshotToList(buffers);
16424 void SaveEngineSnapshotToListInitial(void)
16426 FreeEngineSnapshotList();
16428 SaveEngineSnapshotToList();
16431 static void LoadEngineSnapshotValues(void)
16433 // restore special values from snapshot structure
16435 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16436 LoadEngineSnapshotValues_RND();
16437 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16438 LoadEngineSnapshotValues_EM();
16439 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16440 LoadEngineSnapshotValues_SP();
16441 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16442 LoadEngineSnapshotValues_MM();
16445 void LoadEngineSnapshotSingle(void)
16447 LoadSnapshotSingle();
16449 LoadEngineSnapshotValues();
16452 static void LoadEngineSnapshot_Undo(int steps)
16454 LoadSnapshotFromList_Older(steps);
16456 LoadEngineSnapshotValues();
16459 static void LoadEngineSnapshot_Redo(int steps)
16461 LoadSnapshotFromList_Newer(steps);
16463 LoadEngineSnapshotValues();
16466 boolean CheckEngineSnapshotSingle(void)
16468 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16469 snapshot_level_nr == level_nr);
16472 boolean CheckEngineSnapshotList(void)
16474 return CheckSnapshotList();
16478 // ---------- new game button stuff -------------------------------------------
16485 boolean *setup_value;
16486 boolean allowed_on_tape;
16487 boolean is_touch_button;
16489 } gamebutton_info[NUM_GAME_BUTTONS] =
16492 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16493 GAME_CTRL_ID_STOP, NULL,
16494 TRUE, FALSE, "stop game"
16497 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16498 GAME_CTRL_ID_PAUSE, NULL,
16499 TRUE, FALSE, "pause game"
16502 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16503 GAME_CTRL_ID_PLAY, NULL,
16504 TRUE, FALSE, "play game"
16507 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16508 GAME_CTRL_ID_UNDO, NULL,
16509 TRUE, FALSE, "undo step"
16512 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16513 GAME_CTRL_ID_REDO, NULL,
16514 TRUE, FALSE, "redo step"
16517 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16518 GAME_CTRL_ID_SAVE, NULL,
16519 TRUE, FALSE, "save game"
16522 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16523 GAME_CTRL_ID_PAUSE2, NULL,
16524 TRUE, FALSE, "pause game"
16527 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16528 GAME_CTRL_ID_LOAD, NULL,
16529 TRUE, FALSE, "load game"
16532 IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart,
16533 GAME_CTRL_ID_RESTART, NULL,
16534 TRUE, FALSE, "restart game"
16537 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16538 GAME_CTRL_ID_PANEL_STOP, NULL,
16539 FALSE, FALSE, "stop game"
16542 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16543 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16544 FALSE, FALSE, "pause game"
16547 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16548 GAME_CTRL_ID_PANEL_PLAY, NULL,
16549 FALSE, FALSE, "play game"
16552 IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart,
16553 GAME_CTRL_ID_PANEL_RESTART, NULL,
16554 FALSE, FALSE, "restart game"
16557 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16558 GAME_CTRL_ID_TOUCH_STOP, NULL,
16559 FALSE, TRUE, "stop game"
16562 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16563 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16564 FALSE, TRUE, "pause game"
16567 IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart,
16568 GAME_CTRL_ID_TOUCH_RESTART, NULL,
16569 FALSE, TRUE, "restart game"
16572 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16573 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16574 TRUE, FALSE, "background music on/off"
16577 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16578 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16579 TRUE, FALSE, "sound loops on/off"
16582 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16583 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16584 TRUE, FALSE, "normal sounds on/off"
16587 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16588 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16589 FALSE, FALSE, "background music on/off"
16592 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16593 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16594 FALSE, FALSE, "sound loops on/off"
16597 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16598 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16599 FALSE, FALSE, "normal sounds on/off"
16603 void CreateGameButtons(void)
16607 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16609 int graphic = gamebutton_info[i].graphic;
16610 struct GraphicInfo *gfx = &graphic_info[graphic];
16611 struct XY *pos = gamebutton_info[i].pos;
16612 struct GadgetInfo *gi;
16615 unsigned int event_mask;
16616 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16617 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16618 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16619 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16620 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16621 int gd_x = gfx->src_x;
16622 int gd_y = gfx->src_y;
16623 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16624 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16625 int gd_xa = gfx->src_x + gfx->active_xoffset;
16626 int gd_ya = gfx->src_y + gfx->active_yoffset;
16627 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16628 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16629 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16630 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16633 // do not use touch buttons if overlay touch buttons are disabled
16634 if (is_touch_button && !setup.touch.overlay_buttons)
16637 if (gfx->bitmap == NULL)
16639 game_gadget[id] = NULL;
16644 if (id == GAME_CTRL_ID_STOP ||
16645 id == GAME_CTRL_ID_PANEL_STOP ||
16646 id == GAME_CTRL_ID_TOUCH_STOP ||
16647 id == GAME_CTRL_ID_PLAY ||
16648 id == GAME_CTRL_ID_PANEL_PLAY ||
16649 id == GAME_CTRL_ID_SAVE ||
16650 id == GAME_CTRL_ID_LOAD ||
16651 id == GAME_CTRL_ID_RESTART ||
16652 id == GAME_CTRL_ID_PANEL_RESTART ||
16653 id == GAME_CTRL_ID_TOUCH_RESTART)
16655 button_type = GD_TYPE_NORMAL_BUTTON;
16657 event_mask = GD_EVENT_RELEASED;
16659 else if (id == GAME_CTRL_ID_UNDO ||
16660 id == GAME_CTRL_ID_REDO)
16662 button_type = GD_TYPE_NORMAL_BUTTON;
16664 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16668 button_type = GD_TYPE_CHECK_BUTTON;
16669 checked = (gamebutton_info[i].setup_value != NULL ?
16670 *gamebutton_info[i].setup_value : FALSE);
16671 event_mask = GD_EVENT_PRESSED;
16674 gi = CreateGadget(GDI_CUSTOM_ID, id,
16675 GDI_IMAGE_ID, graphic,
16676 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16679 GDI_WIDTH, gfx->width,
16680 GDI_HEIGHT, gfx->height,
16681 GDI_TYPE, button_type,
16682 GDI_STATE, GD_BUTTON_UNPRESSED,
16683 GDI_CHECKED, checked,
16684 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16685 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16686 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16687 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16688 GDI_DIRECT_DRAW, FALSE,
16689 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16690 GDI_EVENT_MASK, event_mask,
16691 GDI_CALLBACK_ACTION, HandleGameButtons,
16695 Fail("cannot create gadget");
16697 game_gadget[id] = gi;
16701 void FreeGameButtons(void)
16705 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16706 FreeGadget(game_gadget[i]);
16709 static void UnmapGameButtonsAtSamePosition(int id)
16713 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16715 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16716 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16717 UnmapGadget(game_gadget[i]);
16720 static void UnmapGameButtonsAtSamePosition_All(void)
16722 if (setup.show_load_save_buttons)
16724 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16725 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16726 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16728 else if (setup.show_undo_redo_buttons)
16730 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16731 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16732 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16736 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16737 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16738 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16740 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16741 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16742 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16746 void MapLoadSaveButtons(void)
16748 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16749 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16751 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16752 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16755 void MapUndoRedoButtons(void)
16757 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16758 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16760 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16761 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16764 void ModifyPauseButtons(void)
16768 GAME_CTRL_ID_PAUSE,
16769 GAME_CTRL_ID_PAUSE2,
16770 GAME_CTRL_ID_PANEL_PAUSE,
16771 GAME_CTRL_ID_TOUCH_PAUSE,
16776 // do not redraw pause button on closed door (may happen when restarting game)
16777 if (!(GetDoorState() & DOOR_OPEN_1))
16780 for (i = 0; ids[i] > -1; i++)
16781 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16784 static void MapGameButtonsExt(boolean on_tape)
16788 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16790 if ((i == GAME_CTRL_ID_UNDO ||
16791 i == GAME_CTRL_ID_REDO) &&
16792 game_status != GAME_MODE_PLAYING)
16795 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16796 MapGadget(game_gadget[i]);
16799 UnmapGameButtonsAtSamePosition_All();
16801 RedrawGameButtons();
16804 static void UnmapGameButtonsExt(boolean on_tape)
16808 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16809 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16810 UnmapGadget(game_gadget[i]);
16813 static void RedrawGameButtonsExt(boolean on_tape)
16817 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16818 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16819 RedrawGadget(game_gadget[i]);
16822 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16827 gi->checked = state;
16830 static void RedrawSoundButtonGadget(int id)
16832 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16833 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16834 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16835 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16836 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16837 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16840 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16841 RedrawGadget(game_gadget[id2]);
16844 void MapGameButtons(void)
16846 MapGameButtonsExt(FALSE);
16849 void UnmapGameButtons(void)
16851 UnmapGameButtonsExt(FALSE);
16854 void RedrawGameButtons(void)
16856 RedrawGameButtonsExt(FALSE);
16859 void MapGameButtonsOnTape(void)
16861 MapGameButtonsExt(TRUE);
16864 void UnmapGameButtonsOnTape(void)
16866 UnmapGameButtonsExt(TRUE);
16869 void RedrawGameButtonsOnTape(void)
16871 RedrawGameButtonsExt(TRUE);
16874 static void GameUndoRedoExt(void)
16876 ClearPlayerAction();
16878 tape.pausing = TRUE;
16881 UpdateAndDisplayGameControlValues();
16883 DrawCompleteVideoDisplay();
16884 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16885 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16886 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16888 ModifyPauseButtons();
16893 static void GameUndo(int steps)
16895 if (!CheckEngineSnapshotList())
16898 int tape_property_bits = tape.property_bits;
16900 LoadEngineSnapshot_Undo(steps);
16902 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16907 static void GameRedo(int steps)
16909 if (!CheckEngineSnapshotList())
16912 int tape_property_bits = tape.property_bits;
16914 LoadEngineSnapshot_Redo(steps);
16916 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16921 static void HandleGameButtonsExt(int id, int button)
16923 static boolean game_undo_executed = FALSE;
16924 int steps = BUTTON_STEPSIZE(button);
16925 boolean handle_game_buttons =
16926 (game_status == GAME_MODE_PLAYING ||
16927 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16929 if (!handle_game_buttons)
16934 case GAME_CTRL_ID_STOP:
16935 case GAME_CTRL_ID_PANEL_STOP:
16936 case GAME_CTRL_ID_TOUCH_STOP:
16941 case GAME_CTRL_ID_PAUSE:
16942 case GAME_CTRL_ID_PAUSE2:
16943 case GAME_CTRL_ID_PANEL_PAUSE:
16944 case GAME_CTRL_ID_TOUCH_PAUSE:
16945 if (network.enabled && game_status == GAME_MODE_PLAYING)
16948 SendToServer_ContinuePlaying();
16950 SendToServer_PausePlaying();
16953 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16955 game_undo_executed = FALSE;
16959 case GAME_CTRL_ID_PLAY:
16960 case GAME_CTRL_ID_PANEL_PLAY:
16961 if (game_status == GAME_MODE_MAIN)
16963 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16965 else if (tape.pausing)
16967 if (network.enabled)
16968 SendToServer_ContinuePlaying();
16970 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16974 case GAME_CTRL_ID_UNDO:
16975 // Important: When using "save snapshot when collecting an item" mode,
16976 // load last (current) snapshot for first "undo" after pressing "pause"
16977 // (else the last-but-one snapshot would be loaded, because the snapshot
16978 // pointer already points to the last snapshot when pressing "pause",
16979 // which is fine for "every step/move" mode, but not for "every collect")
16980 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16981 !game_undo_executed)
16984 game_undo_executed = TRUE;
16989 case GAME_CTRL_ID_REDO:
16993 case GAME_CTRL_ID_SAVE:
16997 case GAME_CTRL_ID_LOAD:
17001 case GAME_CTRL_ID_RESTART:
17002 case GAME_CTRL_ID_PANEL_RESTART:
17003 case GAME_CTRL_ID_TOUCH_RESTART:
17008 case SOUND_CTRL_ID_MUSIC:
17009 case SOUND_CTRL_ID_PANEL_MUSIC:
17010 if (setup.sound_music)
17012 setup.sound_music = FALSE;
17016 else if (audio.music_available)
17018 setup.sound = setup.sound_music = TRUE;
17020 SetAudioMode(setup.sound);
17022 if (game_status == GAME_MODE_PLAYING)
17026 RedrawSoundButtonGadget(id);
17030 case SOUND_CTRL_ID_LOOPS:
17031 case SOUND_CTRL_ID_PANEL_LOOPS:
17032 if (setup.sound_loops)
17033 setup.sound_loops = FALSE;
17034 else if (audio.loops_available)
17036 setup.sound = setup.sound_loops = TRUE;
17038 SetAudioMode(setup.sound);
17041 RedrawSoundButtonGadget(id);
17045 case SOUND_CTRL_ID_SIMPLE:
17046 case SOUND_CTRL_ID_PANEL_SIMPLE:
17047 if (setup.sound_simple)
17048 setup.sound_simple = FALSE;
17049 else if (audio.sound_available)
17051 setup.sound = setup.sound_simple = TRUE;
17053 SetAudioMode(setup.sound);
17056 RedrawSoundButtonGadget(id);
17065 static void HandleGameButtons(struct GadgetInfo *gi)
17067 HandleGameButtonsExt(gi->custom_id, gi->event.button);
17070 void HandleSoundButtonKeys(Key key)
17072 if (key == setup.shortcut.sound_simple)
17073 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
17074 else if (key == setup.shortcut.sound_loops)
17075 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
17076 else if (key == setup.shortcut.sound_music)
17077 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);