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 CheckLevelTime_StepCounter(void)
11682 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11683 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11685 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11687 DisplayGameControlValues();
11689 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11690 for (i = 0; i < MAX_PLAYERS; i++)
11691 KillPlayer(&stored_player[i]);
11693 else if (game.no_level_time_limit && !game.all_players_gone)
11695 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11697 DisplayGameControlValues();
11701 static void CheckLevelTime(void)
11705 if (TimeFrames >= FRAMES_PER_SECOND)
11709 for (i = 0; i < MAX_PLAYERS; i++)
11711 struct PlayerInfo *player = &stored_player[i];
11713 if (SHIELD_ON(player))
11715 player->shield_normal_time_left--;
11717 if (player->shield_deadly_time_left > 0)
11718 player->shield_deadly_time_left--;
11722 if (!game.LevelSolved && !level.use_step_counter)
11730 if (TimeLeft <= 10 && game.time_limit)
11731 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11733 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11734 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11736 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11738 if (!TimeLeft && game.time_limit)
11740 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11741 game_em.lev->killed_out_of_time = TRUE;
11743 for (i = 0; i < MAX_PLAYERS; i++)
11744 KillPlayer(&stored_player[i]);
11747 else if (game.no_level_time_limit && !game.all_players_gone)
11749 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11752 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11756 if (TapeTimeFrames >= FRAMES_PER_SECOND)
11758 TapeTimeFrames = 0;
11761 if (tape.recording || tape.playing)
11762 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11765 if (tape.recording || tape.playing)
11766 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11768 UpdateAndDisplayGameControlValues();
11771 void AdvanceFrameAndPlayerCounters(int player_nr)
11775 // advance frame counters (global frame counter and tape time frame counter)
11779 // advance time frame counter (used to control available time to solve level)
11782 // advance player counters (counters for move delay, move animation etc.)
11783 for (i = 0; i < MAX_PLAYERS; i++)
11785 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11786 int move_delay_value = stored_player[i].move_delay_value;
11787 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11789 if (!advance_player_counters) // not all players may be affected
11792 if (move_frames == 0) // less than one move per game frame
11794 int stepsize = TILEX / move_delay_value;
11795 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11796 int count = (stored_player[i].is_moving ?
11797 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11799 if (count % delay == 0)
11803 stored_player[i].Frame += move_frames;
11805 if (stored_player[i].MovPos != 0)
11806 stored_player[i].StepFrame += move_frames;
11808 if (stored_player[i].move_delay > 0)
11809 stored_player[i].move_delay--;
11811 // due to bugs in previous versions, counter must count up, not down
11812 if (stored_player[i].push_delay != -1)
11813 stored_player[i].push_delay++;
11815 if (stored_player[i].drop_delay > 0)
11816 stored_player[i].drop_delay--;
11818 if (stored_player[i].is_dropping_pressed)
11819 stored_player[i].drop_pressed_delay++;
11823 void AdvanceFrameCounter(void)
11828 void AdvanceGfxFrame(void)
11832 SCAN_PLAYFIELD(x, y)
11838 static void HandleMouseAction(struct MouseActionInfo *mouse_action,
11839 struct MouseActionInfo *mouse_action_last)
11841 if (mouse_action->button)
11843 int new_button = (mouse_action->button && mouse_action_last->button == 0);
11844 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button);
11845 int x = mouse_action->lx;
11846 int y = mouse_action->ly;
11847 int element = Tile[x][y];
11851 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
11852 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
11856 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
11857 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
11860 if (level.use_step_counter)
11862 boolean counted_click = FALSE;
11864 // element clicked that can change when clicked/pressed
11865 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
11866 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
11867 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
11868 counted_click = TRUE;
11870 // element clicked that can trigger change when clicked/pressed
11871 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
11872 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
11873 counted_click = TRUE;
11875 if (new_button && counted_click)
11876 CheckLevelTime_StepCounter();
11881 void StartGameActions(boolean init_network_game, boolean record_tape,
11884 unsigned int new_random_seed = InitRND(random_seed);
11887 TapeStartRecording(new_random_seed);
11889 if (setup.auto_pause_on_start && !tape.pausing)
11890 TapeTogglePause(TAPE_TOGGLE_MANUAL);
11892 if (init_network_game)
11894 SendToServer_LevelFile();
11895 SendToServer_StartPlaying();
11903 static void GameActionsExt(void)
11906 static unsigned int game_frame_delay = 0;
11908 unsigned int game_frame_delay_value;
11909 byte *recorded_player_action;
11910 byte summarized_player_action = 0;
11911 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11914 // detect endless loops, caused by custom element programming
11915 if (recursion_loop_detected && recursion_loop_depth == 0)
11917 char *message = getStringCat3("Internal Error! Element ",
11918 EL_NAME(recursion_loop_element),
11919 " caused endless loop! Quit the game?");
11921 Warn("element '%s' caused endless loop in game engine",
11922 EL_NAME(recursion_loop_element));
11924 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11926 recursion_loop_detected = FALSE; // if game should be continued
11933 if (game.restart_level)
11934 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11936 CheckLevelSolved();
11938 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11941 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11944 if (game_status != GAME_MODE_PLAYING) // status might have changed
11947 game_frame_delay_value =
11948 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11950 if (tape.playing && tape.warp_forward && !tape.pausing)
11951 game_frame_delay_value = 0;
11953 SetVideoFrameDelay(game_frame_delay_value);
11955 // (de)activate virtual buttons depending on current game status
11956 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11958 if (game.all_players_gone) // if no players there to be controlled anymore
11959 SetOverlayActive(FALSE);
11960 else if (!tape.playing) // if game continues after tape stopped playing
11961 SetOverlayActive(TRUE);
11966 // ---------- main game synchronization point ----------
11968 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11970 Debug("game:playing:skip", "skip == %d", skip);
11973 // ---------- main game synchronization point ----------
11975 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11979 if (network_playing && !network_player_action_received)
11981 // try to get network player actions in time
11983 // last chance to get network player actions without main loop delay
11984 HandleNetworking();
11986 // game was quit by network peer
11987 if (game_status != GAME_MODE_PLAYING)
11990 // check if network player actions still missing and game still running
11991 if (!network_player_action_received && !checkGameEnded())
11992 return; // failed to get network player actions in time
11994 // do not yet reset "network_player_action_received" (for tape.pausing)
12000 // at this point we know that we really continue executing the game
12002 network_player_action_received = FALSE;
12004 // when playing tape, read previously recorded player input from tape data
12005 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
12007 local_player->effective_mouse_action = local_player->mouse_action;
12009 if (recorded_player_action != NULL)
12010 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
12011 recorded_player_action);
12013 // TapePlayAction() may return NULL when toggling to "pause before death"
12017 if (tape.set_centered_player)
12019 game.centered_player_nr_next = tape.centered_player_nr_next;
12020 game.set_centered_player = TRUE;
12023 for (i = 0; i < MAX_PLAYERS; i++)
12025 summarized_player_action |= stored_player[i].action;
12027 if (!network_playing && (game.team_mode || tape.playing))
12028 stored_player[i].effective_action = stored_player[i].action;
12031 if (network_playing && !checkGameEnded())
12032 SendToServer_MovePlayer(summarized_player_action);
12034 // summarize all actions at local players mapped input device position
12035 // (this allows using different input devices in single player mode)
12036 if (!network.enabled && !game.team_mode)
12037 stored_player[map_player_action[local_player->index_nr]].effective_action =
12038 summarized_player_action;
12040 // summarize all actions at centered player in local team mode
12041 if (tape.recording &&
12042 setup.team_mode && !network.enabled &&
12043 setup.input_on_focus &&
12044 game.centered_player_nr != -1)
12046 for (i = 0; i < MAX_PLAYERS; i++)
12047 stored_player[map_player_action[i]].effective_action =
12048 (i == game.centered_player_nr ? summarized_player_action : 0);
12051 if (recorded_player_action != NULL)
12052 for (i = 0; i < MAX_PLAYERS; i++)
12053 stored_player[i].effective_action = recorded_player_action[i];
12055 for (i = 0; i < MAX_PLAYERS; i++)
12057 tape_action[i] = stored_player[i].effective_action;
12059 /* (this may happen in the RND game engine if a player was not present on
12060 the playfield on level start, but appeared later from a custom element */
12061 if (setup.team_mode &&
12064 !tape.player_participates[i])
12065 tape.player_participates[i] = TRUE;
12068 SetTapeActionFromMouseAction(tape_action,
12069 &local_player->effective_mouse_action);
12071 // only record actions from input devices, but not programmed actions
12072 if (tape.recording)
12073 TapeRecordAction(tape_action);
12075 // remember if game was played (especially after tape stopped playing)
12076 if (!tape.playing && summarized_player_action && !checkGameFailed())
12077 game.GamePlayed = TRUE;
12079 #if USE_NEW_PLAYER_ASSIGNMENTS
12080 // !!! also map player actions in single player mode !!!
12081 // if (game.team_mode)
12084 byte mapped_action[MAX_PLAYERS];
12086 #if DEBUG_PLAYER_ACTIONS
12087 for (i = 0; i < MAX_PLAYERS; i++)
12088 DebugContinued("", "%d, ", stored_player[i].effective_action);
12091 for (i = 0; i < MAX_PLAYERS; i++)
12092 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
12094 for (i = 0; i < MAX_PLAYERS; i++)
12095 stored_player[i].effective_action = mapped_action[i];
12097 #if DEBUG_PLAYER_ACTIONS
12098 DebugContinued("", "=> ");
12099 for (i = 0; i < MAX_PLAYERS; i++)
12100 DebugContinued("", "%d, ", stored_player[i].effective_action);
12101 DebugContinued("game:playing:player", "\n");
12104 #if DEBUG_PLAYER_ACTIONS
12107 for (i = 0; i < MAX_PLAYERS; i++)
12108 DebugContinued("", "%d, ", stored_player[i].effective_action);
12109 DebugContinued("game:playing:player", "\n");
12114 for (i = 0; i < MAX_PLAYERS; i++)
12116 // allow engine snapshot in case of changed movement attempt
12117 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12118 (stored_player[i].effective_action & KEY_MOTION))
12119 game.snapshot.changed_action = TRUE;
12121 // allow engine snapshot in case of snapping/dropping attempt
12122 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12123 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12124 game.snapshot.changed_action = TRUE;
12126 game.snapshot.last_action[i] = stored_player[i].effective_action;
12129 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12131 GameActions_EM_Main();
12133 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12135 GameActions_SP_Main();
12137 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12139 GameActions_MM_Main();
12143 GameActions_RND_Main();
12146 BlitScreenToBitmap(backbuffer);
12148 CheckLevelSolved();
12151 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12153 if (global.show_frames_per_second)
12155 static unsigned int fps_counter = 0;
12156 static int fps_frames = 0;
12157 unsigned int fps_delay_ms = Counter() - fps_counter;
12161 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12163 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12166 fps_counter = Counter();
12168 // always draw FPS to screen after FPS value was updated
12169 redraw_mask |= REDRAW_FPS;
12172 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12173 if (GetDrawDeactivationMask() == REDRAW_NONE)
12174 redraw_mask |= REDRAW_FPS;
12178 static void GameActions_CheckSaveEngineSnapshot(void)
12180 if (!game.snapshot.save_snapshot)
12183 // clear flag for saving snapshot _before_ saving snapshot
12184 game.snapshot.save_snapshot = FALSE;
12186 SaveEngineSnapshotToList();
12189 void GameActions(void)
12193 GameActions_CheckSaveEngineSnapshot();
12196 void GameActions_EM_Main(void)
12198 byte effective_action[MAX_PLAYERS];
12201 for (i = 0; i < MAX_PLAYERS; i++)
12202 effective_action[i] = stored_player[i].effective_action;
12204 GameActions_EM(effective_action);
12207 void GameActions_SP_Main(void)
12209 byte effective_action[MAX_PLAYERS];
12212 for (i = 0; i < MAX_PLAYERS; i++)
12213 effective_action[i] = stored_player[i].effective_action;
12215 GameActions_SP(effective_action);
12217 for (i = 0; i < MAX_PLAYERS; i++)
12219 if (stored_player[i].force_dropping)
12220 stored_player[i].action |= KEY_BUTTON_DROP;
12222 stored_player[i].force_dropping = FALSE;
12226 void GameActions_MM_Main(void)
12230 GameActions_MM(local_player->effective_mouse_action);
12233 void GameActions_RND_Main(void)
12238 void GameActions_RND(void)
12240 static struct MouseActionInfo mouse_action_last = { 0 };
12241 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12242 int magic_wall_x = 0, magic_wall_y = 0;
12243 int i, x, y, element, graphic, last_gfx_frame;
12245 InitPlayfieldScanModeVars();
12247 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12249 SCAN_PLAYFIELD(x, y)
12251 ChangeCount[x][y] = 0;
12252 ChangeEvent[x][y] = -1;
12256 if (game.set_centered_player)
12258 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12260 // switching to "all players" only possible if all players fit to screen
12261 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12263 game.centered_player_nr_next = game.centered_player_nr;
12264 game.set_centered_player = FALSE;
12267 // do not switch focus to non-existing (or non-active) player
12268 if (game.centered_player_nr_next >= 0 &&
12269 !stored_player[game.centered_player_nr_next].active)
12271 game.centered_player_nr_next = game.centered_player_nr;
12272 game.set_centered_player = FALSE;
12276 if (game.set_centered_player &&
12277 ScreenMovPos == 0) // screen currently aligned at tile position
12281 if (game.centered_player_nr_next == -1)
12283 setScreenCenteredToAllPlayers(&sx, &sy);
12287 sx = stored_player[game.centered_player_nr_next].jx;
12288 sy = stored_player[game.centered_player_nr_next].jy;
12291 game.centered_player_nr = game.centered_player_nr_next;
12292 game.set_centered_player = FALSE;
12294 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12295 DrawGameDoorValues();
12298 // check single step mode (set flag and clear again if any player is active)
12299 game.enter_single_step_mode =
12300 (tape.single_step && tape.recording && !tape.pausing);
12302 for (i = 0; i < MAX_PLAYERS; i++)
12304 int actual_player_action = stored_player[i].effective_action;
12307 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12308 - rnd_equinox_tetrachloride 048
12309 - rnd_equinox_tetrachloride_ii 096
12310 - rnd_emanuel_schmieg 002
12311 - doctor_sloan_ww 001, 020
12313 if (stored_player[i].MovPos == 0)
12314 CheckGravityMovement(&stored_player[i]);
12317 // overwrite programmed action with tape action
12318 if (stored_player[i].programmed_action)
12319 actual_player_action = stored_player[i].programmed_action;
12321 PlayerActions(&stored_player[i], actual_player_action);
12323 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12326 // single step pause mode may already have been toggled by "ScrollPlayer()"
12327 if (game.enter_single_step_mode && !tape.pausing)
12328 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12330 ScrollScreen(NULL, SCROLL_GO_ON);
12332 /* for backwards compatibility, the following code emulates a fixed bug that
12333 occured when pushing elements (causing elements that just made their last
12334 pushing step to already (if possible) make their first falling step in the
12335 same game frame, which is bad); this code is also needed to use the famous
12336 "spring push bug" which is used in older levels and might be wanted to be
12337 used also in newer levels, but in this case the buggy pushing code is only
12338 affecting the "spring" element and no other elements */
12340 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12342 for (i = 0; i < MAX_PLAYERS; i++)
12344 struct PlayerInfo *player = &stored_player[i];
12345 int x = player->jx;
12346 int y = player->jy;
12348 if (player->active && player->is_pushing && player->is_moving &&
12350 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12351 Tile[x][y] == EL_SPRING))
12353 ContinueMoving(x, y);
12355 // continue moving after pushing (this is actually a bug)
12356 if (!IS_MOVING(x, y))
12357 Stop[x][y] = FALSE;
12362 SCAN_PLAYFIELD(x, y)
12364 Last[x][y] = Tile[x][y];
12366 ChangeCount[x][y] = 0;
12367 ChangeEvent[x][y] = -1;
12369 // this must be handled before main playfield loop
12370 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12373 if (MovDelay[x][y] <= 0)
12377 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12380 if (MovDelay[x][y] <= 0)
12382 int element = Store[x][y];
12383 int move_direction = MovDir[x][y];
12384 int player_index_bit = Store2[x][y];
12390 TEST_DrawLevelField(x, y);
12392 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12394 if (IS_ENVELOPE(element))
12395 local_player->show_envelope = element;
12400 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12402 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12404 Debug("game:playing:GameActions_RND", "This should never happen!");
12406 ChangePage[x][y] = -1;
12410 Stop[x][y] = FALSE;
12411 if (WasJustMoving[x][y] > 0)
12412 WasJustMoving[x][y]--;
12413 if (WasJustFalling[x][y] > 0)
12414 WasJustFalling[x][y]--;
12415 if (CheckCollision[x][y] > 0)
12416 CheckCollision[x][y]--;
12417 if (CheckImpact[x][y] > 0)
12418 CheckImpact[x][y]--;
12422 /* reset finished pushing action (not done in ContinueMoving() to allow
12423 continuous pushing animation for elements with zero push delay) */
12424 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12426 ResetGfxAnimation(x, y);
12427 TEST_DrawLevelField(x, y);
12431 if (IS_BLOCKED(x, y))
12435 Blocked2Moving(x, y, &oldx, &oldy);
12436 if (!IS_MOVING(oldx, oldy))
12438 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12439 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12440 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12441 Debug("game:playing:GameActions_RND", "This should never happen!");
12447 HandleMouseAction(&mouse_action, &mouse_action_last);
12449 SCAN_PLAYFIELD(x, y)
12451 element = Tile[x][y];
12452 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12453 last_gfx_frame = GfxFrame[x][y];
12455 if (element == EL_EMPTY)
12456 graphic = el2img(GfxElementEmpty[x][y]);
12458 ResetGfxFrame(x, y);
12460 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12461 DrawLevelGraphicAnimation(x, y, graphic);
12463 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12464 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12465 ResetRandomAnimationValue(x, y);
12467 SetRandomAnimationValue(x, y);
12469 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12471 if (IS_INACTIVE(element))
12473 if (IS_ANIMATED(graphic))
12474 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12479 // this may take place after moving, so 'element' may have changed
12480 if (IS_CHANGING(x, y) &&
12481 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12483 int page = element_info[element].event_page_nr[CE_DELAY];
12485 HandleElementChange(x, y, page);
12487 element = Tile[x][y];
12488 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12491 CheckNextToConditions(x, y);
12493 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12497 element = Tile[x][y];
12498 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12500 if (IS_ANIMATED(graphic) &&
12501 !IS_MOVING(x, y) &&
12503 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12505 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12506 TEST_DrawTwinkleOnField(x, y);
12508 else if (element == EL_ACID)
12511 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12513 else if ((element == EL_EXIT_OPEN ||
12514 element == EL_EM_EXIT_OPEN ||
12515 element == EL_SP_EXIT_OPEN ||
12516 element == EL_STEEL_EXIT_OPEN ||
12517 element == EL_EM_STEEL_EXIT_OPEN ||
12518 element == EL_SP_TERMINAL ||
12519 element == EL_SP_TERMINAL_ACTIVE ||
12520 element == EL_EXTRA_TIME ||
12521 element == EL_SHIELD_NORMAL ||
12522 element == EL_SHIELD_DEADLY) &&
12523 IS_ANIMATED(graphic))
12524 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12525 else if (IS_MOVING(x, y))
12526 ContinueMoving(x, y);
12527 else if (IS_ACTIVE_BOMB(element))
12528 CheckDynamite(x, y);
12529 else if (element == EL_AMOEBA_GROWING)
12530 AmoebaGrowing(x, y);
12531 else if (element == EL_AMOEBA_SHRINKING)
12532 AmoebaShrinking(x, y);
12534 #if !USE_NEW_AMOEBA_CODE
12535 else if (IS_AMOEBALIVE(element))
12536 AmoebaReproduce(x, y);
12539 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12541 else if (element == EL_EXIT_CLOSED)
12543 else if (element == EL_EM_EXIT_CLOSED)
12545 else if (element == EL_STEEL_EXIT_CLOSED)
12546 CheckExitSteel(x, y);
12547 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12548 CheckExitSteelEM(x, y);
12549 else if (element == EL_SP_EXIT_CLOSED)
12551 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12552 element == EL_EXPANDABLE_STEELWALL_GROWING)
12554 else if (element == EL_EXPANDABLE_WALL ||
12555 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12556 element == EL_EXPANDABLE_WALL_VERTICAL ||
12557 element == EL_EXPANDABLE_WALL_ANY ||
12558 element == EL_BD_EXPANDABLE_WALL ||
12559 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12560 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12561 element == EL_EXPANDABLE_STEELWALL_ANY)
12562 CheckWallGrowing(x, y);
12563 else if (element == EL_FLAMES)
12564 CheckForDragon(x, y);
12565 else if (element == EL_EXPLOSION)
12566 ; // drawing of correct explosion animation is handled separately
12567 else if (element == EL_ELEMENT_SNAPPING ||
12568 element == EL_DIAGONAL_SHRINKING ||
12569 element == EL_DIAGONAL_GROWING)
12571 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]);
12573 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12575 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12576 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12578 if (IS_BELT_ACTIVE(element))
12579 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12581 if (game.magic_wall_active)
12583 int jx = local_player->jx, jy = local_player->jy;
12585 // play the element sound at the position nearest to the player
12586 if ((element == EL_MAGIC_WALL_FULL ||
12587 element == EL_MAGIC_WALL_ACTIVE ||
12588 element == EL_MAGIC_WALL_EMPTYING ||
12589 element == EL_BD_MAGIC_WALL_FULL ||
12590 element == EL_BD_MAGIC_WALL_ACTIVE ||
12591 element == EL_BD_MAGIC_WALL_EMPTYING ||
12592 element == EL_DC_MAGIC_WALL_FULL ||
12593 element == EL_DC_MAGIC_WALL_ACTIVE ||
12594 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12595 ABS(x - jx) + ABS(y - jy) <
12596 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12604 #if USE_NEW_AMOEBA_CODE
12605 // new experimental amoeba growth stuff
12606 if (!(FrameCounter % 8))
12608 static unsigned int random = 1684108901;
12610 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12612 x = RND(lev_fieldx);
12613 y = RND(lev_fieldy);
12614 element = Tile[x][y];
12616 if (!IS_PLAYER(x, y) &&
12617 (element == EL_EMPTY ||
12618 CAN_GROW_INTO(element) ||
12619 element == EL_QUICKSAND_EMPTY ||
12620 element == EL_QUICKSAND_FAST_EMPTY ||
12621 element == EL_ACID_SPLASH_LEFT ||
12622 element == EL_ACID_SPLASH_RIGHT))
12624 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12625 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12626 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12627 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12628 Tile[x][y] = EL_AMOEBA_DROP;
12631 random = random * 129 + 1;
12636 game.explosions_delayed = FALSE;
12638 SCAN_PLAYFIELD(x, y)
12640 element = Tile[x][y];
12642 if (ExplodeField[x][y])
12643 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12644 else if (element == EL_EXPLOSION)
12645 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12647 ExplodeField[x][y] = EX_TYPE_NONE;
12650 game.explosions_delayed = TRUE;
12652 if (game.magic_wall_active)
12654 if (!(game.magic_wall_time_left % 4))
12656 int element = Tile[magic_wall_x][magic_wall_y];
12658 if (element == EL_BD_MAGIC_WALL_FULL ||
12659 element == EL_BD_MAGIC_WALL_ACTIVE ||
12660 element == EL_BD_MAGIC_WALL_EMPTYING)
12661 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12662 else if (element == EL_DC_MAGIC_WALL_FULL ||
12663 element == EL_DC_MAGIC_WALL_ACTIVE ||
12664 element == EL_DC_MAGIC_WALL_EMPTYING)
12665 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12667 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12670 if (game.magic_wall_time_left > 0)
12672 game.magic_wall_time_left--;
12674 if (!game.magic_wall_time_left)
12676 SCAN_PLAYFIELD(x, y)
12678 element = Tile[x][y];
12680 if (element == EL_MAGIC_WALL_ACTIVE ||
12681 element == EL_MAGIC_WALL_FULL)
12683 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12684 TEST_DrawLevelField(x, y);
12686 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12687 element == EL_BD_MAGIC_WALL_FULL)
12689 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12690 TEST_DrawLevelField(x, y);
12692 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12693 element == EL_DC_MAGIC_WALL_FULL)
12695 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12696 TEST_DrawLevelField(x, y);
12700 game.magic_wall_active = FALSE;
12705 if (game.light_time_left > 0)
12707 game.light_time_left--;
12709 if (game.light_time_left == 0)
12710 RedrawAllLightSwitchesAndInvisibleElements();
12713 if (game.timegate_time_left > 0)
12715 game.timegate_time_left--;
12717 if (game.timegate_time_left == 0)
12718 CloseAllOpenTimegates();
12721 if (game.lenses_time_left > 0)
12723 game.lenses_time_left--;
12725 if (game.lenses_time_left == 0)
12726 RedrawAllInvisibleElementsForLenses();
12729 if (game.magnify_time_left > 0)
12731 game.magnify_time_left--;
12733 if (game.magnify_time_left == 0)
12734 RedrawAllInvisibleElementsForMagnifier();
12737 for (i = 0; i < MAX_PLAYERS; i++)
12739 struct PlayerInfo *player = &stored_player[i];
12741 if (SHIELD_ON(player))
12743 if (player->shield_deadly_time_left)
12744 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12745 else if (player->shield_normal_time_left)
12746 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12750 #if USE_DELAYED_GFX_REDRAW
12751 SCAN_PLAYFIELD(x, y)
12753 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12755 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12756 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12758 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12759 DrawLevelField(x, y);
12761 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12762 DrawLevelFieldCrumbled(x, y);
12764 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12765 DrawLevelFieldCrumbledNeighbours(x, y);
12767 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12768 DrawTwinkleOnField(x, y);
12771 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12776 PlayAllPlayersSound();
12778 for (i = 0; i < MAX_PLAYERS; i++)
12780 struct PlayerInfo *player = &stored_player[i];
12782 if (player->show_envelope != 0 && (!player->active ||
12783 player->MovPos == 0))
12785 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12787 player->show_envelope = 0;
12791 // use random number generator in every frame to make it less predictable
12792 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12795 mouse_action_last = mouse_action;
12798 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12800 int min_x = x, min_y = y, max_x = x, max_y = y;
12801 int scr_fieldx = getScreenFieldSizeX();
12802 int scr_fieldy = getScreenFieldSizeY();
12805 for (i = 0; i < MAX_PLAYERS; i++)
12807 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12809 if (!stored_player[i].active || &stored_player[i] == player)
12812 min_x = MIN(min_x, jx);
12813 min_y = MIN(min_y, jy);
12814 max_x = MAX(max_x, jx);
12815 max_y = MAX(max_y, jy);
12818 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12821 static boolean AllPlayersInVisibleScreen(void)
12825 for (i = 0; i < MAX_PLAYERS; i++)
12827 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12829 if (!stored_player[i].active)
12832 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12839 void ScrollLevel(int dx, int dy)
12841 int scroll_offset = 2 * TILEX_VAR;
12844 BlitBitmap(drawto_field, drawto_field,
12845 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12846 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12847 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12848 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12849 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12850 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12854 x = (dx == 1 ? BX1 : BX2);
12855 for (y = BY1; y <= BY2; y++)
12856 DrawScreenField(x, y);
12861 y = (dy == 1 ? BY1 : BY2);
12862 for (x = BX1; x <= BX2; x++)
12863 DrawScreenField(x, y);
12866 redraw_mask |= REDRAW_FIELD;
12869 static boolean canFallDown(struct PlayerInfo *player)
12871 int jx = player->jx, jy = player->jy;
12873 return (IN_LEV_FIELD(jx, jy + 1) &&
12874 (IS_FREE(jx, jy + 1) ||
12875 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12876 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12877 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12880 static boolean canPassField(int x, int y, int move_dir)
12882 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12883 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12884 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12885 int nextx = x + dx;
12886 int nexty = y + dy;
12887 int element = Tile[x][y];
12889 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12890 !CAN_MOVE(element) &&
12891 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12892 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12893 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12896 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12898 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12899 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12900 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12904 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12905 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12906 (IS_DIGGABLE(Tile[newx][newy]) ||
12907 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12908 canPassField(newx, newy, move_dir)));
12911 static void CheckGravityMovement(struct PlayerInfo *player)
12913 if (player->gravity && !player->programmed_action)
12915 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12916 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12917 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12918 int jx = player->jx, jy = player->jy;
12919 boolean player_is_moving_to_valid_field =
12920 (!player_is_snapping &&
12921 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12922 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12923 boolean player_can_fall_down = canFallDown(player);
12925 if (player_can_fall_down &&
12926 !player_is_moving_to_valid_field)
12927 player->programmed_action = MV_DOWN;
12931 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12933 return CheckGravityMovement(player);
12935 if (player->gravity && !player->programmed_action)
12937 int jx = player->jx, jy = player->jy;
12938 boolean field_under_player_is_free =
12939 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12940 boolean player_is_standing_on_valid_field =
12941 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12942 (IS_WALKABLE(Tile[jx][jy]) &&
12943 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12945 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12946 player->programmed_action = MV_DOWN;
12951 MovePlayerOneStep()
12952 -----------------------------------------------------------------------------
12953 dx, dy: direction (non-diagonal) to try to move the player to
12954 real_dx, real_dy: direction as read from input device (can be diagonal)
12957 boolean MovePlayerOneStep(struct PlayerInfo *player,
12958 int dx, int dy, int real_dx, int real_dy)
12960 int jx = player->jx, jy = player->jy;
12961 int new_jx = jx + dx, new_jy = jy + dy;
12963 boolean player_can_move = !player->cannot_move;
12965 if (!player->active || (!dx && !dy))
12966 return MP_NO_ACTION;
12968 player->MovDir = (dx < 0 ? MV_LEFT :
12969 dx > 0 ? MV_RIGHT :
12971 dy > 0 ? MV_DOWN : MV_NONE);
12973 if (!IN_LEV_FIELD(new_jx, new_jy))
12974 return MP_NO_ACTION;
12976 if (!player_can_move)
12978 if (player->MovPos == 0)
12980 player->is_moving = FALSE;
12981 player->is_digging = FALSE;
12982 player->is_collecting = FALSE;
12983 player->is_snapping = FALSE;
12984 player->is_pushing = FALSE;
12988 if (!network.enabled && game.centered_player_nr == -1 &&
12989 !AllPlayersInSight(player, new_jx, new_jy))
12990 return MP_NO_ACTION;
12992 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG);
12993 if (can_move != MP_MOVING)
12996 // check if DigField() has caused relocation of the player
12997 if (player->jx != jx || player->jy != jy)
12998 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
13000 StorePlayer[jx][jy] = 0;
13001 player->last_jx = jx;
13002 player->last_jy = jy;
13003 player->jx = new_jx;
13004 player->jy = new_jy;
13005 StorePlayer[new_jx][new_jy] = player->element_nr;
13007 if (player->move_delay_value_next != -1)
13009 player->move_delay_value = player->move_delay_value_next;
13010 player->move_delay_value_next = -1;
13014 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
13016 player->step_counter++;
13018 PlayerVisit[jx][jy] = FrameCounter;
13020 player->is_moving = TRUE;
13023 // should better be called in MovePlayer(), but this breaks some tapes
13024 ScrollPlayer(player, SCROLL_INIT);
13030 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
13032 int jx = player->jx, jy = player->jy;
13033 int old_jx = jx, old_jy = jy;
13034 int moved = MP_NO_ACTION;
13036 if (!player->active)
13041 if (player->MovPos == 0)
13043 player->is_moving = FALSE;
13044 player->is_digging = FALSE;
13045 player->is_collecting = FALSE;
13046 player->is_snapping = FALSE;
13047 player->is_pushing = FALSE;
13053 if (player->move_delay > 0)
13056 player->move_delay = -1; // set to "uninitialized" value
13058 // store if player is automatically moved to next field
13059 player->is_auto_moving = (player->programmed_action != MV_NONE);
13061 // remove the last programmed player action
13062 player->programmed_action = 0;
13064 if (player->MovPos)
13066 // should only happen if pre-1.2 tape recordings are played
13067 // this is only for backward compatibility
13069 int original_move_delay_value = player->move_delay_value;
13072 Debug("game:playing:MovePlayer",
13073 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13077 // scroll remaining steps with finest movement resolution
13078 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13080 while (player->MovPos)
13082 ScrollPlayer(player, SCROLL_GO_ON);
13083 ScrollScreen(NULL, SCROLL_GO_ON);
13085 AdvanceFrameAndPlayerCounters(player->index_nr);
13088 BackToFront_WithFrameDelay(0);
13091 player->move_delay_value = original_move_delay_value;
13094 player->is_active = FALSE;
13096 if (player->last_move_dir & MV_HORIZONTAL)
13098 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13099 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13103 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13104 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13107 if (!moved && !player->is_active)
13109 player->is_moving = FALSE;
13110 player->is_digging = FALSE;
13111 player->is_collecting = FALSE;
13112 player->is_snapping = FALSE;
13113 player->is_pushing = FALSE;
13119 if (moved & MP_MOVING && !ScreenMovPos &&
13120 (player->index_nr == game.centered_player_nr ||
13121 game.centered_player_nr == -1))
13123 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13125 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13127 // actual player has left the screen -- scroll in that direction
13128 if (jx != old_jx) // player has moved horizontally
13129 scroll_x += (jx - old_jx);
13130 else // player has moved vertically
13131 scroll_y += (jy - old_jy);
13135 int offset_raw = game.scroll_delay_value;
13137 if (jx != old_jx) // player has moved horizontally
13139 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13140 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13141 int new_scroll_x = jx - MIDPOSX + offset_x;
13143 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13144 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13145 scroll_x = new_scroll_x;
13147 // don't scroll over playfield boundaries
13148 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13150 // don't scroll more than one field at a time
13151 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13153 // don't scroll against the player's moving direction
13154 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13155 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13156 scroll_x = old_scroll_x;
13158 else // player has moved vertically
13160 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13161 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13162 int new_scroll_y = jy - MIDPOSY + offset_y;
13164 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13165 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13166 scroll_y = new_scroll_y;
13168 // don't scroll over playfield boundaries
13169 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13171 // don't scroll more than one field at a time
13172 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13174 // don't scroll against the player's moving direction
13175 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13176 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13177 scroll_y = old_scroll_y;
13181 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13183 if (!network.enabled && game.centered_player_nr == -1 &&
13184 !AllPlayersInVisibleScreen())
13186 scroll_x = old_scroll_x;
13187 scroll_y = old_scroll_y;
13191 ScrollScreen(player, SCROLL_INIT);
13192 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13197 player->StepFrame = 0;
13199 if (moved & MP_MOVING)
13201 if (old_jx != jx && old_jy == jy)
13202 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13203 else if (old_jx == jx && old_jy != jy)
13204 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13206 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13208 player->last_move_dir = player->MovDir;
13209 player->is_moving = TRUE;
13210 player->is_snapping = FALSE;
13211 player->is_switching = FALSE;
13212 player->is_dropping = FALSE;
13213 player->is_dropping_pressed = FALSE;
13214 player->drop_pressed_delay = 0;
13217 // should better be called here than above, but this breaks some tapes
13218 ScrollPlayer(player, SCROLL_INIT);
13223 CheckGravityMovementWhenNotMoving(player);
13225 player->is_moving = FALSE;
13227 /* at this point, the player is allowed to move, but cannot move right now
13228 (e.g. because of something blocking the way) -- ensure that the player
13229 is also allowed to move in the next frame (in old versions before 3.1.1,
13230 the player was forced to wait again for eight frames before next try) */
13232 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13233 player->move_delay = 0; // allow direct movement in the next frame
13236 if (player->move_delay == -1) // not yet initialized by DigField()
13237 player->move_delay = player->move_delay_value;
13239 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13241 TestIfPlayerTouchesBadThing(jx, jy);
13242 TestIfPlayerTouchesCustomElement(jx, jy);
13245 if (!player->active)
13246 RemovePlayer(player);
13251 void ScrollPlayer(struct PlayerInfo *player, int mode)
13253 int jx = player->jx, jy = player->jy;
13254 int last_jx = player->last_jx, last_jy = player->last_jy;
13255 int move_stepsize = TILEX / player->move_delay_value;
13257 if (!player->active)
13260 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13263 if (mode == SCROLL_INIT)
13265 player->actual_frame_counter.count = FrameCounter;
13266 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13268 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13269 Tile[last_jx][last_jy] == EL_EMPTY)
13271 int last_field_block_delay = 0; // start with no blocking at all
13272 int block_delay_adjustment = player->block_delay_adjustment;
13274 // if player blocks last field, add delay for exactly one move
13275 if (player->block_last_field)
13277 last_field_block_delay += player->move_delay_value;
13279 // when blocking enabled, prevent moving up despite gravity
13280 if (player->gravity && player->MovDir == MV_UP)
13281 block_delay_adjustment = -1;
13284 // add block delay adjustment (also possible when not blocking)
13285 last_field_block_delay += block_delay_adjustment;
13287 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13288 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13291 if (player->MovPos != 0) // player has not yet reached destination
13294 else if (!FrameReached(&player->actual_frame_counter))
13297 if (player->MovPos != 0)
13299 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13300 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13302 // before DrawPlayer() to draw correct player graphic for this case
13303 if (player->MovPos == 0)
13304 CheckGravityMovement(player);
13307 if (player->MovPos == 0) // player reached destination field
13309 if (player->move_delay_reset_counter > 0)
13311 player->move_delay_reset_counter--;
13313 if (player->move_delay_reset_counter == 0)
13315 // continue with normal speed after quickly moving through gate
13316 HALVE_PLAYER_SPEED(player);
13318 // be able to make the next move without delay
13319 player->move_delay = 0;
13323 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13324 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13325 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13326 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13327 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13328 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13329 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13330 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13332 ExitPlayer(player);
13334 if (game.players_still_needed == 0 &&
13335 (game.friends_still_needed == 0 ||
13336 IS_SP_ELEMENT(Tile[jx][jy])))
13340 player->last_jx = jx;
13341 player->last_jy = jy;
13343 // this breaks one level: "machine", level 000
13345 int move_direction = player->MovDir;
13346 int enter_side = MV_DIR_OPPOSITE(move_direction);
13347 int leave_side = move_direction;
13348 int old_jx = last_jx;
13349 int old_jy = last_jy;
13350 int old_element = Tile[old_jx][old_jy];
13351 int new_element = Tile[jx][jy];
13353 if (IS_CUSTOM_ELEMENT(old_element))
13354 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13356 player->index_bit, leave_side);
13358 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13359 CE_PLAYER_LEAVES_X,
13360 player->index_bit, leave_side);
13362 // needed because pushed element has not yet reached its destination,
13363 // so it would trigger a change event at its previous field location
13364 if (!player->is_pushing)
13366 if (IS_CUSTOM_ELEMENT(new_element))
13367 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13368 player->index_bit, enter_side);
13370 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13371 CE_PLAYER_ENTERS_X,
13372 player->index_bit, enter_side);
13375 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13376 CE_MOVE_OF_X, move_direction);
13379 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13381 TestIfPlayerTouchesBadThing(jx, jy);
13382 TestIfPlayerTouchesCustomElement(jx, jy);
13384 // needed because pushed element has not yet reached its destination,
13385 // so it would trigger a change event at its previous field location
13386 if (!player->is_pushing)
13387 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13389 if (level.finish_dig_collect &&
13390 (player->is_digging || player->is_collecting))
13392 int last_element = player->last_removed_element;
13393 int move_direction = player->MovDir;
13394 int enter_side = MV_DIR_OPPOSITE(move_direction);
13395 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13396 CE_PLAYER_COLLECTS_X);
13398 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13399 player->index_bit, enter_side);
13401 player->last_removed_element = EL_UNDEFINED;
13404 if (!player->active)
13405 RemovePlayer(player);
13408 if (level.use_step_counter)
13409 CheckLevelTime_StepCounter();
13411 if (tape.single_step && tape.recording && !tape.pausing &&
13412 !player->programmed_action)
13413 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13415 if (!player->programmed_action)
13416 CheckSaveEngineSnapshot(player);
13420 void ScrollScreen(struct PlayerInfo *player, int mode)
13422 static DelayCounter screen_frame_counter = { 0 };
13424 if (mode == SCROLL_INIT)
13426 // set scrolling step size according to actual player's moving speed
13427 ScrollStepSize = TILEX / player->move_delay_value;
13429 screen_frame_counter.count = FrameCounter;
13430 screen_frame_counter.value = 1;
13432 ScreenMovDir = player->MovDir;
13433 ScreenMovPos = player->MovPos;
13434 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13437 else if (!FrameReached(&screen_frame_counter))
13442 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13443 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13444 redraw_mask |= REDRAW_FIELD;
13447 ScreenMovDir = MV_NONE;
13450 void CheckNextToConditions(int x, int y)
13452 int element = Tile[x][y];
13454 if (IS_PLAYER(x, y))
13455 TestIfPlayerNextToCustomElement(x, y);
13457 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13458 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13459 TestIfElementNextToCustomElement(x, y);
13462 void TestIfPlayerNextToCustomElement(int x, int y)
13464 struct XY *xy = xy_topdown;
13465 static int trigger_sides[4][2] =
13467 // center side border side
13468 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13469 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13470 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13471 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13475 if (!IS_PLAYER(x, y))
13478 struct PlayerInfo *player = PLAYERINFO(x, y);
13480 if (player->is_moving)
13483 for (i = 0; i < NUM_DIRECTIONS; i++)
13485 int xx = x + xy[i].x;
13486 int yy = y + xy[i].y;
13487 int border_side = trigger_sides[i][1];
13488 int border_element;
13490 if (!IN_LEV_FIELD(xx, yy))
13493 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13494 continue; // center and border element not connected
13496 border_element = Tile[xx][yy];
13498 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13499 player->index_bit, border_side);
13500 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13501 CE_PLAYER_NEXT_TO_X,
13502 player->index_bit, border_side);
13504 /* use player element that is initially defined in the level playfield,
13505 not the player element that corresponds to the runtime player number
13506 (example: a level that contains EL_PLAYER_3 as the only player would
13507 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13509 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13510 CE_NEXT_TO_X, border_side);
13514 void TestIfPlayerTouchesCustomElement(int x, int y)
13516 struct XY *xy = xy_topdown;
13517 static int trigger_sides[4][2] =
13519 // center side border side
13520 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13521 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13522 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13523 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13525 static int touch_dir[4] =
13527 MV_LEFT | MV_RIGHT,
13532 int center_element = Tile[x][y]; // should always be non-moving!
13535 for (i = 0; i < NUM_DIRECTIONS; i++)
13537 int xx = x + xy[i].x;
13538 int yy = y + xy[i].y;
13539 int center_side = trigger_sides[i][0];
13540 int border_side = trigger_sides[i][1];
13541 int border_element;
13543 if (!IN_LEV_FIELD(xx, yy))
13546 if (IS_PLAYER(x, y)) // player found at center element
13548 struct PlayerInfo *player = PLAYERINFO(x, y);
13550 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13551 border_element = Tile[xx][yy]; // may be moving!
13552 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13553 border_element = Tile[xx][yy];
13554 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13555 border_element = MovingOrBlocked2Element(xx, yy);
13557 continue; // center and border element do not touch
13559 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13560 player->index_bit, border_side);
13561 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13562 CE_PLAYER_TOUCHES_X,
13563 player->index_bit, border_side);
13566 /* use player element that is initially defined in the level playfield,
13567 not the player element that corresponds to the runtime player number
13568 (example: a level that contains EL_PLAYER_3 as the only player would
13569 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13570 int player_element = PLAYERINFO(x, y)->initial_element;
13572 // as element "X" is the player here, check opposite (center) side
13573 CheckElementChangeBySide(xx, yy, border_element, player_element,
13574 CE_TOUCHING_X, center_side);
13577 else if (IS_PLAYER(xx, yy)) // player found at border element
13579 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13581 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13583 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13584 continue; // center and border element do not touch
13587 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13588 player->index_bit, center_side);
13589 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13590 CE_PLAYER_TOUCHES_X,
13591 player->index_bit, center_side);
13594 /* use player element that is initially defined in the level playfield,
13595 not the player element that corresponds to the runtime player number
13596 (example: a level that contains EL_PLAYER_3 as the only player would
13597 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13598 int player_element = PLAYERINFO(xx, yy)->initial_element;
13600 // as element "X" is the player here, check opposite (border) side
13601 CheckElementChangeBySide(x, y, center_element, player_element,
13602 CE_TOUCHING_X, border_side);
13610 void TestIfElementNextToCustomElement(int x, int y)
13612 struct XY *xy = xy_topdown;
13613 static int trigger_sides[4][2] =
13615 // center side border side
13616 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13617 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13618 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13619 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13621 int center_element = Tile[x][y]; // should always be non-moving!
13624 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13627 for (i = 0; i < NUM_DIRECTIONS; i++)
13629 int xx = x + xy[i].x;
13630 int yy = y + xy[i].y;
13631 int border_side = trigger_sides[i][1];
13632 int border_element;
13634 if (!IN_LEV_FIELD(xx, yy))
13637 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13638 continue; // center and border element not connected
13640 border_element = Tile[xx][yy];
13642 // check for change of center element (but change it only once)
13643 if (CheckElementChangeBySide(x, y, center_element, border_element,
13644 CE_NEXT_TO_X, border_side))
13649 void TestIfElementTouchesCustomElement(int x, int y)
13651 struct XY *xy = xy_topdown;
13652 static int trigger_sides[4][2] =
13654 // center side border side
13655 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13656 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13657 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13658 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13660 static int touch_dir[4] =
13662 MV_LEFT | MV_RIGHT,
13667 boolean change_center_element = FALSE;
13668 int center_element = Tile[x][y]; // should always be non-moving!
13669 int border_element_old[NUM_DIRECTIONS];
13672 for (i = 0; i < NUM_DIRECTIONS; i++)
13674 int xx = x + xy[i].x;
13675 int yy = y + xy[i].y;
13676 int border_element;
13678 border_element_old[i] = -1;
13680 if (!IN_LEV_FIELD(xx, yy))
13683 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13684 border_element = Tile[xx][yy]; // may be moving!
13685 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13686 border_element = Tile[xx][yy];
13687 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13688 border_element = MovingOrBlocked2Element(xx, yy);
13690 continue; // center and border element do not touch
13692 border_element_old[i] = border_element;
13695 for (i = 0; i < NUM_DIRECTIONS; i++)
13697 int xx = x + xy[i].x;
13698 int yy = y + xy[i].y;
13699 int center_side = trigger_sides[i][0];
13700 int border_element = border_element_old[i];
13702 if (border_element == -1)
13705 // check for change of border element
13706 CheckElementChangeBySide(xx, yy, border_element, center_element,
13707 CE_TOUCHING_X, center_side);
13709 // (center element cannot be player, so we don't have to check this here)
13712 for (i = 0; i < NUM_DIRECTIONS; i++)
13714 int xx = x + xy[i].x;
13715 int yy = y + xy[i].y;
13716 int border_side = trigger_sides[i][1];
13717 int border_element = border_element_old[i];
13719 if (border_element == -1)
13722 // check for change of center element (but change it only once)
13723 if (!change_center_element)
13724 change_center_element =
13725 CheckElementChangeBySide(x, y, center_element, border_element,
13726 CE_TOUCHING_X, border_side);
13728 if (IS_PLAYER(xx, yy))
13730 /* use player element that is initially defined in the level playfield,
13731 not the player element that corresponds to the runtime player number
13732 (example: a level that contains EL_PLAYER_3 as the only player would
13733 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13734 int player_element = PLAYERINFO(xx, yy)->initial_element;
13736 // as element "X" is the player here, check opposite (border) side
13737 CheckElementChangeBySide(x, y, center_element, player_element,
13738 CE_TOUCHING_X, border_side);
13743 void TestIfElementHitsCustomElement(int x, int y, int direction)
13745 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13746 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13747 int hitx = x + dx, hity = y + dy;
13748 int hitting_element = Tile[x][y];
13749 int touched_element;
13751 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13754 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13755 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13757 if (IN_LEV_FIELD(hitx, hity))
13759 int opposite_direction = MV_DIR_OPPOSITE(direction);
13760 int hitting_side = direction;
13761 int touched_side = opposite_direction;
13762 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13763 MovDir[hitx][hity] != direction ||
13764 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13770 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13771 CE_HITTING_X, touched_side);
13773 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13774 CE_HIT_BY_X, hitting_side);
13776 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13777 CE_HIT_BY_SOMETHING, opposite_direction);
13779 if (IS_PLAYER(hitx, hity))
13781 /* use player element that is initially defined in the level playfield,
13782 not the player element that corresponds to the runtime player number
13783 (example: a level that contains EL_PLAYER_3 as the only player would
13784 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13785 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13787 CheckElementChangeBySide(x, y, hitting_element, player_element,
13788 CE_HITTING_X, touched_side);
13793 // "hitting something" is also true when hitting the playfield border
13794 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13795 CE_HITTING_SOMETHING, direction);
13798 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13800 int i, kill_x = -1, kill_y = -1;
13802 int bad_element = -1;
13803 struct XY *test_xy = xy_topdown;
13804 static int test_dir[4] =
13812 for (i = 0; i < NUM_DIRECTIONS; i++)
13814 int test_x, test_y, test_move_dir, test_element;
13816 test_x = good_x + test_xy[i].x;
13817 test_y = good_y + test_xy[i].y;
13819 if (!IN_LEV_FIELD(test_x, test_y))
13823 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13825 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13827 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13828 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13830 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13831 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13835 bad_element = test_element;
13841 if (kill_x != -1 || kill_y != -1)
13843 if (IS_PLAYER(good_x, good_y))
13845 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13847 if (player->shield_deadly_time_left > 0 &&
13848 !IS_INDESTRUCTIBLE(bad_element))
13849 Bang(kill_x, kill_y);
13850 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13851 KillPlayer(player);
13854 Bang(good_x, good_y);
13858 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13860 int i, kill_x = -1, kill_y = -1;
13861 int bad_element = Tile[bad_x][bad_y];
13862 struct XY *test_xy = xy_topdown;
13863 static int touch_dir[4] =
13865 MV_LEFT | MV_RIGHT,
13870 static int test_dir[4] =
13878 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13881 for (i = 0; i < NUM_DIRECTIONS; i++)
13883 int test_x, test_y, test_move_dir, test_element;
13885 test_x = bad_x + test_xy[i].x;
13886 test_y = bad_y + test_xy[i].y;
13888 if (!IN_LEV_FIELD(test_x, test_y))
13892 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13894 test_element = Tile[test_x][test_y];
13896 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13897 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13899 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13900 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13902 // good thing is player or penguin that does not move away
13903 if (IS_PLAYER(test_x, test_y))
13905 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13907 if (bad_element == EL_ROBOT && player->is_moving)
13908 continue; // robot does not kill player if he is moving
13910 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13912 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13913 continue; // center and border element do not touch
13921 else if (test_element == EL_PENGUIN)
13931 if (kill_x != -1 || kill_y != -1)
13933 if (IS_PLAYER(kill_x, kill_y))
13935 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13937 if (player->shield_deadly_time_left > 0 &&
13938 !IS_INDESTRUCTIBLE(bad_element))
13939 Bang(bad_x, bad_y);
13940 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13941 KillPlayer(player);
13944 Bang(kill_x, kill_y);
13948 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13950 int bad_element = Tile[bad_x][bad_y];
13951 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13952 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13953 int test_x = bad_x + dx, test_y = bad_y + dy;
13954 int test_move_dir, test_element;
13955 int kill_x = -1, kill_y = -1;
13957 if (!IN_LEV_FIELD(test_x, test_y))
13961 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13963 test_element = Tile[test_x][test_y];
13965 if (test_move_dir != bad_move_dir)
13967 // good thing can be player or penguin that does not move away
13968 if (IS_PLAYER(test_x, test_y))
13970 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13972 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13973 player as being hit when he is moving towards the bad thing, because
13974 the "get hit by" condition would be lost after the player stops) */
13975 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13976 return; // player moves away from bad thing
13981 else if (test_element == EL_PENGUIN)
13988 if (kill_x != -1 || kill_y != -1)
13990 if (IS_PLAYER(kill_x, kill_y))
13992 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13994 if (player->shield_deadly_time_left > 0 &&
13995 !IS_INDESTRUCTIBLE(bad_element))
13996 Bang(bad_x, bad_y);
13997 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13998 KillPlayer(player);
14001 Bang(kill_x, kill_y);
14005 void TestIfPlayerTouchesBadThing(int x, int y)
14007 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14010 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
14012 TestIfGoodThingHitsBadThing(x, y, move_dir);
14015 void TestIfBadThingTouchesPlayer(int x, int y)
14017 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14020 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
14022 TestIfBadThingHitsGoodThing(x, y, move_dir);
14025 void TestIfFriendTouchesBadThing(int x, int y)
14027 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14030 void TestIfBadThingTouchesFriend(int x, int y)
14032 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14035 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
14037 int i, kill_x = bad_x, kill_y = bad_y;
14038 struct XY *xy = xy_topdown;
14040 for (i = 0; i < NUM_DIRECTIONS; i++)
14044 x = bad_x + xy[i].x;
14045 y = bad_y + xy[i].y;
14046 if (!IN_LEV_FIELD(x, y))
14049 element = Tile[x][y];
14050 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14051 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14059 if (kill_x != bad_x || kill_y != bad_y)
14060 Bang(bad_x, bad_y);
14063 void KillPlayer(struct PlayerInfo *player)
14065 int jx = player->jx, jy = player->jy;
14067 if (!player->active)
14071 Debug("game:playing:KillPlayer",
14072 "0: killed == %d, active == %d, reanimated == %d",
14073 player->killed, player->active, player->reanimated);
14076 /* the following code was introduced to prevent an infinite loop when calling
14078 -> CheckTriggeredElementChangeExt()
14079 -> ExecuteCustomElementAction()
14081 -> (infinitely repeating the above sequence of function calls)
14082 which occurs when killing the player while having a CE with the setting
14083 "kill player X when explosion of <player X>"; the solution using a new
14084 field "player->killed" was chosen for backwards compatibility, although
14085 clever use of the fields "player->active" etc. would probably also work */
14087 if (player->killed)
14091 player->killed = TRUE;
14093 // remove accessible field at the player's position
14094 RemoveField(jx, jy);
14096 // deactivate shield (else Bang()/Explode() would not work right)
14097 player->shield_normal_time_left = 0;
14098 player->shield_deadly_time_left = 0;
14101 Debug("game:playing:KillPlayer",
14102 "1: killed == %d, active == %d, reanimated == %d",
14103 player->killed, player->active, player->reanimated);
14109 Debug("game:playing:KillPlayer",
14110 "2: killed == %d, active == %d, reanimated == %d",
14111 player->killed, player->active, player->reanimated);
14114 if (player->reanimated) // killed player may have been reanimated
14115 player->killed = player->reanimated = FALSE;
14117 BuryPlayer(player);
14120 static void KillPlayerUnlessEnemyProtected(int x, int y)
14122 if (!PLAYER_ENEMY_PROTECTED(x, y))
14123 KillPlayer(PLAYERINFO(x, y));
14126 static void KillPlayerUnlessExplosionProtected(int x, int y)
14128 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14129 KillPlayer(PLAYERINFO(x, y));
14132 void BuryPlayer(struct PlayerInfo *player)
14134 int jx = player->jx, jy = player->jy;
14136 if (!player->active)
14139 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14141 RemovePlayer(player);
14143 player->buried = TRUE;
14145 if (game.all_players_gone)
14146 game.GameOver = TRUE;
14149 void RemovePlayer(struct PlayerInfo *player)
14151 int jx = player->jx, jy = player->jy;
14152 int i, found = FALSE;
14154 player->present = FALSE;
14155 player->active = FALSE;
14157 // required for some CE actions (even if the player is not active anymore)
14158 player->MovPos = 0;
14160 if (!ExplodeField[jx][jy])
14161 StorePlayer[jx][jy] = 0;
14163 if (player->is_moving)
14164 TEST_DrawLevelField(player->last_jx, player->last_jy);
14166 for (i = 0; i < MAX_PLAYERS; i++)
14167 if (stored_player[i].active)
14172 game.all_players_gone = TRUE;
14173 game.GameOver = TRUE;
14176 game.exit_x = game.robot_wheel_x = jx;
14177 game.exit_y = game.robot_wheel_y = jy;
14180 void ExitPlayer(struct PlayerInfo *player)
14182 DrawPlayer(player); // needed here only to cleanup last field
14183 RemovePlayer(player);
14185 if (game.players_still_needed > 0)
14186 game.players_still_needed--;
14189 static void SetFieldForSnapping(int x, int y, int element, int direction,
14190 int player_index_bit)
14192 struct ElementInfo *ei = &element_info[element];
14193 int direction_bit = MV_DIR_TO_BIT(direction);
14194 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14195 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14196 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14198 Tile[x][y] = EL_ELEMENT_SNAPPING;
14199 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14200 MovDir[x][y] = direction;
14201 Store[x][y] = element;
14202 Store2[x][y] = player_index_bit;
14204 ResetGfxAnimation(x, y);
14206 GfxElement[x][y] = element;
14207 GfxAction[x][y] = action;
14208 GfxDir[x][y] = direction;
14209 GfxFrame[x][y] = -1;
14212 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14213 int player_index_bit)
14215 TestIfElementTouchesCustomElement(x, y); // for empty space
14217 if (level.finish_dig_collect)
14219 int dig_side = MV_DIR_OPPOSITE(direction);
14220 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14221 CE_PLAYER_COLLECTS_X);
14223 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14224 player_index_bit, dig_side);
14225 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14226 player_index_bit, dig_side);
14231 =============================================================================
14232 checkDiagonalPushing()
14233 -----------------------------------------------------------------------------
14234 check if diagonal input device direction results in pushing of object
14235 (by checking if the alternative direction is walkable, diggable, ...)
14236 =============================================================================
14239 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14240 int x, int y, int real_dx, int real_dy)
14242 int jx, jy, dx, dy, xx, yy;
14244 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14247 // diagonal direction: check alternative direction
14252 xx = jx + (dx == 0 ? real_dx : 0);
14253 yy = jy + (dy == 0 ? real_dy : 0);
14255 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14259 =============================================================================
14261 -----------------------------------------------------------------------------
14262 x, y: field next to player (non-diagonal) to try to dig to
14263 real_dx, real_dy: direction as read from input device (can be diagonal)
14264 =============================================================================
14267 static int DigField(struct PlayerInfo *player,
14268 int oldx, int oldy, int x, int y,
14269 int real_dx, int real_dy, int mode)
14271 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14272 boolean player_was_pushing = player->is_pushing;
14273 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14274 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14275 int jx = oldx, jy = oldy;
14276 int dx = x - jx, dy = y - jy;
14277 int nextx = x + dx, nexty = y + dy;
14278 int move_direction = (dx == -1 ? MV_LEFT :
14279 dx == +1 ? MV_RIGHT :
14281 dy == +1 ? MV_DOWN : MV_NONE);
14282 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14283 int dig_side = MV_DIR_OPPOSITE(move_direction);
14284 int old_element = Tile[jx][jy];
14285 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14288 if (is_player) // function can also be called by EL_PENGUIN
14290 if (player->MovPos == 0)
14292 player->is_digging = FALSE;
14293 player->is_collecting = FALSE;
14296 if (player->MovPos == 0) // last pushing move finished
14297 player->is_pushing = FALSE;
14299 if (mode == DF_NO_PUSH) // player just stopped pushing
14301 player->is_switching = FALSE;
14302 player->push_delay = -1;
14304 return MP_NO_ACTION;
14307 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14308 old_element = Back[jx][jy];
14310 // in case of element dropped at player position, check background
14311 else if (Back[jx][jy] != EL_EMPTY &&
14312 game.engine_version >= VERSION_IDENT(2,2,0,0))
14313 old_element = Back[jx][jy];
14315 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14316 return MP_NO_ACTION; // field has no opening in this direction
14318 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction))
14319 return MP_NO_ACTION; // field has no opening in this direction
14321 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14325 Tile[jx][jy] = player->artwork_element;
14326 InitMovingField(jx, jy, MV_DOWN);
14327 Store[jx][jy] = EL_ACID;
14328 ContinueMoving(jx, jy);
14329 BuryPlayer(player);
14331 return MP_DONT_RUN_INTO;
14334 if (player_can_move && DONT_RUN_INTO(element))
14336 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14338 return MP_DONT_RUN_INTO;
14341 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14342 return MP_NO_ACTION;
14344 collect_count = element_info[element].collect_count_initial;
14346 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14347 return MP_NO_ACTION;
14349 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14350 player_can_move = player_can_move_or_snap;
14352 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14353 game.engine_version >= VERSION_IDENT(2,2,0,0))
14355 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14356 player->index_bit, dig_side);
14357 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14358 player->index_bit, dig_side);
14360 if (element == EL_DC_LANDMINE)
14363 if (Tile[x][y] != element) // field changed by snapping
14366 return MP_NO_ACTION;
14369 if (player->gravity && is_player && !player->is_auto_moving &&
14370 canFallDown(player) && move_direction != MV_DOWN &&
14371 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14372 return MP_NO_ACTION; // player cannot walk here due to gravity
14374 if (player_can_move &&
14375 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14377 int sound_element = SND_ELEMENT(element);
14378 int sound_action = ACTION_WALKING;
14380 if (IS_RND_GATE(element))
14382 if (!player->key[RND_GATE_NR(element)])
14383 return MP_NO_ACTION;
14385 else if (IS_RND_GATE_GRAY(element))
14387 if (!player->key[RND_GATE_GRAY_NR(element)])
14388 return MP_NO_ACTION;
14390 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14392 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14393 return MP_NO_ACTION;
14395 else if (element == EL_EXIT_OPEN ||
14396 element == EL_EM_EXIT_OPEN ||
14397 element == EL_EM_EXIT_OPENING ||
14398 element == EL_STEEL_EXIT_OPEN ||
14399 element == EL_EM_STEEL_EXIT_OPEN ||
14400 element == EL_EM_STEEL_EXIT_OPENING ||
14401 element == EL_SP_EXIT_OPEN ||
14402 element == EL_SP_EXIT_OPENING)
14404 sound_action = ACTION_PASSING; // player is passing exit
14406 else if (element == EL_EMPTY)
14408 sound_action = ACTION_MOVING; // nothing to walk on
14411 // play sound from background or player, whatever is available
14412 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14413 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14415 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14417 else if (player_can_move &&
14418 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14420 if (!ACCESS_FROM(element, opposite_direction))
14421 return MP_NO_ACTION; // field not accessible from this direction
14423 if (CAN_MOVE(element)) // only fixed elements can be passed!
14424 return MP_NO_ACTION;
14426 if (IS_EM_GATE(element))
14428 if (!player->key[EM_GATE_NR(element)])
14429 return MP_NO_ACTION;
14431 else if (IS_EM_GATE_GRAY(element))
14433 if (!player->key[EM_GATE_GRAY_NR(element)])
14434 return MP_NO_ACTION;
14436 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14438 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14439 return MP_NO_ACTION;
14441 else if (IS_EMC_GATE(element))
14443 if (!player->key[EMC_GATE_NR(element)])
14444 return MP_NO_ACTION;
14446 else if (IS_EMC_GATE_GRAY(element))
14448 if (!player->key[EMC_GATE_GRAY_NR(element)])
14449 return MP_NO_ACTION;
14451 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14453 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14454 return MP_NO_ACTION;
14456 else if (element == EL_DC_GATE_WHITE ||
14457 element == EL_DC_GATE_WHITE_GRAY ||
14458 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14460 if (player->num_white_keys == 0)
14461 return MP_NO_ACTION;
14463 player->num_white_keys--;
14465 else if (IS_SP_PORT(element))
14467 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14468 element == EL_SP_GRAVITY_PORT_RIGHT ||
14469 element == EL_SP_GRAVITY_PORT_UP ||
14470 element == EL_SP_GRAVITY_PORT_DOWN)
14471 player->gravity = !player->gravity;
14472 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14473 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14474 element == EL_SP_GRAVITY_ON_PORT_UP ||
14475 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14476 player->gravity = TRUE;
14477 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14478 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14479 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14480 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14481 player->gravity = FALSE;
14484 // automatically move to the next field with double speed
14485 player->programmed_action = move_direction;
14487 if (player->move_delay_reset_counter == 0)
14489 player->move_delay_reset_counter = 2; // two double speed steps
14491 DOUBLE_PLAYER_SPEED(player);
14494 PlayLevelSoundAction(x, y, ACTION_PASSING);
14496 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14500 if (mode != DF_SNAP)
14502 GfxElement[x][y] = GFX_ELEMENT(element);
14503 player->is_digging = TRUE;
14506 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14508 // use old behaviour for old levels (digging)
14509 if (!level.finish_dig_collect)
14511 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14512 player->index_bit, dig_side);
14514 // if digging triggered player relocation, finish digging tile
14515 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14516 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14519 if (mode == DF_SNAP)
14521 if (level.block_snap_field)
14522 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14524 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14526 // use old behaviour for old levels (snapping)
14527 if (!level.finish_dig_collect)
14528 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14529 player->index_bit, dig_side);
14532 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14536 if (is_player && mode != DF_SNAP)
14538 GfxElement[x][y] = element;
14539 player->is_collecting = TRUE;
14542 if (element == EL_SPEED_PILL)
14544 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14546 else if (element == EL_EXTRA_TIME && level.time > 0)
14548 TimeLeft += level.extra_time;
14550 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14552 DisplayGameControlValues();
14554 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14556 int shield_time = (element == EL_SHIELD_DEADLY ?
14557 level.shield_deadly_time :
14558 level.shield_normal_time);
14560 player->shield_normal_time_left += shield_time;
14561 if (element == EL_SHIELD_DEADLY)
14562 player->shield_deadly_time_left += shield_time;
14564 else if (element == EL_DYNAMITE ||
14565 element == EL_EM_DYNAMITE ||
14566 element == EL_SP_DISK_RED)
14568 if (player->inventory_size < MAX_INVENTORY_SIZE)
14569 player->inventory_element[player->inventory_size++] = element;
14571 DrawGameDoorValues();
14573 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14575 player->dynabomb_count++;
14576 player->dynabombs_left++;
14578 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14580 player->dynabomb_size++;
14582 else if (element == EL_DYNABOMB_INCREASE_POWER)
14584 player->dynabomb_xl = TRUE;
14586 else if (IS_KEY(element))
14588 player->key[KEY_NR(element)] = TRUE;
14590 DrawGameDoorValues();
14592 else if (element == EL_DC_KEY_WHITE)
14594 player->num_white_keys++;
14596 // display white keys?
14597 // DrawGameDoorValues();
14599 else if (IS_ENVELOPE(element))
14601 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14603 if (!wait_for_snapping)
14604 player->show_envelope = element;
14606 else if (element == EL_EMC_LENSES)
14608 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14610 RedrawAllInvisibleElementsForLenses();
14612 else if (element == EL_EMC_MAGNIFIER)
14614 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14616 RedrawAllInvisibleElementsForMagnifier();
14618 else if (IS_DROPPABLE(element) ||
14619 IS_THROWABLE(element)) // can be collected and dropped
14623 if (collect_count == 0)
14624 player->inventory_infinite_element = element;
14626 for (i = 0; i < collect_count; i++)
14627 if (player->inventory_size < MAX_INVENTORY_SIZE)
14628 player->inventory_element[player->inventory_size++] = element;
14630 DrawGameDoorValues();
14632 else if (collect_count > 0)
14634 game.gems_still_needed -= collect_count;
14635 if (game.gems_still_needed < 0)
14636 game.gems_still_needed = 0;
14638 game.snapshot.collected_item = TRUE;
14640 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14642 DisplayGameControlValues();
14645 RaiseScoreElement(element);
14646 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14648 // use old behaviour for old levels (collecting)
14649 if (!level.finish_dig_collect && is_player)
14651 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14652 player->index_bit, dig_side);
14654 // if collecting triggered player relocation, finish collecting tile
14655 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14656 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14659 if (mode == DF_SNAP)
14661 if (level.block_snap_field)
14662 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14664 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14666 // use old behaviour for old levels (snapping)
14667 if (!level.finish_dig_collect)
14668 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14669 player->index_bit, dig_side);
14672 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14674 if (mode == DF_SNAP && element != EL_BD_ROCK)
14675 return MP_NO_ACTION;
14677 if (CAN_FALL(element) && dy)
14678 return MP_NO_ACTION;
14680 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14681 !(element == EL_SPRING && level.use_spring_bug))
14682 return MP_NO_ACTION;
14684 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14685 ((move_direction & MV_VERTICAL &&
14686 ((element_info[element].move_pattern & MV_LEFT &&
14687 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14688 (element_info[element].move_pattern & MV_RIGHT &&
14689 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14690 (move_direction & MV_HORIZONTAL &&
14691 ((element_info[element].move_pattern & MV_UP &&
14692 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14693 (element_info[element].move_pattern & MV_DOWN &&
14694 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14695 return MP_NO_ACTION;
14697 // do not push elements already moving away faster than player
14698 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14699 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14700 return MP_NO_ACTION;
14702 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14704 if (player->push_delay_value == -1 || !player_was_pushing)
14705 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14707 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14709 if (player->push_delay_value == -1)
14710 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14712 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14714 if (!player->is_pushing)
14715 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14718 player->is_pushing = TRUE;
14719 player->is_active = TRUE;
14721 if (!(IN_LEV_FIELD(nextx, nexty) &&
14722 (IS_FREE(nextx, nexty) ||
14723 (IS_SB_ELEMENT(element) &&
14724 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14725 (IS_CUSTOM_ELEMENT(element) &&
14726 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14727 return MP_NO_ACTION;
14729 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14730 return MP_NO_ACTION;
14732 if (player->push_delay == -1) // new pushing; restart delay
14733 player->push_delay = 0;
14735 if (player->push_delay < player->push_delay_value &&
14736 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14737 element != EL_SPRING && element != EL_BALLOON)
14739 // make sure that there is no move delay before next try to push
14740 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14741 player->move_delay = 0;
14743 return MP_NO_ACTION;
14746 if (IS_CUSTOM_ELEMENT(element) &&
14747 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14749 if (!DigFieldByCE(nextx, nexty, element))
14750 return MP_NO_ACTION;
14753 if (IS_SB_ELEMENT(element))
14755 boolean sokoban_task_solved = FALSE;
14757 if (element == EL_SOKOBAN_FIELD_FULL)
14759 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14761 IncrementSokobanFieldsNeeded();
14762 IncrementSokobanObjectsNeeded();
14765 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14767 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14769 DecrementSokobanFieldsNeeded();
14770 DecrementSokobanObjectsNeeded();
14772 // sokoban object was pushed from empty field to sokoban field
14773 if (Back[x][y] == EL_EMPTY)
14774 sokoban_task_solved = TRUE;
14777 Tile[x][y] = EL_SOKOBAN_OBJECT;
14779 if (Back[x][y] == Back[nextx][nexty])
14780 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14781 else if (Back[x][y] != 0)
14782 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14785 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14788 if (sokoban_task_solved &&
14789 game.sokoban_fields_still_needed == 0 &&
14790 game.sokoban_objects_still_needed == 0 &&
14791 level.auto_exit_sokoban)
14793 game.players_still_needed = 0;
14797 PlaySound(SND_GAME_SOKOBAN_SOLVING);
14801 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14803 InitMovingField(x, y, move_direction);
14804 GfxAction[x][y] = ACTION_PUSHING;
14806 if (mode == DF_SNAP)
14807 ContinueMoving(x, y);
14809 MovPos[x][y] = (dx != 0 ? dx : dy);
14811 Pushed[x][y] = TRUE;
14812 Pushed[nextx][nexty] = TRUE;
14814 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14815 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14817 player->push_delay_value = -1; // get new value later
14819 // check for element change _after_ element has been pushed
14820 if (game.use_change_when_pushing_bug)
14822 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14823 player->index_bit, dig_side);
14824 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14825 player->index_bit, dig_side);
14828 else if (IS_SWITCHABLE(element))
14830 if (PLAYER_SWITCHING(player, x, y))
14832 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14833 player->index_bit, dig_side);
14838 player->is_switching = TRUE;
14839 player->switch_x = x;
14840 player->switch_y = y;
14842 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14844 if (element == EL_ROBOT_WHEEL)
14846 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14848 game.robot_wheel_x = x;
14849 game.robot_wheel_y = y;
14850 game.robot_wheel_active = TRUE;
14852 TEST_DrawLevelField(x, y);
14854 else if (element == EL_SP_TERMINAL)
14858 SCAN_PLAYFIELD(xx, yy)
14860 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14864 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14866 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14868 ResetGfxAnimation(xx, yy);
14869 TEST_DrawLevelField(xx, yy);
14873 else if (IS_BELT_SWITCH(element))
14875 ToggleBeltSwitch(x, y);
14877 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14878 element == EL_SWITCHGATE_SWITCH_DOWN ||
14879 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14880 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14882 ToggleSwitchgateSwitch();
14884 else if (element == EL_LIGHT_SWITCH ||
14885 element == EL_LIGHT_SWITCH_ACTIVE)
14887 ToggleLightSwitch(x, y);
14889 else if (element == EL_TIMEGATE_SWITCH ||
14890 element == EL_DC_TIMEGATE_SWITCH)
14892 ActivateTimegateSwitch(x, y);
14894 else if (element == EL_BALLOON_SWITCH_LEFT ||
14895 element == EL_BALLOON_SWITCH_RIGHT ||
14896 element == EL_BALLOON_SWITCH_UP ||
14897 element == EL_BALLOON_SWITCH_DOWN ||
14898 element == EL_BALLOON_SWITCH_NONE ||
14899 element == EL_BALLOON_SWITCH_ANY)
14901 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14902 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14903 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14904 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14905 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14908 else if (element == EL_LAMP)
14910 Tile[x][y] = EL_LAMP_ACTIVE;
14911 game.lights_still_needed--;
14913 ResetGfxAnimation(x, y);
14914 TEST_DrawLevelField(x, y);
14916 else if (element == EL_TIME_ORB_FULL)
14918 Tile[x][y] = EL_TIME_ORB_EMPTY;
14920 if (level.time > 0 || level.use_time_orb_bug)
14922 TimeLeft += level.time_orb_time;
14923 game.no_level_time_limit = FALSE;
14925 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14927 DisplayGameControlValues();
14930 ResetGfxAnimation(x, y);
14931 TEST_DrawLevelField(x, y);
14933 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14934 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14938 game.ball_active = !game.ball_active;
14940 SCAN_PLAYFIELD(xx, yy)
14942 int e = Tile[xx][yy];
14944 if (game.ball_active)
14946 if (e == EL_EMC_MAGIC_BALL)
14947 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14948 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14949 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14953 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14954 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14955 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14956 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14961 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14962 player->index_bit, dig_side);
14964 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14965 player->index_bit, dig_side);
14967 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14968 player->index_bit, dig_side);
14974 if (!PLAYER_SWITCHING(player, x, y))
14976 player->is_switching = TRUE;
14977 player->switch_x = x;
14978 player->switch_y = y;
14980 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14981 player->index_bit, dig_side);
14982 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14983 player->index_bit, dig_side);
14985 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14986 player->index_bit, dig_side);
14987 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14988 player->index_bit, dig_side);
14991 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14992 player->index_bit, dig_side);
14993 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14994 player->index_bit, dig_side);
14996 return MP_NO_ACTION;
14999 player->push_delay = -1;
15001 if (is_player) // function can also be called by EL_PENGUIN
15003 if (Tile[x][y] != element) // really digged/collected something
15005 player->is_collecting = !player->is_digging;
15006 player->is_active = TRUE;
15008 player->last_removed_element = element;
15015 static boolean DigFieldByCE(int x, int y, int digging_element)
15017 int element = Tile[x][y];
15019 if (!IS_FREE(x, y))
15021 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
15022 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
15025 // no element can dig solid indestructible elements
15026 if (IS_INDESTRUCTIBLE(element) &&
15027 !IS_DIGGABLE(element) &&
15028 !IS_COLLECTIBLE(element))
15031 if (AmoebaNr[x][y] &&
15032 (element == EL_AMOEBA_FULL ||
15033 element == EL_BD_AMOEBA ||
15034 element == EL_AMOEBA_GROWING))
15036 AmoebaCnt[AmoebaNr[x][y]]--;
15037 AmoebaCnt2[AmoebaNr[x][y]]--;
15040 if (IS_MOVING(x, y))
15041 RemoveMovingField(x, y);
15045 TEST_DrawLevelField(x, y);
15048 // if digged element was about to explode, prevent the explosion
15049 ExplodeField[x][y] = EX_TYPE_NONE;
15051 PlayLevelSoundAction(x, y, action);
15054 Store[x][y] = EL_EMPTY;
15056 // this makes it possible to leave the removed element again
15057 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15058 Store[x][y] = element;
15063 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15065 int jx = player->jx, jy = player->jy;
15066 int x = jx + dx, y = jy + dy;
15067 int snap_direction = (dx == -1 ? MV_LEFT :
15068 dx == +1 ? MV_RIGHT :
15070 dy == +1 ? MV_DOWN : MV_NONE);
15071 boolean can_continue_snapping = (level.continuous_snapping &&
15072 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15074 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15077 if (!player->active || !IN_LEV_FIELD(x, y))
15085 if (player->MovPos == 0)
15086 player->is_pushing = FALSE;
15088 player->is_snapping = FALSE;
15090 if (player->MovPos == 0)
15092 player->is_moving = FALSE;
15093 player->is_digging = FALSE;
15094 player->is_collecting = FALSE;
15100 // prevent snapping with already pressed snap key when not allowed
15101 if (player->is_snapping && !can_continue_snapping)
15104 player->MovDir = snap_direction;
15106 if (player->MovPos == 0)
15108 player->is_moving = FALSE;
15109 player->is_digging = FALSE;
15110 player->is_collecting = FALSE;
15113 player->is_dropping = FALSE;
15114 player->is_dropping_pressed = FALSE;
15115 player->drop_pressed_delay = 0;
15117 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15120 player->is_snapping = TRUE;
15121 player->is_active = TRUE;
15123 if (player->MovPos == 0)
15125 player->is_moving = FALSE;
15126 player->is_digging = FALSE;
15127 player->is_collecting = FALSE;
15130 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15131 TEST_DrawLevelField(player->last_jx, player->last_jy);
15133 TEST_DrawLevelField(x, y);
15138 static boolean DropElement(struct PlayerInfo *player)
15140 int old_element, new_element;
15141 int dropx = player->jx, dropy = player->jy;
15142 int drop_direction = player->MovDir;
15143 int drop_side = drop_direction;
15144 int drop_element = get_next_dropped_element(player);
15146 /* do not drop an element on top of another element; when holding drop key
15147 pressed without moving, dropped element must move away before the next
15148 element can be dropped (this is especially important if the next element
15149 is dynamite, which can be placed on background for historical reasons) */
15150 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15153 if (IS_THROWABLE(drop_element))
15155 dropx += GET_DX_FROM_DIR(drop_direction);
15156 dropy += GET_DY_FROM_DIR(drop_direction);
15158 if (!IN_LEV_FIELD(dropx, dropy))
15162 old_element = Tile[dropx][dropy]; // old element at dropping position
15163 new_element = drop_element; // default: no change when dropping
15165 // check if player is active, not moving and ready to drop
15166 if (!player->active || player->MovPos || player->drop_delay > 0)
15169 // check if player has anything that can be dropped
15170 if (new_element == EL_UNDEFINED)
15173 // only set if player has anything that can be dropped
15174 player->is_dropping_pressed = TRUE;
15176 // check if drop key was pressed long enough for EM style dynamite
15177 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15180 // check if anything can be dropped at the current position
15181 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15184 // collected custom elements can only be dropped on empty fields
15185 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15188 if (old_element != EL_EMPTY)
15189 Back[dropx][dropy] = old_element; // store old element on this field
15191 ResetGfxAnimation(dropx, dropy);
15192 ResetRandomAnimationValue(dropx, dropy);
15194 if (player->inventory_size > 0 ||
15195 player->inventory_infinite_element != EL_UNDEFINED)
15197 if (player->inventory_size > 0)
15199 player->inventory_size--;
15201 DrawGameDoorValues();
15203 if (new_element == EL_DYNAMITE)
15204 new_element = EL_DYNAMITE_ACTIVE;
15205 else if (new_element == EL_EM_DYNAMITE)
15206 new_element = EL_EM_DYNAMITE_ACTIVE;
15207 else if (new_element == EL_SP_DISK_RED)
15208 new_element = EL_SP_DISK_RED_ACTIVE;
15211 Tile[dropx][dropy] = new_element;
15213 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15214 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15215 el2img(Tile[dropx][dropy]), 0);
15217 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15219 // needed if previous element just changed to "empty" in the last frame
15220 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15222 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15223 player->index_bit, drop_side);
15224 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15226 player->index_bit, drop_side);
15228 TestIfElementTouchesCustomElement(dropx, dropy);
15230 else // player is dropping a dyna bomb
15232 player->dynabombs_left--;
15234 Tile[dropx][dropy] = new_element;
15236 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15237 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15238 el2img(Tile[dropx][dropy]), 0);
15240 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15243 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15244 InitField_WithBug1(dropx, dropy, FALSE);
15246 new_element = Tile[dropx][dropy]; // element might have changed
15248 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15249 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15251 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15252 MovDir[dropx][dropy] = drop_direction;
15254 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15256 // do not cause impact style collision by dropping elements that can fall
15257 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15260 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15261 player->is_dropping = TRUE;
15263 player->drop_pressed_delay = 0;
15264 player->is_dropping_pressed = FALSE;
15266 player->drop_x = dropx;
15267 player->drop_y = dropy;
15272 // ----------------------------------------------------------------------------
15273 // game sound playing functions
15274 // ----------------------------------------------------------------------------
15276 static int *loop_sound_frame = NULL;
15277 static int *loop_sound_volume = NULL;
15279 void InitPlayLevelSound(void)
15281 int num_sounds = getSoundListSize();
15283 checked_free(loop_sound_frame);
15284 checked_free(loop_sound_volume);
15286 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15287 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15290 static void PlayLevelSoundExt(int x, int y, int nr, boolean is_loop_sound)
15292 int sx = SCREENX(x), sy = SCREENY(y);
15293 int volume, stereo_position;
15294 int max_distance = 8;
15295 int type = (is_loop_sound ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15297 if ((!setup.sound_simple && !is_loop_sound) ||
15298 (!setup.sound_loops && is_loop_sound))
15301 if (!IN_LEV_FIELD(x, y) ||
15302 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15303 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15306 volume = SOUND_MAX_VOLUME;
15308 if (!IN_SCR_FIELD(sx, sy))
15310 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15311 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15313 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15316 stereo_position = (SOUND_MAX_LEFT +
15317 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15318 (SCR_FIELDX + 2 * max_distance));
15322 /* This assures that quieter loop sounds do not overwrite louder ones,
15323 while restarting sound volume comparison with each new game frame. */
15325 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15328 loop_sound_volume[nr] = volume;
15329 loop_sound_frame[nr] = FrameCounter;
15332 PlaySoundExt(nr, volume, stereo_position, type);
15335 static void PlayLevelSound(int x, int y, int nr)
15337 PlayLevelSoundExt(x, y, nr, IS_LOOP_SOUND(nr));
15340 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15342 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15343 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15344 y < LEVELY(BY1) ? LEVELY(BY1) :
15345 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15349 static void PlayLevelSoundAction(int x, int y, int action)
15351 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15354 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15356 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15358 if (sound_effect != SND_UNDEFINED)
15359 PlayLevelSound(x, y, sound_effect);
15362 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15365 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15367 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15368 PlayLevelSound(x, y, sound_effect);
15371 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15373 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15375 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15376 PlayLevelSound(x, y, sound_effect);
15379 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15381 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15383 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15384 StopSound(sound_effect);
15387 static int getLevelMusicNr(void)
15389 int level_pos = level_nr - leveldir_current->first_level;
15391 if (levelset.music[level_nr] != MUS_UNDEFINED)
15392 return levelset.music[level_nr]; // from config file
15394 return MAP_NOCONF_MUSIC(level_pos); // from music dir
15397 static void FadeLevelSounds(void)
15402 static void FadeLevelMusic(void)
15404 int music_nr = getLevelMusicNr();
15405 char *curr_music = getCurrentlyPlayingMusicFilename();
15406 char *next_music = getMusicInfoEntryFilename(music_nr);
15408 if (!strEqual(curr_music, next_music))
15412 void FadeLevelSoundsAndMusic(void)
15418 static void PlayLevelMusic(void)
15420 int music_nr = getLevelMusicNr();
15421 char *curr_music = getCurrentlyPlayingMusicFilename();
15422 char *next_music = getMusicInfoEntryFilename(music_nr);
15424 if (!strEqual(curr_music, next_music))
15425 PlayMusicLoop(music_nr);
15428 static int getSoundAction_BD(int sample)
15434 case GD_S_DIRT_BALL:
15436 case GD_S_FALLING_WALL:
15437 return ACTION_IMPACT;
15439 case GD_S_NUT_CRACK:
15440 return ACTION_BREAKING;
15442 case GD_S_EXPANDING_WALL:
15443 case GD_S_WALL_REAPPEAR:
15446 case GD_S_ACID_SPREAD:
15447 return ACTION_GROWING;
15449 case GD_S_DIAMOND_COLLECT:
15450 case GD_S_SKELETON_COLLECT:
15451 case GD_S_PNEUMATIC_COLLECT:
15452 case GD_S_BOMB_COLLECT:
15453 case GD_S_CLOCK_COLLECT:
15454 case GD_S_SWEET_COLLECT:
15455 case GD_S_KEY_COLLECT:
15456 case GD_S_DIAMOND_KEY_COLLECT:
15457 return ACTION_COLLECTING;
15459 case GD_S_BOMB_PLACE:
15460 case GD_S_REPLICATOR:
15461 return ACTION_DROPPING;
15463 case GD_S_BLADDER_MOVE:
15464 return ACTION_MOVING;
15466 case GD_S_BLADDER_SPENDER:
15467 case GD_S_BLADDER_CONVERT:
15468 case GD_S_GRAVITY_CHANGE:
15469 return ACTION_CHANGING;
15471 case GD_S_BITER_EAT:
15472 return ACTION_EATING;
15474 case GD_S_DOOR_OPEN:
15476 return ACTION_OPENING;
15478 case GD_S_WALK_EARTH:
15479 return ACTION_DIGGING;
15481 case GD_S_WALK_EMPTY:
15482 return ACTION_WALKING;
15484 case GD_S_SWITCH_BITER:
15485 case GD_S_SWITCH_CREATURES:
15486 case GD_S_SWITCH_GRAVITY:
15487 case GD_S_SWITCH_EXPANDING:
15488 case GD_S_SWITCH_CONVEYOR:
15489 case GD_S_SWITCH_REPLICATOR:
15490 case GD_S_STIRRING:
15491 return ACTION_ACTIVATING;
15493 case GD_S_BOX_PUSH:
15494 return ACTION_PUSHING;
15496 case GD_S_TELEPORTER:
15497 return ACTION_PASSING;
15499 case GD_S_EXPLOSION:
15500 case GD_S_BOMB_EXPLOSION:
15501 case GD_S_GHOST_EXPLOSION:
15502 case GD_S_VOODOO_EXPLOSION:
15503 case GD_S_NITRO_EXPLOSION:
15504 return ACTION_EXPLODING;
15508 case GD_S_AMOEBA_MAGIC:
15509 case GD_S_MAGIC_WALL:
15510 case GD_S_PNEUMATIC_HAMMER:
15512 return ACTION_ACTIVE;
15514 case GD_S_DIAMOND_RANDOM:
15515 case GD_S_DIAMOND_1:
15516 case GD_S_DIAMOND_2:
15517 case GD_S_DIAMOND_3:
15518 case GD_S_DIAMOND_4:
15519 case GD_S_DIAMOND_5:
15520 case GD_S_DIAMOND_6:
15521 case GD_S_DIAMOND_7:
15522 case GD_S_DIAMOND_8:
15523 case GD_S_TIMEOUT_0:
15524 case GD_S_TIMEOUT_1:
15525 case GD_S_TIMEOUT_2:
15526 case GD_S_TIMEOUT_3:
15527 case GD_S_TIMEOUT_4:
15528 case GD_S_TIMEOUT_5:
15529 case GD_S_TIMEOUT_6:
15530 case GD_S_TIMEOUT_7:
15531 case GD_S_TIMEOUT_8:
15532 case GD_S_TIMEOUT_9:
15533 case GD_S_TIMEOUT_10:
15534 case GD_S_BONUS_LIFE:
15535 // kludge to prevent playing as loop sound
15536 return ACTION_OTHER;
15538 case GD_S_FINISHED:
15539 return ACTION_DEFAULT;
15542 return ACTION_DEFAULT;
15546 static int getSoundEffect_BD(int element_bd, int sample)
15548 int sound_action = getSoundAction_BD(sample);
15549 int sound_effect = element_info[SND_ELEMENT(element_bd)].sound[sound_action];
15553 if (sound_action != ACTION_OTHER &&
15554 sound_action != ACTION_DEFAULT)
15555 return sound_effect;
15560 case GD_S_DIAMOND_RANDOM:
15561 nr = GetSimpleRandom(8);
15562 sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr;
15565 case GD_S_DIAMOND_1:
15566 case GD_S_DIAMOND_2:
15567 case GD_S_DIAMOND_3:
15568 case GD_S_DIAMOND_4:
15569 case GD_S_DIAMOND_5:
15570 case GD_S_DIAMOND_6:
15571 case GD_S_DIAMOND_7:
15572 case GD_S_DIAMOND_8:
15573 nr = sample - GD_S_DIAMOND_1;
15574 sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr;
15577 case GD_S_TIMEOUT_0:
15578 case GD_S_TIMEOUT_1:
15579 case GD_S_TIMEOUT_2:
15580 case GD_S_TIMEOUT_3:
15581 case GD_S_TIMEOUT_4:
15582 case GD_S_TIMEOUT_5:
15583 case GD_S_TIMEOUT_6:
15584 case GD_S_TIMEOUT_7:
15585 case GD_S_TIMEOUT_8:
15586 case GD_S_TIMEOUT_9:
15587 case GD_S_TIMEOUT_10:
15588 nr = sample - GD_S_TIMEOUT_0;
15589 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME_0 + nr;
15591 if (getSoundInfoEntryFilename(sound_effect) == NULL && sample != GD_S_TIMEOUT_0)
15592 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME;
15595 case GD_S_FINISHED:
15596 sound_effect = SND_GAME_LEVELTIME_BONUS;
15599 case GD_S_BONUS_LIFE:
15600 sound_effect = SND_GAME_HEALTH_BONUS;
15604 sound_effect = SND_UNDEFINED;
15608 return sound_effect;
15611 void PlayLevelSound_BD(int xx, int yy, int element_bd, int sample)
15613 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15614 int sound_effect = getSoundEffect_BD(element, sample);
15615 int sound_action = getSoundAction_BD(sample);
15616 boolean is_loop_sound = IS_LOOP_SOUND(sound_effect);
15618 int x = xx - offset;
15619 int y = yy - offset;
15621 if (sound_action == ACTION_OTHER)
15622 is_loop_sound = FALSE;
15624 if (sound_effect != SND_UNDEFINED)
15625 PlayLevelSoundExt(x, y, sound_effect, is_loop_sound);
15628 void StopSound_BD(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);
15633 if (sound_effect != SND_UNDEFINED)
15634 StopSound(sound_effect);
15637 boolean isSoundPlaying_BD(int element_bd, int sample)
15639 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15640 int sound_effect = getSoundEffect_BD(element, sample);
15642 if (sound_effect != SND_UNDEFINED)
15643 return isSoundPlaying(sound_effect);
15648 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15650 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15652 int x = xx - offset;
15653 int y = yy - offset;
15658 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15662 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15666 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15670 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15674 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15678 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15682 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15685 case SOUND_android_clone:
15686 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15689 case SOUND_android_move:
15690 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15694 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15698 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15702 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15705 case SOUND_eater_eat:
15706 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15710 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15713 case SOUND_collect:
15714 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15717 case SOUND_diamond:
15718 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15722 // !!! CHECK THIS !!!
15724 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15726 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15730 case SOUND_wonderfall:
15731 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15735 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15739 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15743 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15747 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15751 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15755 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15759 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15763 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15766 case SOUND_exit_open:
15767 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15770 case SOUND_exit_leave:
15771 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15774 case SOUND_dynamite:
15775 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15779 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15783 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15787 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15791 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15795 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15799 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15803 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15808 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15810 int element = map_element_SP_to_RND(element_sp);
15811 int action = map_action_SP_to_RND(action_sp);
15812 int offset = (setup.sp_show_border_elements ? 0 : 1);
15813 int x = xx - offset;
15814 int y = yy - offset;
15816 PlayLevelSoundElementAction(x, y, element, action);
15819 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15821 int element = map_element_MM_to_RND(element_mm);
15822 int action = map_action_MM_to_RND(action_mm);
15824 int x = xx - offset;
15825 int y = yy - offset;
15827 if (!IS_MM_ELEMENT(element))
15828 element = EL_MM_DEFAULT;
15830 PlayLevelSoundElementAction(x, y, element, action);
15833 void PlaySound_MM(int sound_mm)
15835 int sound = map_sound_MM_to_RND(sound_mm);
15837 if (sound == SND_UNDEFINED)
15843 void PlaySoundLoop_MM(int sound_mm)
15845 int sound = map_sound_MM_to_RND(sound_mm);
15847 if (sound == SND_UNDEFINED)
15850 PlaySoundLoop(sound);
15853 void StopSound_MM(int sound_mm)
15855 int sound = map_sound_MM_to_RND(sound_mm);
15857 if (sound == SND_UNDEFINED)
15863 void RaiseScore(int value)
15865 game.score += value;
15867 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15869 DisplayGameControlValues();
15872 void RaiseScoreElement(int element)
15877 case EL_BD_DIAMOND:
15878 case EL_EMERALD_YELLOW:
15879 case EL_EMERALD_RED:
15880 case EL_EMERALD_PURPLE:
15881 case EL_SP_INFOTRON:
15882 RaiseScore(level.score[SC_EMERALD]);
15885 RaiseScore(level.score[SC_DIAMOND]);
15888 RaiseScore(level.score[SC_CRYSTAL]);
15891 RaiseScore(level.score[SC_PEARL]);
15894 case EL_BD_BUTTERFLY:
15895 case EL_SP_ELECTRON:
15896 RaiseScore(level.score[SC_BUG]);
15899 case EL_BD_FIREFLY:
15900 case EL_SP_SNIKSNAK:
15901 RaiseScore(level.score[SC_SPACESHIP]);
15904 case EL_DARK_YAMYAM:
15905 RaiseScore(level.score[SC_YAMYAM]);
15908 RaiseScore(level.score[SC_ROBOT]);
15911 RaiseScore(level.score[SC_PACMAN]);
15914 RaiseScore(level.score[SC_NUT]);
15917 case EL_EM_DYNAMITE:
15918 case EL_SP_DISK_RED:
15919 case EL_DYNABOMB_INCREASE_NUMBER:
15920 case EL_DYNABOMB_INCREASE_SIZE:
15921 case EL_DYNABOMB_INCREASE_POWER:
15922 RaiseScore(level.score[SC_DYNAMITE]);
15924 case EL_SHIELD_NORMAL:
15925 case EL_SHIELD_DEADLY:
15926 RaiseScore(level.score[SC_SHIELD]);
15928 case EL_EXTRA_TIME:
15929 RaiseScore(level.extra_time_score);
15943 case EL_DC_KEY_WHITE:
15944 RaiseScore(level.score[SC_KEY]);
15947 RaiseScore(element_info[element].collect_score);
15952 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15954 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15958 // prevent short reactivation of overlay buttons while closing door
15959 SetOverlayActive(FALSE);
15960 UnmapGameButtons();
15962 // door may still be open due to skipped or envelope style request
15963 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
15966 if (network.enabled)
15968 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15973 FadeSkipNextFadeIn();
15975 SetGameStatus(GAME_MODE_MAIN);
15980 else // continue playing the game
15982 if (tape.playing && tape.deactivate_display)
15983 TapeDeactivateDisplayOff(TRUE);
15985 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15987 if (tape.playing && tape.deactivate_display)
15988 TapeDeactivateDisplayOn();
15992 void RequestQuitGame(boolean escape_key_pressed)
15994 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15995 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15996 level_editor_test_game);
15997 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15998 quick_quit || score_info_tape_play);
16000 RequestQuitGameExt(skip_request, quick_quit,
16001 "Do you really want to quit the game?");
16004 static char *getRestartGameMessage(void)
16006 boolean play_again = hasStartedNetworkGame();
16007 static char message[MAX_OUTPUT_LINESIZE];
16008 char *game_over_text = "Game over!";
16009 char *play_again_text = " Play it again?";
16011 if (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
16012 game_mm.game_over_message != NULL)
16013 game_over_text = game_mm.game_over_message;
16015 snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text,
16016 (play_again ? play_again_text : ""));
16021 static void RequestRestartGame(void)
16023 char *message = getRestartGameMessage();
16024 boolean has_started_game = hasStartedNetworkGame();
16025 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
16026 int door_state = DOOR_CLOSE_1;
16028 if (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game)
16030 CloseDoor(door_state);
16032 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16036 // if game was invoked from level editor, also close tape recorder door
16037 if (level_editor_test_game)
16038 door_state = DOOR_CLOSE_ALL;
16040 CloseDoor(door_state);
16042 SetGameStatus(GAME_MODE_MAIN);
16048 boolean CheckRestartGame(void)
16050 static int game_over_delay = 0;
16051 int game_over_delay_value = 50;
16052 boolean game_over = checkGameFailed();
16056 game_over_delay = game_over_delay_value;
16061 if (game_over_delay > 0)
16063 if (game_over_delay == game_over_delay_value / 2)
16064 PlaySound(SND_GAME_LOSING);
16071 // do not ask to play again if request dialog is already active
16072 if (game.request_active)
16075 // do not ask to play again if request dialog already handled
16076 if (game.RestartGameRequested)
16079 // do not ask to play again if game was never actually played
16080 if (!game.GamePlayed)
16083 // do not ask to play again if this was disabled in setup menu
16084 if (!setup.ask_on_game_over)
16087 game.RestartGameRequested = TRUE;
16089 RequestRestartGame();
16094 boolean checkGameSolved(void)
16096 // set for all game engines if level was solved
16097 return game.LevelSolved_GameEnd;
16100 boolean checkGameFailed(void)
16102 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16103 return (game_em.game_over && !game_em.level_solved);
16104 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16105 return (game_sp.game_over && !game_sp.level_solved);
16106 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16107 return (game_mm.game_over && !game_mm.level_solved);
16108 else // GAME_ENGINE_TYPE_RND
16109 return (game.GameOver && !game.LevelSolved);
16112 boolean checkGameEnded(void)
16114 return (checkGameSolved() || checkGameFailed());
16118 // ----------------------------------------------------------------------------
16119 // random generator functions
16120 // ----------------------------------------------------------------------------
16122 unsigned int InitEngineRandom_RND(int seed)
16124 game.num_random_calls = 0;
16126 return InitEngineRandom(seed);
16129 unsigned int RND(int max)
16133 game.num_random_calls++;
16135 return GetEngineRandom(max);
16142 // ----------------------------------------------------------------------------
16143 // game engine snapshot handling functions
16144 // ----------------------------------------------------------------------------
16146 struct EngineSnapshotInfo
16148 // runtime values for custom element collect score
16149 int collect_score[NUM_CUSTOM_ELEMENTS];
16151 // runtime values for group element choice position
16152 int choice_pos[NUM_GROUP_ELEMENTS];
16154 // runtime values for belt position animations
16155 int belt_graphic[4][NUM_BELT_PARTS];
16156 int belt_anim_mode[4][NUM_BELT_PARTS];
16159 static struct EngineSnapshotInfo engine_snapshot_rnd;
16160 static char *snapshot_level_identifier = NULL;
16161 static int snapshot_level_nr = -1;
16163 static void SaveEngineSnapshotValues_RND(void)
16165 static int belt_base_active_element[4] =
16167 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
16168 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
16169 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
16170 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
16174 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16176 int element = EL_CUSTOM_START + i;
16178 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
16181 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16183 int element = EL_GROUP_START + i;
16185 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
16188 for (i = 0; i < 4; i++)
16190 for (j = 0; j < NUM_BELT_PARTS; j++)
16192 int element = belt_base_active_element[i] + j;
16193 int graphic = el2img(element);
16194 int anim_mode = graphic_info[graphic].anim_mode;
16196 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
16197 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
16202 static void LoadEngineSnapshotValues_RND(void)
16204 unsigned int num_random_calls = game.num_random_calls;
16207 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16209 int element = EL_CUSTOM_START + i;
16211 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
16214 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16216 int element = EL_GROUP_START + i;
16218 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
16221 for (i = 0; i < 4; i++)
16223 for (j = 0; j < NUM_BELT_PARTS; j++)
16225 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
16226 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
16228 graphic_info[graphic].anim_mode = anim_mode;
16232 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16234 InitRND(tape.random_seed);
16235 for (i = 0; i < num_random_calls; i++)
16239 if (game.num_random_calls != num_random_calls)
16241 Error("number of random calls out of sync");
16242 Error("number of random calls should be %d", num_random_calls);
16243 Error("number of random calls is %d", game.num_random_calls);
16245 Fail("this should not happen -- please debug");
16249 void FreeEngineSnapshotSingle(void)
16251 FreeSnapshotSingle();
16253 setString(&snapshot_level_identifier, NULL);
16254 snapshot_level_nr = -1;
16257 void FreeEngineSnapshotList(void)
16259 FreeSnapshotList();
16262 static ListNode *SaveEngineSnapshotBuffers(void)
16264 ListNode *buffers = NULL;
16266 // copy some special values to a structure better suited for the snapshot
16268 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16269 SaveEngineSnapshotValues_RND();
16270 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16271 SaveEngineSnapshotValues_EM();
16272 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16273 SaveEngineSnapshotValues_SP(&buffers);
16274 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16275 SaveEngineSnapshotValues_MM();
16277 // save values stored in special snapshot structure
16279 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16280 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
16281 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16282 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
16283 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16284 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
16285 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16286 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
16288 // save further RND engine values
16290 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
16291 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
16292 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
16294 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
16295 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
16296 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
16297 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16298 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTimeFrames));
16299 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16301 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16302 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16303 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16305 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16307 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16308 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16310 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16311 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16312 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16313 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16314 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16315 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16316 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16317 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16318 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16319 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16320 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16321 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16322 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16323 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16324 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16325 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16326 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16327 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16329 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16330 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16332 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16333 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16334 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16336 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16337 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16339 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16340 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16341 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16342 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16343 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16344 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16346 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16347 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16350 ListNode *node = engine_snapshot_list_rnd;
16353 while (node != NULL)
16355 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16360 Debug("game:playing:SaveEngineSnapshotBuffers",
16361 "size of engine snapshot: %d bytes", num_bytes);
16367 void SaveEngineSnapshotSingle(void)
16369 ListNode *buffers = SaveEngineSnapshotBuffers();
16371 // finally save all snapshot buffers to single snapshot
16372 SaveSnapshotSingle(buffers);
16374 // save level identification information
16375 setString(&snapshot_level_identifier, leveldir_current->identifier);
16376 snapshot_level_nr = level_nr;
16379 boolean CheckSaveEngineSnapshotToList(void)
16381 boolean save_snapshot =
16382 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16383 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16384 game.snapshot.changed_action) ||
16385 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16386 game.snapshot.collected_item));
16388 game.snapshot.changed_action = FALSE;
16389 game.snapshot.collected_item = FALSE;
16390 game.snapshot.save_snapshot = save_snapshot;
16392 return save_snapshot;
16395 void SaveEngineSnapshotToList(void)
16397 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16401 ListNode *buffers = SaveEngineSnapshotBuffers();
16403 // finally save all snapshot buffers to snapshot list
16404 SaveSnapshotToList(buffers);
16407 void SaveEngineSnapshotToListInitial(void)
16409 FreeEngineSnapshotList();
16411 SaveEngineSnapshotToList();
16414 static void LoadEngineSnapshotValues(void)
16416 // restore special values from snapshot structure
16418 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16419 LoadEngineSnapshotValues_RND();
16420 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16421 LoadEngineSnapshotValues_EM();
16422 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16423 LoadEngineSnapshotValues_SP();
16424 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16425 LoadEngineSnapshotValues_MM();
16428 void LoadEngineSnapshotSingle(void)
16430 LoadSnapshotSingle();
16432 LoadEngineSnapshotValues();
16435 static void LoadEngineSnapshot_Undo(int steps)
16437 LoadSnapshotFromList_Older(steps);
16439 LoadEngineSnapshotValues();
16442 static void LoadEngineSnapshot_Redo(int steps)
16444 LoadSnapshotFromList_Newer(steps);
16446 LoadEngineSnapshotValues();
16449 boolean CheckEngineSnapshotSingle(void)
16451 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16452 snapshot_level_nr == level_nr);
16455 boolean CheckEngineSnapshotList(void)
16457 return CheckSnapshotList();
16461 // ---------- new game button stuff -------------------------------------------
16468 boolean *setup_value;
16469 boolean allowed_on_tape;
16470 boolean is_touch_button;
16472 } gamebutton_info[NUM_GAME_BUTTONS] =
16475 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16476 GAME_CTRL_ID_STOP, NULL,
16477 TRUE, FALSE, "stop game"
16480 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16481 GAME_CTRL_ID_PAUSE, NULL,
16482 TRUE, FALSE, "pause game"
16485 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16486 GAME_CTRL_ID_PLAY, NULL,
16487 TRUE, FALSE, "play game"
16490 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16491 GAME_CTRL_ID_UNDO, NULL,
16492 TRUE, FALSE, "undo step"
16495 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16496 GAME_CTRL_ID_REDO, NULL,
16497 TRUE, FALSE, "redo step"
16500 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16501 GAME_CTRL_ID_SAVE, NULL,
16502 TRUE, FALSE, "save game"
16505 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16506 GAME_CTRL_ID_PAUSE2, NULL,
16507 TRUE, FALSE, "pause game"
16510 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16511 GAME_CTRL_ID_LOAD, NULL,
16512 TRUE, FALSE, "load game"
16515 IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart,
16516 GAME_CTRL_ID_RESTART, NULL,
16517 TRUE, FALSE, "restart game"
16520 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16521 GAME_CTRL_ID_PANEL_STOP, NULL,
16522 FALSE, FALSE, "stop game"
16525 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16526 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16527 FALSE, FALSE, "pause game"
16530 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16531 GAME_CTRL_ID_PANEL_PLAY, NULL,
16532 FALSE, FALSE, "play game"
16535 IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart,
16536 GAME_CTRL_ID_PANEL_RESTART, NULL,
16537 FALSE, FALSE, "restart game"
16540 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16541 GAME_CTRL_ID_TOUCH_STOP, NULL,
16542 FALSE, TRUE, "stop game"
16545 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16546 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16547 FALSE, TRUE, "pause game"
16550 IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart,
16551 GAME_CTRL_ID_TOUCH_RESTART, NULL,
16552 FALSE, TRUE, "restart game"
16555 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16556 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16557 TRUE, FALSE, "background music on/off"
16560 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16561 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16562 TRUE, FALSE, "sound loops on/off"
16565 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16566 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16567 TRUE, FALSE, "normal sounds on/off"
16570 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16571 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16572 FALSE, FALSE, "background music on/off"
16575 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16576 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16577 FALSE, FALSE, "sound loops on/off"
16580 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16581 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16582 FALSE, FALSE, "normal sounds on/off"
16586 void CreateGameButtons(void)
16590 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16592 int graphic = gamebutton_info[i].graphic;
16593 struct GraphicInfo *gfx = &graphic_info[graphic];
16594 struct XY *pos = gamebutton_info[i].pos;
16595 struct GadgetInfo *gi;
16598 unsigned int event_mask;
16599 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16600 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16601 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16602 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16603 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16604 int gd_x = gfx->src_x;
16605 int gd_y = gfx->src_y;
16606 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16607 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16608 int gd_xa = gfx->src_x + gfx->active_xoffset;
16609 int gd_ya = gfx->src_y + gfx->active_yoffset;
16610 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16611 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16612 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16613 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16616 // do not use touch buttons if overlay touch buttons are disabled
16617 if (is_touch_button && !setup.touch.overlay_buttons)
16620 if (gfx->bitmap == NULL)
16622 game_gadget[id] = NULL;
16627 if (id == GAME_CTRL_ID_STOP ||
16628 id == GAME_CTRL_ID_PANEL_STOP ||
16629 id == GAME_CTRL_ID_TOUCH_STOP ||
16630 id == GAME_CTRL_ID_PLAY ||
16631 id == GAME_CTRL_ID_PANEL_PLAY ||
16632 id == GAME_CTRL_ID_SAVE ||
16633 id == GAME_CTRL_ID_LOAD ||
16634 id == GAME_CTRL_ID_RESTART ||
16635 id == GAME_CTRL_ID_PANEL_RESTART ||
16636 id == GAME_CTRL_ID_TOUCH_RESTART)
16638 button_type = GD_TYPE_NORMAL_BUTTON;
16640 event_mask = GD_EVENT_RELEASED;
16642 else if (id == GAME_CTRL_ID_UNDO ||
16643 id == GAME_CTRL_ID_REDO)
16645 button_type = GD_TYPE_NORMAL_BUTTON;
16647 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16651 button_type = GD_TYPE_CHECK_BUTTON;
16652 checked = (gamebutton_info[i].setup_value != NULL ?
16653 *gamebutton_info[i].setup_value : FALSE);
16654 event_mask = GD_EVENT_PRESSED;
16657 gi = CreateGadget(GDI_CUSTOM_ID, id,
16658 GDI_IMAGE_ID, graphic,
16659 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16662 GDI_WIDTH, gfx->width,
16663 GDI_HEIGHT, gfx->height,
16664 GDI_TYPE, button_type,
16665 GDI_STATE, GD_BUTTON_UNPRESSED,
16666 GDI_CHECKED, checked,
16667 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16668 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16669 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16670 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16671 GDI_DIRECT_DRAW, FALSE,
16672 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16673 GDI_EVENT_MASK, event_mask,
16674 GDI_CALLBACK_ACTION, HandleGameButtons,
16678 Fail("cannot create gadget");
16680 game_gadget[id] = gi;
16684 void FreeGameButtons(void)
16688 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16689 FreeGadget(game_gadget[i]);
16692 static void UnmapGameButtonsAtSamePosition(int id)
16696 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16698 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16699 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16700 UnmapGadget(game_gadget[i]);
16703 static void UnmapGameButtonsAtSamePosition_All(void)
16705 if (setup.show_load_save_buttons)
16707 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16708 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16709 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16711 else if (setup.show_undo_redo_buttons)
16713 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16714 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16715 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16719 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16720 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16721 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16723 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16724 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16725 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16729 void MapLoadSaveButtons(void)
16731 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16732 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16734 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16735 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16738 void MapUndoRedoButtons(void)
16740 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16741 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16743 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16744 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16747 void ModifyPauseButtons(void)
16751 GAME_CTRL_ID_PAUSE,
16752 GAME_CTRL_ID_PAUSE2,
16753 GAME_CTRL_ID_PANEL_PAUSE,
16754 GAME_CTRL_ID_TOUCH_PAUSE,
16759 // do not redraw pause button on closed door (may happen when restarting game)
16760 if (!(GetDoorState() & DOOR_OPEN_1))
16763 for (i = 0; ids[i] > -1; i++)
16764 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16767 static void MapGameButtonsExt(boolean on_tape)
16771 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16773 if ((i == GAME_CTRL_ID_UNDO ||
16774 i == GAME_CTRL_ID_REDO) &&
16775 game_status != GAME_MODE_PLAYING)
16778 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16779 MapGadget(game_gadget[i]);
16782 UnmapGameButtonsAtSamePosition_All();
16784 RedrawGameButtons();
16787 static void UnmapGameButtonsExt(boolean on_tape)
16791 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16792 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16793 UnmapGadget(game_gadget[i]);
16796 static void RedrawGameButtonsExt(boolean on_tape)
16800 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16801 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16802 RedrawGadget(game_gadget[i]);
16805 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16810 gi->checked = state;
16813 static void RedrawSoundButtonGadget(int id)
16815 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16816 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16817 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16818 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16819 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16820 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16823 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16824 RedrawGadget(game_gadget[id2]);
16827 void MapGameButtons(void)
16829 MapGameButtonsExt(FALSE);
16832 void UnmapGameButtons(void)
16834 UnmapGameButtonsExt(FALSE);
16837 void RedrawGameButtons(void)
16839 RedrawGameButtonsExt(FALSE);
16842 void MapGameButtonsOnTape(void)
16844 MapGameButtonsExt(TRUE);
16847 void UnmapGameButtonsOnTape(void)
16849 UnmapGameButtonsExt(TRUE);
16852 void RedrawGameButtonsOnTape(void)
16854 RedrawGameButtonsExt(TRUE);
16857 static void GameUndoRedoExt(void)
16859 ClearPlayerAction();
16861 tape.pausing = TRUE;
16864 UpdateAndDisplayGameControlValues();
16866 DrawCompleteVideoDisplay();
16867 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16868 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16869 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16871 ModifyPauseButtons();
16876 static void GameUndo(int steps)
16878 if (!CheckEngineSnapshotList())
16881 int tape_property_bits = tape.property_bits;
16883 LoadEngineSnapshot_Undo(steps);
16885 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16890 static void GameRedo(int steps)
16892 if (!CheckEngineSnapshotList())
16895 int tape_property_bits = tape.property_bits;
16897 LoadEngineSnapshot_Redo(steps);
16899 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16904 static void HandleGameButtonsExt(int id, int button)
16906 static boolean game_undo_executed = FALSE;
16907 int steps = BUTTON_STEPSIZE(button);
16908 boolean handle_game_buttons =
16909 (game_status == GAME_MODE_PLAYING ||
16910 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16912 if (!handle_game_buttons)
16917 case GAME_CTRL_ID_STOP:
16918 case GAME_CTRL_ID_PANEL_STOP:
16919 case GAME_CTRL_ID_TOUCH_STOP:
16924 case GAME_CTRL_ID_PAUSE:
16925 case GAME_CTRL_ID_PAUSE2:
16926 case GAME_CTRL_ID_PANEL_PAUSE:
16927 case GAME_CTRL_ID_TOUCH_PAUSE:
16928 if (network.enabled && game_status == GAME_MODE_PLAYING)
16931 SendToServer_ContinuePlaying();
16933 SendToServer_PausePlaying();
16936 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16938 game_undo_executed = FALSE;
16942 case GAME_CTRL_ID_PLAY:
16943 case GAME_CTRL_ID_PANEL_PLAY:
16944 if (game_status == GAME_MODE_MAIN)
16946 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16948 else if (tape.pausing)
16950 if (network.enabled)
16951 SendToServer_ContinuePlaying();
16953 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16957 case GAME_CTRL_ID_UNDO:
16958 // Important: When using "save snapshot when collecting an item" mode,
16959 // load last (current) snapshot for first "undo" after pressing "pause"
16960 // (else the last-but-one snapshot would be loaded, because the snapshot
16961 // pointer already points to the last snapshot when pressing "pause",
16962 // which is fine for "every step/move" mode, but not for "every collect")
16963 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16964 !game_undo_executed)
16967 game_undo_executed = TRUE;
16972 case GAME_CTRL_ID_REDO:
16976 case GAME_CTRL_ID_SAVE:
16980 case GAME_CTRL_ID_LOAD:
16984 case GAME_CTRL_ID_RESTART:
16985 case GAME_CTRL_ID_PANEL_RESTART:
16986 case GAME_CTRL_ID_TOUCH_RESTART:
16991 case SOUND_CTRL_ID_MUSIC:
16992 case SOUND_CTRL_ID_PANEL_MUSIC:
16993 if (setup.sound_music)
16995 setup.sound_music = FALSE;
16999 else if (audio.music_available)
17001 setup.sound = setup.sound_music = TRUE;
17003 SetAudioMode(setup.sound);
17005 if (game_status == GAME_MODE_PLAYING)
17009 RedrawSoundButtonGadget(id);
17013 case SOUND_CTRL_ID_LOOPS:
17014 case SOUND_CTRL_ID_PANEL_LOOPS:
17015 if (setup.sound_loops)
17016 setup.sound_loops = FALSE;
17017 else if (audio.loops_available)
17019 setup.sound = setup.sound_loops = TRUE;
17021 SetAudioMode(setup.sound);
17024 RedrawSoundButtonGadget(id);
17028 case SOUND_CTRL_ID_SIMPLE:
17029 case SOUND_CTRL_ID_PANEL_SIMPLE:
17030 if (setup.sound_simple)
17031 setup.sound_simple = FALSE;
17032 else if (audio.sound_available)
17034 setup.sound = setup.sound_simple = TRUE;
17036 SetAudioMode(setup.sound);
17039 RedrawSoundButtonGadget(id);
17048 static void HandleGameButtons(struct GadgetInfo *gi)
17050 HandleGameButtonsExt(gi->custom_id, gi->event.button);
17053 void HandleSoundButtonKeys(Key key)
17055 if (key == setup.shortcut.sound_simple)
17056 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
17057 else if (key == setup.shortcut.sound_loops)
17058 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
17059 else if (key == setup.shortcut.sound_music)
17060 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);