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_NEEDED 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_NEEDED,
250 &game.panel.gems_needed,
254 GAME_PANEL_GEMS_COLLECTED,
255 &game.panel.gems_collected,
259 GAME_PANEL_GEMS_SCORE,
260 &game.panel.gems_score,
264 GAME_PANEL_INVENTORY_COUNT,
265 &game.panel.inventory_count,
269 GAME_PANEL_INVENTORY_FIRST_1,
270 &game.panel.inventory_first[0],
274 GAME_PANEL_INVENTORY_FIRST_2,
275 &game.panel.inventory_first[1],
279 GAME_PANEL_INVENTORY_FIRST_3,
280 &game.panel.inventory_first[2],
284 GAME_PANEL_INVENTORY_FIRST_4,
285 &game.panel.inventory_first[3],
289 GAME_PANEL_INVENTORY_FIRST_5,
290 &game.panel.inventory_first[4],
294 GAME_PANEL_INVENTORY_FIRST_6,
295 &game.panel.inventory_first[5],
299 GAME_PANEL_INVENTORY_FIRST_7,
300 &game.panel.inventory_first[6],
304 GAME_PANEL_INVENTORY_FIRST_8,
305 &game.panel.inventory_first[7],
309 GAME_PANEL_INVENTORY_LAST_1,
310 &game.panel.inventory_last[0],
314 GAME_PANEL_INVENTORY_LAST_2,
315 &game.panel.inventory_last[1],
319 GAME_PANEL_INVENTORY_LAST_3,
320 &game.panel.inventory_last[2],
324 GAME_PANEL_INVENTORY_LAST_4,
325 &game.panel.inventory_last[3],
329 GAME_PANEL_INVENTORY_LAST_5,
330 &game.panel.inventory_last[4],
334 GAME_PANEL_INVENTORY_LAST_6,
335 &game.panel.inventory_last[5],
339 GAME_PANEL_INVENTORY_LAST_7,
340 &game.panel.inventory_last[6],
344 GAME_PANEL_INVENTORY_LAST_8,
345 &game.panel.inventory_last[7],
389 GAME_PANEL_KEY_WHITE,
390 &game.panel.key_white,
394 GAME_PANEL_KEY_WHITE_COUNT,
395 &game.panel.key_white_count,
404 GAME_PANEL_HIGHSCORE,
405 &game.panel.highscore,
429 GAME_PANEL_TIME_ANIM,
430 &game.panel.time_anim,
433 IMG_GFX_GAME_PANEL_TIME_ANIM,
434 IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
442 GAME_PANEL_HEALTH_ANIM,
443 &game.panel.health_anim,
446 IMG_GFX_GAME_PANEL_HEALTH_ANIM,
447 IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
455 GAME_PANEL_SHIELD_NORMAL,
456 &game.panel.shield_normal,
460 GAME_PANEL_SHIELD_NORMAL_TIME,
461 &game.panel.shield_normal_time,
465 GAME_PANEL_SHIELD_DEADLY,
466 &game.panel.shield_deadly,
470 GAME_PANEL_SHIELD_DEADLY_TIME,
471 &game.panel.shield_deadly_time,
480 GAME_PANEL_EMC_MAGIC_BALL,
481 &game.panel.emc_magic_ball,
485 GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
486 &game.panel.emc_magic_ball_switch,
490 GAME_PANEL_LIGHT_SWITCH,
491 &game.panel.light_switch,
495 GAME_PANEL_LIGHT_SWITCH_TIME,
496 &game.panel.light_switch_time,
500 GAME_PANEL_TIMEGATE_SWITCH,
501 &game.panel.timegate_switch,
505 GAME_PANEL_TIMEGATE_SWITCH_TIME,
506 &game.panel.timegate_switch_time,
510 GAME_PANEL_SWITCHGATE_SWITCH,
511 &game.panel.switchgate_switch,
515 GAME_PANEL_EMC_LENSES,
516 &game.panel.emc_lenses,
520 GAME_PANEL_EMC_LENSES_TIME,
521 &game.panel.emc_lenses_time,
525 GAME_PANEL_EMC_MAGNIFIER,
526 &game.panel.emc_magnifier,
530 GAME_PANEL_EMC_MAGNIFIER_TIME,
531 &game.panel.emc_magnifier_time,
535 GAME_PANEL_BALLOON_SWITCH,
536 &game.panel.balloon_switch,
540 GAME_PANEL_DYNABOMB_NUMBER,
541 &game.panel.dynabomb_number,
545 GAME_PANEL_DYNABOMB_SIZE,
546 &game.panel.dynabomb_size,
550 GAME_PANEL_DYNABOMB_POWER,
551 &game.panel.dynabomb_power,
556 &game.panel.penguins,
560 GAME_PANEL_SOKOBAN_OBJECTS,
561 &game.panel.sokoban_objects,
565 GAME_PANEL_SOKOBAN_FIELDS,
566 &game.panel.sokoban_fields,
570 GAME_PANEL_ROBOT_WHEEL,
571 &game.panel.robot_wheel,
575 GAME_PANEL_CONVEYOR_BELT_1,
576 &game.panel.conveyor_belt[0],
580 GAME_PANEL_CONVEYOR_BELT_2,
581 &game.panel.conveyor_belt[1],
585 GAME_PANEL_CONVEYOR_BELT_3,
586 &game.panel.conveyor_belt[2],
590 GAME_PANEL_CONVEYOR_BELT_4,
591 &game.panel.conveyor_belt[3],
595 GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
596 &game.panel.conveyor_belt_switch[0],
600 GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
601 &game.panel.conveyor_belt_switch[1],
605 GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
606 &game.panel.conveyor_belt_switch[2],
610 GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
611 &game.panel.conveyor_belt_switch[3],
615 GAME_PANEL_MAGIC_WALL,
616 &game.panel.magic_wall,
620 GAME_PANEL_MAGIC_WALL_TIME,
621 &game.panel.magic_wall_time,
625 GAME_PANEL_GRAVITY_STATE,
626 &game.panel.gravity_state,
630 GAME_PANEL_GRAPHIC_1,
631 &game.panel.graphic[0],
635 GAME_PANEL_GRAPHIC_2,
636 &game.panel.graphic[1],
640 GAME_PANEL_GRAPHIC_3,
641 &game.panel.graphic[2],
645 GAME_PANEL_GRAPHIC_4,
646 &game.panel.graphic[3],
650 GAME_PANEL_GRAPHIC_5,
651 &game.panel.graphic[4],
655 GAME_PANEL_GRAPHIC_6,
656 &game.panel.graphic[5],
660 GAME_PANEL_GRAPHIC_7,
661 &game.panel.graphic[6],
665 GAME_PANEL_GRAPHIC_8,
666 &game.panel.graphic[7],
670 GAME_PANEL_ELEMENT_1,
671 &game.panel.element[0],
675 GAME_PANEL_ELEMENT_2,
676 &game.panel.element[1],
680 GAME_PANEL_ELEMENT_3,
681 &game.panel.element[2],
685 GAME_PANEL_ELEMENT_4,
686 &game.panel.element[3],
690 GAME_PANEL_ELEMENT_5,
691 &game.panel.element[4],
695 GAME_PANEL_ELEMENT_6,
696 &game.panel.element[5],
700 GAME_PANEL_ELEMENT_7,
701 &game.panel.element[6],
705 GAME_PANEL_ELEMENT_8,
706 &game.panel.element[7],
710 GAME_PANEL_ELEMENT_COUNT_1,
711 &game.panel.element_count[0],
715 GAME_PANEL_ELEMENT_COUNT_2,
716 &game.panel.element_count[1],
720 GAME_PANEL_ELEMENT_COUNT_3,
721 &game.panel.element_count[2],
725 GAME_PANEL_ELEMENT_COUNT_4,
726 &game.panel.element_count[3],
730 GAME_PANEL_ELEMENT_COUNT_5,
731 &game.panel.element_count[4],
735 GAME_PANEL_ELEMENT_COUNT_6,
736 &game.panel.element_count[5],
740 GAME_PANEL_ELEMENT_COUNT_7,
741 &game.panel.element_count[6],
745 GAME_PANEL_ELEMENT_COUNT_8,
746 &game.panel.element_count[7],
750 GAME_PANEL_CE_SCORE_1,
751 &game.panel.ce_score[0],
755 GAME_PANEL_CE_SCORE_2,
756 &game.panel.ce_score[1],
760 GAME_PANEL_CE_SCORE_3,
761 &game.panel.ce_score[2],
765 GAME_PANEL_CE_SCORE_4,
766 &game.panel.ce_score[3],
770 GAME_PANEL_CE_SCORE_5,
771 &game.panel.ce_score[4],
775 GAME_PANEL_CE_SCORE_6,
776 &game.panel.ce_score[5],
780 GAME_PANEL_CE_SCORE_7,
781 &game.panel.ce_score[6],
785 GAME_PANEL_CE_SCORE_8,
786 &game.panel.ce_score[7],
790 GAME_PANEL_CE_SCORE_1_ELEMENT,
791 &game.panel.ce_score_element[0],
795 GAME_PANEL_CE_SCORE_2_ELEMENT,
796 &game.panel.ce_score_element[1],
800 GAME_PANEL_CE_SCORE_3_ELEMENT,
801 &game.panel.ce_score_element[2],
805 GAME_PANEL_CE_SCORE_4_ELEMENT,
806 &game.panel.ce_score_element[3],
810 GAME_PANEL_CE_SCORE_5_ELEMENT,
811 &game.panel.ce_score_element[4],
815 GAME_PANEL_CE_SCORE_6_ELEMENT,
816 &game.panel.ce_score_element[5],
820 GAME_PANEL_CE_SCORE_7_ELEMENT,
821 &game.panel.ce_score_element[6],
825 GAME_PANEL_CE_SCORE_8_ELEMENT,
826 &game.panel.ce_score_element[7],
830 GAME_PANEL_PLAYER_NAME,
831 &game.panel.player_name,
835 GAME_PANEL_LEVEL_NAME,
836 &game.panel.level_name,
840 GAME_PANEL_LEVEL_AUTHOR,
841 &game.panel.level_author,
852 // values for delayed check of falling and moving elements and for collision
853 #define CHECK_DELAY_MOVING 3
854 #define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
855 #define CHECK_DELAY_COLLISION 2
856 #define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
858 // values for initial player move delay (initial delay counter value)
859 #define INITIAL_MOVE_DELAY_OFF -1
860 #define INITIAL_MOVE_DELAY_ON 0
862 // values for player movement speed (which is in fact a delay value)
863 #define MOVE_DELAY_MIN_SPEED 32
864 #define MOVE_DELAY_NORMAL_SPEED 8
865 #define MOVE_DELAY_HIGH_SPEED 4
866 #define MOVE_DELAY_MAX_SPEED 1
868 #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
869 #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
871 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
872 #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
874 // values for scroll positions
875 #define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
876 (x) > SBX_Right + MIDPOSX ? SBX_Right :\
878 #define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
879 (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
882 // values for other actions
883 #define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
884 #define MOVE_STEPSIZE_MIN (1)
885 #define MOVE_STEPSIZE_MAX (TILEX)
887 #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
888 #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
890 #define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
892 #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
893 RND(element_info[e].push_delay_random))
894 #define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
895 RND(element_info[e].drop_delay_random))
896 #define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
897 RND(element_info[e].move_delay_random))
898 #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
899 (element_info[e].move_delay_random))
900 #define GET_NEW_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
901 RND(element_info[e].step_delay_random))
902 #define GET_MAX_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
903 (element_info[e].step_delay_random))
904 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
905 RND(element_info[e].ce_value_random_initial))
906 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
907 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
908 RND((c)->delay_random * (c)->delay_frames))
909 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
910 RND((c)->delay_random))
913 #define GET_VALID_RUNTIME_ELEMENT(e) \
914 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
916 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
917 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
918 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
919 (be) + (e) - EL_SELF)
921 #define GET_PLAYER_FROM_BITS(p) \
922 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
924 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
925 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
926 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
927 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
928 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
929 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
930 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
931 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
932 RESOLVED_REFERENCE_ELEMENT(be, e) : \
935 #define CAN_GROW_INTO(e) \
936 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
938 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
939 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
942 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
943 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
944 (CAN_MOVE_INTO_ACID(e) && \
945 Tile[x][y] == EL_ACID) || \
948 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
949 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
950 (CAN_MOVE_INTO_ACID(e) && \
951 Tile[x][y] == EL_ACID) || \
954 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
955 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
957 (CAN_MOVE_INTO_ACID(e) && \
958 Tile[x][y] == EL_ACID) || \
959 (DONT_COLLIDE_WITH(e) && \
961 !PLAYER_ENEMY_PROTECTED(x, y))))
963 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
964 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
966 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
967 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
969 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
970 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
972 #define ANDROID_CAN_CLONE_FIELD(x, y) \
973 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
974 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
976 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
977 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
979 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
980 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
982 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
983 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
985 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
986 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
988 #define PIG_CAN_ENTER_FIELD(e, x, y) \
989 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
991 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
992 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
993 Tile[x][y] == EL_EM_EXIT_OPEN || \
994 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
995 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
996 IS_FOOD_PENGUIN(Tile[x][y])))
997 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
998 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
1000 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
1001 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
1003 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
1004 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
1006 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
1007 (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
1008 Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
1010 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
1012 #define CE_ENTER_FIELD_COND(e, x, y) \
1013 (!IS_PLAYER(x, y) && \
1014 IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
1016 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
1017 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
1019 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
1020 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1022 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1023 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1024 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1025 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1027 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1029 // game button identifiers
1030 #define GAME_CTRL_ID_STOP 0
1031 #define GAME_CTRL_ID_PAUSE 1
1032 #define GAME_CTRL_ID_PLAY 2
1033 #define GAME_CTRL_ID_UNDO 3
1034 #define GAME_CTRL_ID_REDO 4
1035 #define GAME_CTRL_ID_SAVE 5
1036 #define GAME_CTRL_ID_PAUSE2 6
1037 #define GAME_CTRL_ID_LOAD 7
1038 #define GAME_CTRL_ID_RESTART 8
1039 #define GAME_CTRL_ID_PANEL_STOP 9
1040 #define GAME_CTRL_ID_PANEL_PAUSE 10
1041 #define GAME_CTRL_ID_PANEL_PLAY 11
1042 #define GAME_CTRL_ID_PANEL_RESTART 12
1043 #define GAME_CTRL_ID_TOUCH_STOP 13
1044 #define GAME_CTRL_ID_TOUCH_PAUSE 14
1045 #define GAME_CTRL_ID_TOUCH_RESTART 15
1046 #define SOUND_CTRL_ID_MUSIC 16
1047 #define SOUND_CTRL_ID_LOOPS 17
1048 #define SOUND_CTRL_ID_SIMPLE 18
1049 #define SOUND_CTRL_ID_PANEL_MUSIC 19
1050 #define SOUND_CTRL_ID_PANEL_LOOPS 20
1051 #define SOUND_CTRL_ID_PANEL_SIMPLE 21
1053 #define NUM_GAME_BUTTONS 22
1056 // forward declaration for internal use
1058 static void CreateField(int, int, int);
1060 static void ResetGfxAnimation(int, int);
1062 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1063 static void AdvanceFrameAndPlayerCounters(int);
1065 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1066 static boolean MovePlayer(struct PlayerInfo *, int, int);
1067 static void ScrollPlayer(struct PlayerInfo *, int);
1068 static void ScrollScreen(struct PlayerInfo *, int);
1070 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1071 static boolean DigFieldByCE(int, int, int);
1072 static boolean SnapField(struct PlayerInfo *, int, int);
1073 static boolean DropElement(struct PlayerInfo *);
1075 static void InitBeltMovement(void);
1076 static void CloseAllOpenTimegates(void);
1077 static void CheckGravityMovement(struct PlayerInfo *);
1078 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1079 static void KillPlayerUnlessEnemyProtected(int, int);
1080 static void KillPlayerUnlessExplosionProtected(int, int);
1082 static void CheckNextToConditions(int, int);
1083 static void TestIfPlayerNextToCustomElement(int, int);
1084 static void TestIfPlayerTouchesCustomElement(int, int);
1085 static void TestIfElementNextToCustomElement(int, int);
1086 static void TestIfElementTouchesCustomElement(int, int);
1087 static void TestIfElementHitsCustomElement(int, int, int);
1089 static void HandleElementChange(int, int, int);
1090 static void ExecuteCustomElementAction(int, int, int, int);
1091 static boolean ChangeElement(int, int, int, int);
1093 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int, int, int);
1094 #define CheckTriggeredElementChange(x, y, e, ev) \
1095 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, -1)
1096 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1097 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1098 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1099 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1100 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1101 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1102 #define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
1103 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1105 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1106 #define CheckElementChange(x, y, e, te, ev) \
1107 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1108 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1109 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1110 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1111 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1112 #define CheckElementChangeByMouse(x, y, e, ev, s) \
1113 CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
1115 static void PlayLevelSound(int, int, int);
1116 static void PlayLevelSoundNearest(int, int, int);
1117 static void PlayLevelSoundAction(int, int, int);
1118 static void PlayLevelSoundElementAction(int, int, int, int);
1119 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1120 static void PlayLevelSoundActionIfLoop(int, int, int);
1121 static void StopLevelSoundActionIfLoop(int, int, int);
1122 static void PlayLevelMusic(void);
1123 static void FadeLevelSoundsAndMusic(void);
1125 static void HandleGameButtons(struct GadgetInfo *);
1127 int AmoebaNeighbourNr(int, int);
1128 void AmoebaToDiamond(int, int);
1129 void ContinueMoving(int, int);
1130 void Bang(int, int);
1131 void InitMovDir(int, int);
1132 void InitAmoebaNr(int, int);
1133 void NewHighScore(int, boolean);
1135 void TestIfGoodThingHitsBadThing(int, int, int);
1136 void TestIfBadThingHitsGoodThing(int, int, int);
1137 void TestIfPlayerTouchesBadThing(int, int);
1138 void TestIfPlayerRunsIntoBadThing(int, int, int);
1139 void TestIfBadThingTouchesPlayer(int, int);
1140 void TestIfBadThingRunsIntoPlayer(int, int, int);
1141 void TestIfFriendTouchesBadThing(int, int);
1142 void TestIfBadThingTouchesFriend(int, int);
1143 void TestIfBadThingTouchesOtherBadThing(int, int);
1144 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1146 void KillPlayer(struct PlayerInfo *);
1147 void BuryPlayer(struct PlayerInfo *);
1148 void RemovePlayer(struct PlayerInfo *);
1149 void ExitPlayer(struct PlayerInfo *);
1151 static int getInvisibleActiveFromInvisibleElement(int);
1152 static int getInvisibleFromInvisibleActiveElement(int);
1154 static void TestFieldAfterSnapping(int, int, int, int, int);
1156 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1158 // for detection of endless loops, caused by custom element programming
1159 // (using maximal playfield width x 10 is just a rough approximation)
1160 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1162 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1164 if (recursion_loop_detected) \
1167 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1169 recursion_loop_detected = TRUE; \
1170 recursion_loop_element = (e); \
1173 recursion_loop_depth++; \
1176 #define RECURSION_LOOP_DETECTION_END() \
1178 recursion_loop_depth--; \
1181 static int recursion_loop_depth;
1182 static boolean recursion_loop_detected;
1183 static boolean recursion_loop_element;
1185 static int map_player_action[MAX_PLAYERS];
1188 // ----------------------------------------------------------------------------
1189 // definition of elements that automatically change to other elements after
1190 // a specified time, eventually calling a function when changing
1191 // ----------------------------------------------------------------------------
1193 // forward declaration for changer functions
1194 static void InitBuggyBase(int, int);
1195 static void WarnBuggyBase(int, int);
1197 static void InitTrap(int, int);
1198 static void ActivateTrap(int, int);
1199 static void ChangeActiveTrap(int, int);
1201 static void InitRobotWheel(int, int);
1202 static void RunRobotWheel(int, int);
1203 static void StopRobotWheel(int, int);
1205 static void InitTimegateWheel(int, int);
1206 static void RunTimegateWheel(int, int);
1208 static void InitMagicBallDelay(int, int);
1209 static void ActivateMagicBall(int, int);
1211 struct ChangingElementInfo
1216 void (*pre_change_function)(int x, int y);
1217 void (*change_function)(int x, int y);
1218 void (*post_change_function)(int x, int y);
1221 static struct ChangingElementInfo change_delay_list[] =
1256 EL_STEEL_EXIT_OPENING,
1264 EL_STEEL_EXIT_CLOSING,
1265 EL_STEEL_EXIT_CLOSED,
1288 EL_EM_STEEL_EXIT_OPENING,
1289 EL_EM_STEEL_EXIT_OPEN,
1296 EL_EM_STEEL_EXIT_CLOSING,
1320 EL_SWITCHGATE_OPENING,
1328 EL_SWITCHGATE_CLOSING,
1329 EL_SWITCHGATE_CLOSED,
1336 EL_TIMEGATE_OPENING,
1344 EL_TIMEGATE_CLOSING,
1353 EL_ACID_SPLASH_LEFT,
1361 EL_ACID_SPLASH_RIGHT,
1370 EL_SP_BUGGY_BASE_ACTIVATING,
1377 EL_SP_BUGGY_BASE_ACTIVATING,
1378 EL_SP_BUGGY_BASE_ACTIVE,
1385 EL_SP_BUGGY_BASE_ACTIVE,
1409 EL_ROBOT_WHEEL_ACTIVE,
1417 EL_TIMEGATE_SWITCH_ACTIVE,
1425 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1426 EL_DC_TIMEGATE_SWITCH,
1433 EL_EMC_MAGIC_BALL_ACTIVE,
1434 EL_EMC_MAGIC_BALL_ACTIVE,
1441 EL_EMC_SPRING_BUMPER_ACTIVE,
1442 EL_EMC_SPRING_BUMPER,
1449 EL_DIAGONAL_SHRINKING,
1457 EL_DIAGONAL_GROWING,
1478 int push_delay_fixed, push_delay_random;
1482 { EL_SPRING, 0, 0 },
1483 { EL_BALLOON, 0, 0 },
1485 { EL_SOKOBAN_OBJECT, 2, 0 },
1486 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1487 { EL_SATELLITE, 2, 0 },
1488 { EL_SP_DISK_YELLOW, 2, 0 },
1490 { EL_UNDEFINED, 0, 0 },
1498 move_stepsize_list[] =
1500 { EL_AMOEBA_DROP, 2 },
1501 { EL_AMOEBA_DROPPING, 2 },
1502 { EL_QUICKSAND_FILLING, 1 },
1503 { EL_QUICKSAND_EMPTYING, 1 },
1504 { EL_QUICKSAND_FAST_FILLING, 2 },
1505 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1506 { EL_MAGIC_WALL_FILLING, 2 },
1507 { EL_MAGIC_WALL_EMPTYING, 2 },
1508 { EL_BD_MAGIC_WALL_FILLING, 2 },
1509 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1510 { EL_DC_MAGIC_WALL_FILLING, 2 },
1511 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1513 { EL_UNDEFINED, 0 },
1521 collect_count_list[] =
1524 { EL_BD_DIAMOND, 1 },
1525 { EL_EMERALD_YELLOW, 1 },
1526 { EL_EMERALD_RED, 1 },
1527 { EL_EMERALD_PURPLE, 1 },
1529 { EL_SP_INFOTRON, 1 },
1533 { EL_UNDEFINED, 0 },
1541 access_direction_list[] =
1543 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1544 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1545 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1546 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1547 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1548 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1549 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1550 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1551 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1552 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1553 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1555 { EL_SP_PORT_LEFT, MV_RIGHT },
1556 { EL_SP_PORT_RIGHT, MV_LEFT },
1557 { EL_SP_PORT_UP, MV_DOWN },
1558 { EL_SP_PORT_DOWN, MV_UP },
1559 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1560 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1561 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1562 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1563 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1564 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1565 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1566 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1567 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1568 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1569 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1570 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1571 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1572 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1573 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1575 { EL_UNDEFINED, MV_NONE }
1578 static struct XY xy_topdown[] =
1586 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1588 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1589 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1590 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1591 IS_JUST_CHANGING(x, y))
1593 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1595 // static variables for playfield scan mode (scanning forward or backward)
1596 static int playfield_scan_start_x = 0;
1597 static int playfield_scan_start_y = 0;
1598 static int playfield_scan_delta_x = 1;
1599 static int playfield_scan_delta_y = 1;
1601 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1602 (y) >= 0 && (y) <= lev_fieldy - 1; \
1603 (y) += playfield_scan_delta_y) \
1604 for ((x) = playfield_scan_start_x; \
1605 (x) >= 0 && (x) <= lev_fieldx - 1; \
1606 (x) += playfield_scan_delta_x)
1609 void DEBUG_SetMaximumDynamite(void)
1613 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1614 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1615 local_player->inventory_element[local_player->inventory_size++] =
1620 static void InitPlayfieldScanModeVars(void)
1622 if (game.use_reverse_scan_direction)
1624 playfield_scan_start_x = lev_fieldx - 1;
1625 playfield_scan_start_y = lev_fieldy - 1;
1627 playfield_scan_delta_x = -1;
1628 playfield_scan_delta_y = -1;
1632 playfield_scan_start_x = 0;
1633 playfield_scan_start_y = 0;
1635 playfield_scan_delta_x = 1;
1636 playfield_scan_delta_y = 1;
1640 static void InitPlayfieldScanMode(int mode)
1642 game.use_reverse_scan_direction =
1643 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1645 InitPlayfieldScanModeVars();
1648 static int get_move_delay_from_stepsize(int move_stepsize)
1651 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1653 // make sure that stepsize value is always a power of 2
1654 move_stepsize = (1 << log_2(move_stepsize));
1656 return TILEX / move_stepsize;
1659 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1662 int player_nr = player->index_nr;
1663 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1664 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1666 // do no immediately change move delay -- the player might just be moving
1667 player->move_delay_value_next = move_delay;
1669 // information if player can move must be set separately
1670 player->cannot_move = cannot_move;
1674 player->move_delay = game.initial_move_delay[player_nr];
1675 player->move_delay_value = game.initial_move_delay_value[player_nr];
1677 player->move_delay_value_next = -1;
1679 player->move_delay_reset_counter = 0;
1683 void GetPlayerConfig(void)
1685 GameFrameDelay = setup.game_frame_delay;
1687 if (!audio.sound_available)
1688 setup.sound_simple = FALSE;
1690 if (!audio.loops_available)
1691 setup.sound_loops = FALSE;
1693 if (!audio.music_available)
1694 setup.sound_music = FALSE;
1696 if (!video.fullscreen_available)
1697 setup.fullscreen = FALSE;
1699 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1701 SetAudioMode(setup.sound);
1704 int GetElementFromGroupElement(int element)
1706 if (IS_GROUP_ELEMENT(element))
1708 struct ElementGroupInfo *group = element_info[element].group;
1709 int last_anim_random_frame = gfx.anim_random_frame;
1712 if (group->choice_mode == ANIM_RANDOM)
1713 gfx.anim_random_frame = RND(group->num_elements_resolved);
1715 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1716 group->choice_mode, 0,
1719 if (group->choice_mode == ANIM_RANDOM)
1720 gfx.anim_random_frame = last_anim_random_frame;
1722 group->choice_pos++;
1724 element = group->element_resolved[element_pos];
1730 static void IncrementSokobanFieldsNeeded(void)
1732 if (level.sb_fields_needed)
1733 game.sokoban_fields_still_needed++;
1736 static void IncrementSokobanObjectsNeeded(void)
1738 if (level.sb_objects_needed)
1739 game.sokoban_objects_still_needed++;
1742 static void DecrementSokobanFieldsNeeded(void)
1744 if (game.sokoban_fields_still_needed > 0)
1745 game.sokoban_fields_still_needed--;
1748 static void DecrementSokobanObjectsNeeded(void)
1750 if (game.sokoban_objects_still_needed > 0)
1751 game.sokoban_objects_still_needed--;
1754 static void InitPlayerField(int x, int y, int element, boolean init_game)
1756 if (element == EL_SP_MURPHY)
1760 if (stored_player[0].present)
1762 Tile[x][y] = EL_SP_MURPHY_CLONE;
1768 stored_player[0].initial_element = element;
1769 stored_player[0].use_murphy = TRUE;
1771 if (!level.use_artwork_element[0])
1772 stored_player[0].artwork_element = EL_SP_MURPHY;
1775 Tile[x][y] = EL_PLAYER_1;
1781 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1782 int jx = player->jx, jy = player->jy;
1784 player->present = TRUE;
1786 player->block_last_field = (element == EL_SP_MURPHY ?
1787 level.sp_block_last_field :
1788 level.block_last_field);
1790 // ---------- initialize player's last field block delay ------------------
1792 // always start with reliable default value (no adjustment needed)
1793 player->block_delay_adjustment = 0;
1795 // special case 1: in Supaplex, Murphy blocks last field one more frame
1796 if (player->block_last_field && element == EL_SP_MURPHY)
1797 player->block_delay_adjustment = 1;
1799 // special case 2: in game engines before 3.1.1, blocking was different
1800 if (game.use_block_last_field_bug)
1801 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1803 if (!network.enabled || player->connected_network)
1805 player->active = TRUE;
1807 // remove potentially duplicate players
1808 if (IN_LEV_FIELD(jx, jy) && StorePlayer[jx][jy] == Tile[x][y])
1809 StorePlayer[jx][jy] = 0;
1811 StorePlayer[x][y] = Tile[x][y];
1813 #if DEBUG_INIT_PLAYER
1814 Debug("game:init:player", "- player element %d activated",
1815 player->element_nr);
1816 Debug("game:init:player", " (local player is %d and currently %s)",
1817 local_player->element_nr,
1818 local_player->active ? "active" : "not active");
1822 Tile[x][y] = EL_EMPTY;
1824 player->jx = player->last_jx = x;
1825 player->jy = player->last_jy = y;
1828 // always check if player was just killed and should be reanimated
1830 int player_nr = GET_PLAYER_NR(element);
1831 struct PlayerInfo *player = &stored_player[player_nr];
1833 if (player->active && player->killed)
1834 player->reanimated = TRUE; // if player was just killed, reanimate him
1838 static void InitField(int x, int y, boolean init_game)
1840 int element = Tile[x][y];
1849 InitPlayerField(x, y, element, init_game);
1852 case EL_SOKOBAN_FIELD_PLAYER:
1853 element = Tile[x][y] = EL_PLAYER_1;
1854 InitField(x, y, init_game);
1856 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1857 InitField(x, y, init_game);
1860 case EL_SOKOBAN_FIELD_EMPTY:
1861 IncrementSokobanFieldsNeeded();
1864 case EL_SOKOBAN_OBJECT:
1865 IncrementSokobanObjectsNeeded();
1869 if (x < lev_fieldx - 1 && Tile[x + 1][y] == EL_ACID)
1870 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1871 else if (x > 0 && Tile[x - 1][y] == EL_ACID)
1872 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1873 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPLEFT)
1874 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1875 else if (y > 0 && Tile[x][y - 1] == EL_ACID)
1876 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1877 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPRIGHT)
1878 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1887 case EL_SPACESHIP_RIGHT:
1888 case EL_SPACESHIP_UP:
1889 case EL_SPACESHIP_LEFT:
1890 case EL_SPACESHIP_DOWN:
1891 case EL_BD_BUTTERFLY:
1892 case EL_BD_BUTTERFLY_RIGHT:
1893 case EL_BD_BUTTERFLY_UP:
1894 case EL_BD_BUTTERFLY_LEFT:
1895 case EL_BD_BUTTERFLY_DOWN:
1897 case EL_BD_FIREFLY_RIGHT:
1898 case EL_BD_FIREFLY_UP:
1899 case EL_BD_FIREFLY_LEFT:
1900 case EL_BD_FIREFLY_DOWN:
1901 case EL_PACMAN_RIGHT:
1903 case EL_PACMAN_LEFT:
1904 case EL_PACMAN_DOWN:
1906 case EL_YAMYAM_LEFT:
1907 case EL_YAMYAM_RIGHT:
1909 case EL_YAMYAM_DOWN:
1910 case EL_DARK_YAMYAM:
1913 case EL_SP_SNIKSNAK:
1914 case EL_SP_ELECTRON:
1920 case EL_SPRING_LEFT:
1921 case EL_SPRING_RIGHT:
1925 case EL_AMOEBA_FULL:
1930 case EL_AMOEBA_DROP:
1931 if (y == lev_fieldy - 1)
1933 Tile[x][y] = EL_AMOEBA_GROWING;
1934 Store[x][y] = EL_AMOEBA_WET;
1938 case EL_DYNAMITE_ACTIVE:
1939 case EL_SP_DISK_RED_ACTIVE:
1940 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1941 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1942 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1943 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1944 MovDelay[x][y] = 96;
1947 case EL_EM_DYNAMITE_ACTIVE:
1948 MovDelay[x][y] = 32;
1952 game.lights_still_needed++;
1956 game.friends_still_needed++;
1961 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1964 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1965 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1966 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1967 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1968 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1969 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1970 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1971 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1972 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1973 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1974 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1975 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1978 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1979 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1980 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1982 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1984 game.belt_dir[belt_nr] = belt_dir;
1985 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1987 else // more than one switch -- set it like the first switch
1989 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1994 case EL_LIGHT_SWITCH_ACTIVE:
1996 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1999 case EL_INVISIBLE_STEELWALL:
2000 case EL_INVISIBLE_WALL:
2001 case EL_INVISIBLE_SAND:
2002 if (game.light_time_left > 0 ||
2003 game.lenses_time_left > 0)
2004 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
2007 case EL_EMC_MAGIC_BALL:
2008 if (game.ball_active)
2009 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
2012 case EL_EMC_MAGIC_BALL_SWITCH:
2013 if (game.ball_active)
2014 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
2017 case EL_TRIGGER_PLAYER:
2018 case EL_TRIGGER_ELEMENT:
2019 case EL_TRIGGER_CE_VALUE:
2020 case EL_TRIGGER_CE_SCORE:
2022 case EL_ANY_ELEMENT:
2023 case EL_CURRENT_CE_VALUE:
2024 case EL_CURRENT_CE_SCORE:
2041 // reference elements should not be used on the playfield
2042 Tile[x][y] = EL_EMPTY;
2046 if (IS_CUSTOM_ELEMENT(element))
2048 if (CAN_MOVE(element))
2051 if (!element_info[element].use_last_ce_value || init_game)
2052 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2054 else if (IS_GROUP_ELEMENT(element))
2056 Tile[x][y] = GetElementFromGroupElement(element);
2058 InitField(x, y, init_game);
2060 else if (IS_EMPTY_ELEMENT(element))
2062 GfxElementEmpty[x][y] = element;
2063 Tile[x][y] = EL_EMPTY;
2065 if (element_info[element].use_gfx_element)
2066 game.use_masked_elements = TRUE;
2073 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2076 static void InitField_WithBug1(int x, int y, boolean init_game)
2078 InitField(x, y, init_game);
2080 // not needed to call InitMovDir() -- already done by InitField()!
2081 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2082 CAN_MOVE(Tile[x][y]))
2086 static void InitField_WithBug2(int x, int y, boolean init_game)
2088 int old_element = Tile[x][y];
2090 InitField(x, y, init_game);
2092 // not needed to call InitMovDir() -- already done by InitField()!
2093 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2094 CAN_MOVE(old_element) &&
2095 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2098 /* this case is in fact a combination of not less than three bugs:
2099 first, it calls InitMovDir() for elements that can move, although this is
2100 already done by InitField(); then, it checks the element that was at this
2101 field _before_ the call to InitField() (which can change it); lastly, it
2102 was not called for "mole with direction" elements, which were treated as
2103 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2107 static int get_key_element_from_nr(int key_nr)
2109 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2110 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2111 EL_EM_KEY_1 : EL_KEY_1);
2113 return key_base_element + key_nr;
2116 static int get_next_dropped_element(struct PlayerInfo *player)
2118 return (player->inventory_size > 0 ?
2119 player->inventory_element[player->inventory_size - 1] :
2120 player->inventory_infinite_element != EL_UNDEFINED ?
2121 player->inventory_infinite_element :
2122 player->dynabombs_left > 0 ?
2123 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2127 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2129 // pos >= 0: get element from bottom of the stack;
2130 // pos < 0: get element from top of the stack
2134 int min_inventory_size = -pos;
2135 int inventory_pos = player->inventory_size - min_inventory_size;
2136 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2138 return (player->inventory_size >= min_inventory_size ?
2139 player->inventory_element[inventory_pos] :
2140 player->inventory_infinite_element != EL_UNDEFINED ?
2141 player->inventory_infinite_element :
2142 player->dynabombs_left >= min_dynabombs_left ?
2143 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2148 int min_dynabombs_left = pos + 1;
2149 int min_inventory_size = pos + 1 - player->dynabombs_left;
2150 int inventory_pos = pos - player->dynabombs_left;
2152 return (player->inventory_infinite_element != EL_UNDEFINED ?
2153 player->inventory_infinite_element :
2154 player->dynabombs_left >= min_dynabombs_left ?
2155 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2156 player->inventory_size >= min_inventory_size ?
2157 player->inventory_element[inventory_pos] :
2162 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2164 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2165 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2168 if (gpo1->sort_priority != gpo2->sort_priority)
2169 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2171 compare_result = gpo1->nr - gpo2->nr;
2173 return compare_result;
2176 int getPlayerInventorySize(int player_nr)
2178 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2179 return game_em.ply[player_nr]->dynamite;
2180 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2181 return game_sp.red_disk_count;
2183 return stored_player[player_nr].inventory_size;
2186 static void InitGameControlValues(void)
2190 for (i = 0; game_panel_controls[i].nr != -1; i++)
2192 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2193 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2194 struct TextPosInfo *pos = gpc->pos;
2196 int type = gpc->type;
2200 Error("'game_panel_controls' structure corrupted at %d", i);
2202 Fail("this should not happen -- please debug");
2205 // force update of game controls after initialization
2206 gpc->value = gpc->last_value = -1;
2207 gpc->frame = gpc->last_frame = -1;
2208 gpc->gfx_frame = -1;
2210 // determine panel value width for later calculation of alignment
2211 if (type == TYPE_INTEGER || type == TYPE_STRING)
2213 pos->width = pos->size * getFontWidth(pos->font);
2214 pos->height = getFontHeight(pos->font);
2216 else if (type == TYPE_ELEMENT)
2218 pos->width = pos->size;
2219 pos->height = pos->size;
2222 // fill structure for game panel draw order
2224 gpo->sort_priority = pos->sort_priority;
2227 // sort game panel controls according to sort_priority and control number
2228 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2229 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2232 static void UpdatePlayfieldElementCount(void)
2234 boolean use_element_count = FALSE;
2237 // first check if it is needed at all to calculate playfield element count
2238 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2239 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2240 use_element_count = TRUE;
2242 if (!use_element_count)
2245 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2246 element_info[i].element_count = 0;
2248 SCAN_PLAYFIELD(x, y)
2250 element_info[Tile[x][y]].element_count++;
2253 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2254 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2255 if (IS_IN_GROUP(j, i))
2256 element_info[EL_GROUP_START + i].element_count +=
2257 element_info[j].element_count;
2260 static void UpdateGameControlValues(void)
2263 int time = (game.LevelSolved ?
2264 game.LevelSolved_CountingTime :
2265 level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2266 game_bd.time_played :
2267 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2269 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2270 game_sp.time_played :
2271 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2272 game_mm.energy_left :
2273 game.no_level_time_limit ? TimePlayed : TimeLeft);
2274 int score = (game.LevelSolved ?
2275 game.LevelSolved_CountingScore :
2276 level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2278 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2279 game_em.lev->score :
2280 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2282 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2285 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2286 game_bd.gems_still_needed :
2287 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2288 game_em.lev->gems_needed :
2289 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2290 game_sp.infotrons_still_needed :
2291 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2292 game_mm.kettles_still_needed :
2293 game.gems_still_needed);
2294 int gems_needed = level.gems_needed;
2295 int gems_collected = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2296 game_bd.game->cave->diamonds_collected :
2297 gems_needed - gems);
2298 int gems_score = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2299 game_bd.game->cave->diamond_value :
2300 level.score[SC_EMERALD]);
2301 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2302 game_bd.gems_still_needed > 0 :
2303 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2304 game_em.lev->gems_needed > 0 :
2305 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2306 game_sp.infotrons_still_needed > 0 :
2307 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2308 game_mm.kettles_still_needed > 0 ||
2309 game_mm.lights_still_needed > 0 :
2310 game.gems_still_needed > 0 ||
2311 game.sokoban_fields_still_needed > 0 ||
2312 game.sokoban_objects_still_needed > 0 ||
2313 game.lights_still_needed > 0);
2314 int health = (game.LevelSolved ?
2315 game.LevelSolved_CountingHealth :
2316 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2317 MM_HEALTH(game_mm.laser_overload_value) :
2319 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2321 UpdatePlayfieldElementCount();
2323 // update game panel control values
2325 // used instead of "level_nr" (for network games)
2326 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2327 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2328 game_panel_controls[GAME_PANEL_GEMS_NEEDED].value = gems_needed;
2329 game_panel_controls[GAME_PANEL_GEMS_COLLECTED].value = gems_collected;
2330 game_panel_controls[GAME_PANEL_GEMS_SCORE].value = gems_score;
2332 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2333 for (i = 0; i < MAX_NUM_KEYS; i++)
2334 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2335 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2336 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2338 if (game.centered_player_nr == -1)
2340 for (i = 0; i < MAX_PLAYERS; i++)
2342 // only one player in Supaplex game engine
2343 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2346 for (k = 0; k < MAX_NUM_KEYS; k++)
2348 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2350 if (game_em.ply[i]->keys & (1 << k))
2351 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2352 get_key_element_from_nr(k);
2354 else if (stored_player[i].key[k])
2355 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2356 get_key_element_from_nr(k);
2359 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2360 getPlayerInventorySize(i);
2362 if (stored_player[i].num_white_keys > 0)
2363 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2366 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2367 stored_player[i].num_white_keys;
2372 int player_nr = game.centered_player_nr;
2374 for (k = 0; k < MAX_NUM_KEYS; k++)
2376 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2378 if (game_em.ply[player_nr]->keys & (1 << k))
2379 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2380 get_key_element_from_nr(k);
2382 else if (stored_player[player_nr].key[k])
2383 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2384 get_key_element_from_nr(k);
2387 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2388 getPlayerInventorySize(player_nr);
2390 if (stored_player[player_nr].num_white_keys > 0)
2391 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2393 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2394 stored_player[player_nr].num_white_keys;
2397 // re-arrange keys on game panel, if needed or if defined by style settings
2398 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2400 int nr = GAME_PANEL_KEY_1 + i;
2401 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2402 struct TextPosInfo *pos = gpc->pos;
2404 // skip check if key is not in the player's inventory
2405 if (gpc->value == EL_EMPTY)
2408 // check if keys should be arranged on panel from left to right
2409 if (pos->style == STYLE_LEFTMOST_POSITION)
2411 // check previous key positions (left from current key)
2412 for (k = 0; k < i; k++)
2414 int nr_new = GAME_PANEL_KEY_1 + k;
2416 if (game_panel_controls[nr_new].value == EL_EMPTY)
2418 game_panel_controls[nr_new].value = gpc->value;
2419 gpc->value = EL_EMPTY;
2426 // check if "undefined" keys can be placed at some other position
2427 if (pos->x == -1 && pos->y == -1)
2429 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2431 // 1st try: display key at the same position as normal or EM keys
2432 if (game_panel_controls[nr_new].value == EL_EMPTY)
2434 game_panel_controls[nr_new].value = gpc->value;
2438 // 2nd try: display key at the next free position in the key panel
2439 for (k = 0; k < STD_NUM_KEYS; k++)
2441 nr_new = GAME_PANEL_KEY_1 + k;
2443 if (game_panel_controls[nr_new].value == EL_EMPTY)
2445 game_panel_controls[nr_new].value = gpc->value;
2454 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2456 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2457 get_inventory_element_from_pos(local_player, i);
2458 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2459 get_inventory_element_from_pos(local_player, -i - 1);
2462 game_panel_controls[GAME_PANEL_SCORE].value = score;
2463 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2465 game_panel_controls[GAME_PANEL_TIME].value = time;
2467 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2468 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2469 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2471 if (level.time == 0)
2472 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2474 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2476 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2477 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2479 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2481 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2482 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2484 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2485 local_player->shield_normal_time_left;
2486 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2487 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2489 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2490 local_player->shield_deadly_time_left;
2492 game_panel_controls[GAME_PANEL_EXIT].value =
2493 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2495 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2496 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2497 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2498 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2499 EL_EMC_MAGIC_BALL_SWITCH);
2501 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2502 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2503 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2504 game.light_time_left;
2506 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2507 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2508 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2509 game.timegate_time_left;
2511 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2512 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2514 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2515 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2516 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2517 game.lenses_time_left;
2519 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2520 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2521 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2522 game.magnify_time_left;
2524 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2525 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2526 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2527 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2528 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2529 EL_BALLOON_SWITCH_NONE);
2531 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2532 local_player->dynabomb_count;
2533 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2534 local_player->dynabomb_size;
2535 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2536 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2538 game_panel_controls[GAME_PANEL_PENGUINS].value =
2539 game.friends_still_needed;
2541 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2542 game.sokoban_objects_still_needed;
2543 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2544 game.sokoban_fields_still_needed;
2546 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2547 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2549 for (i = 0; i < NUM_BELTS; i++)
2551 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2552 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2553 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2554 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2555 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2558 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2559 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2560 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2561 game.magic_wall_time_left;
2563 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2564 local_player->gravity;
2566 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2567 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2569 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2570 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2571 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2572 game.panel.element[i].id : EL_UNDEFINED);
2574 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2575 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2576 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2577 element_info[game.panel.element_count[i].id].element_count : 0);
2579 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2580 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2581 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2582 element_info[game.panel.ce_score[i].id].collect_score : 0);
2584 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2585 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2586 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2587 element_info[game.panel.ce_score_element[i].id].collect_score :
2590 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2591 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2592 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2594 // update game panel control frames
2596 for (i = 0; game_panel_controls[i].nr != -1; i++)
2598 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2600 if (gpc->type == TYPE_ELEMENT)
2602 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2604 int last_anim_random_frame = gfx.anim_random_frame;
2605 int element = gpc->value;
2606 int graphic = el2panelimg(element);
2607 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2609 graphic_info[graphic].anim_global_anim_sync ?
2610 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2612 if (gpc->value != gpc->last_value)
2615 gpc->gfx_random = init_gfx_random;
2621 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2622 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2623 gpc->gfx_random = init_gfx_random;
2626 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2627 gfx.anim_random_frame = gpc->gfx_random;
2629 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2630 gpc->gfx_frame = element_info[element].collect_score;
2632 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2634 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2635 gfx.anim_random_frame = last_anim_random_frame;
2638 else if (gpc->type == TYPE_GRAPHIC)
2640 if (gpc->graphic != IMG_UNDEFINED)
2642 int last_anim_random_frame = gfx.anim_random_frame;
2643 int graphic = gpc->graphic;
2644 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2646 graphic_info[graphic].anim_global_anim_sync ?
2647 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2649 if (gpc->value != gpc->last_value)
2652 gpc->gfx_random = init_gfx_random;
2658 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2659 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2660 gpc->gfx_random = init_gfx_random;
2663 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2664 gfx.anim_random_frame = gpc->gfx_random;
2666 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2668 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2669 gfx.anim_random_frame = last_anim_random_frame;
2675 static void DisplayGameControlValues(void)
2677 boolean redraw_panel = FALSE;
2680 for (i = 0; game_panel_controls[i].nr != -1; i++)
2682 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2684 if (PANEL_DEACTIVATED(gpc->pos))
2687 if (gpc->value == gpc->last_value &&
2688 gpc->frame == gpc->last_frame)
2691 redraw_panel = TRUE;
2697 // copy default game door content to main double buffer
2699 // !!! CHECK AGAIN !!!
2700 SetPanelBackground();
2701 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2702 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2704 // redraw game control buttons
2705 RedrawGameButtons();
2707 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2709 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2711 int nr = game_panel_order[i].nr;
2712 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2713 struct TextPosInfo *pos = gpc->pos;
2714 int type = gpc->type;
2715 int value = gpc->value;
2716 int frame = gpc->frame;
2717 int size = pos->size;
2718 int font = pos->font;
2719 boolean draw_masked = pos->draw_masked;
2720 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2722 if (PANEL_DEACTIVATED(pos))
2725 if (pos->class == get_hash_from_string("extra_panel_items") &&
2726 !setup.prefer_extra_panel_items)
2729 gpc->last_value = value;
2730 gpc->last_frame = frame;
2732 if (type == TYPE_INTEGER)
2734 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2735 nr == GAME_PANEL_INVENTORY_COUNT ||
2736 nr == GAME_PANEL_SCORE ||
2737 nr == GAME_PANEL_HIGHSCORE ||
2738 nr == GAME_PANEL_TIME)
2740 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2742 if (use_dynamic_size) // use dynamic number of digits
2744 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 :
2745 nr == GAME_PANEL_INVENTORY_COUNT ||
2746 nr == GAME_PANEL_TIME ? 1000 : 100000);
2747 int size_add = (nr == GAME_PANEL_LEVEL_NUMBER ||
2748 nr == GAME_PANEL_INVENTORY_COUNT ||
2749 nr == GAME_PANEL_TIME ? 1 : 2);
2750 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 :
2751 nr == GAME_PANEL_INVENTORY_COUNT ||
2752 nr == GAME_PANEL_TIME ? 3 : 5);
2753 int size2 = size1 + size_add;
2754 int font1 = pos->font;
2755 int font2 = pos->font_alt;
2757 size = (value < value_change ? size1 : size2);
2758 font = (value < value_change ? font1 : font2);
2762 // correct text size if "digits" is zero or less
2764 size = strlen(int2str(value, size));
2766 // dynamically correct text alignment
2767 pos->width = size * getFontWidth(font);
2769 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2770 int2str(value, size), font, mask_mode);
2772 else if (type == TYPE_ELEMENT)
2774 int element, graphic;
2778 int dst_x = PANEL_XPOS(pos);
2779 int dst_y = PANEL_YPOS(pos);
2781 if (value != EL_UNDEFINED && value != EL_EMPTY)
2784 graphic = el2panelimg(value);
2787 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2788 element, EL_NAME(element), size);
2791 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2794 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2797 width = graphic_info[graphic].width * size / TILESIZE;
2798 height = graphic_info[graphic].height * size / TILESIZE;
2801 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2804 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2808 else if (type == TYPE_GRAPHIC)
2810 int graphic = gpc->graphic;
2811 int graphic_active = gpc->graphic_active;
2815 int dst_x = PANEL_XPOS(pos);
2816 int dst_y = PANEL_YPOS(pos);
2817 boolean skip = (pos->class == get_hash_from_string("mm_engine_only") &&
2818 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2820 if (graphic != IMG_UNDEFINED && !skip)
2822 if (pos->style == STYLE_REVERSE)
2823 value = 100 - value;
2825 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2827 if (pos->direction & MV_HORIZONTAL)
2829 width = graphic_info[graphic_active].width * value / 100;
2830 height = graphic_info[graphic_active].height;
2832 if (pos->direction == MV_LEFT)
2834 src_x += graphic_info[graphic_active].width - width;
2835 dst_x += graphic_info[graphic_active].width - width;
2840 width = graphic_info[graphic_active].width;
2841 height = graphic_info[graphic_active].height * value / 100;
2843 if (pos->direction == MV_UP)
2845 src_y += graphic_info[graphic_active].height - height;
2846 dst_y += graphic_info[graphic_active].height - height;
2851 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2854 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2857 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2859 if (pos->direction & MV_HORIZONTAL)
2861 if (pos->direction == MV_RIGHT)
2868 dst_x = PANEL_XPOS(pos);
2871 width = graphic_info[graphic].width - width;
2875 if (pos->direction == MV_DOWN)
2882 dst_y = PANEL_YPOS(pos);
2885 height = graphic_info[graphic].height - height;
2889 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2892 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2896 else if (type == TYPE_STRING)
2898 boolean active = (value != 0);
2899 char *state_normal = "off";
2900 char *state_active = "on";
2901 char *state = (active ? state_active : state_normal);
2902 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2903 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2904 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2905 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2907 if (nr == GAME_PANEL_GRAVITY_STATE)
2909 int font1 = pos->font; // (used for normal state)
2910 int font2 = pos->font_alt; // (used for active state)
2912 font = (active ? font2 : font1);
2921 // don't truncate output if "chars" is zero or less
2924 // dynamically correct text alignment
2925 pos->width = size * getFontWidth(font);
2928 s_cut = getStringCopyN(s, size);
2930 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2931 s_cut, font, mask_mode);
2937 redraw_mask |= REDRAW_DOOR_1;
2940 SetGameStatus(GAME_MODE_PLAYING);
2943 void UpdateAndDisplayGameControlValues(void)
2945 if (tape.deactivate_display)
2948 UpdateGameControlValues();
2949 DisplayGameControlValues();
2952 void UpdateGameDoorValues(void)
2954 UpdateGameControlValues();
2957 void DrawGameDoorValues(void)
2959 DisplayGameControlValues();
2963 // ============================================================================
2965 // ----------------------------------------------------------------------------
2966 // initialize game engine due to level / tape version number
2967 // ============================================================================
2969 static void InitGameEngine(void)
2971 int i, j, k, l, x, y;
2973 // set game engine from tape file when re-playing, else from level file
2974 game.engine_version = (tape.playing ? tape.engine_version :
2975 level.game_version);
2977 // set single or multi-player game mode (needed for re-playing tapes)
2978 game.team_mode = setup.team_mode;
2982 int num_players = 0;
2984 for (i = 0; i < MAX_PLAYERS; i++)
2985 if (tape.player_participates[i])
2988 // multi-player tapes contain input data for more than one player
2989 game.team_mode = (num_players > 1);
2993 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2994 level.game_version);
2995 Debug("game:init:level", " tape.file_version == %06d",
2997 Debug("game:init:level", " tape.game_version == %06d",
2999 Debug("game:init:level", " tape.engine_version == %06d",
3000 tape.engine_version);
3001 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
3002 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
3005 // --------------------------------------------------------------------------
3006 // set flags for bugs and changes according to active game engine version
3007 // --------------------------------------------------------------------------
3011 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
3013 Bug was introduced in version:
3016 Bug was fixed in version:
3020 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
3021 but the property "can fall" was missing, which caused some levels to be
3022 unsolvable. This was fixed in version 4.2.0.0.
3024 Affected levels/tapes:
3025 An example for a tape that was fixed by this bugfix is tape 029 from the
3026 level set "rnd_sam_bateman".
3027 The wrong behaviour will still be used for all levels or tapes that were
3028 created/recorded with it. An example for this is tape 023 from the level
3029 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
3032 boolean use_amoeba_dropping_cannot_fall_bug =
3033 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
3034 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
3036 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3037 tape.game_version < VERSION_IDENT(4,2,0,0)));
3040 Summary of bugfix/change:
3041 Fixed move speed of elements entering or leaving magic wall.
3043 Fixed/changed in version:
3047 Before 2.0.1, move speed of elements entering or leaving magic wall was
3048 twice as fast as it is now.
3049 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
3051 Affected levels/tapes:
3052 The first condition is generally needed for all levels/tapes before version
3053 2.0.1, which might use the old behaviour before it was changed; known tapes
3054 that are affected: Tape 014 from the level set "rnd_conor_mancone".
3055 The second condition is an exception from the above case and is needed for
3056 the special case of tapes recorded with game (not engine!) version 2.0.1 or
3057 above, but before it was known that this change would break tapes like the
3058 above and was fixed in 4.2.0.0, so that the changed behaviour was active
3059 although the engine version while recording maybe was before 2.0.1. There
3060 are a lot of tapes that are affected by this exception, like tape 006 from
3061 the level set "rnd_conor_mancone".
3064 boolean use_old_move_stepsize_for_magic_wall =
3065 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
3067 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3068 tape.game_version < VERSION_IDENT(4,2,0,0)));
3071 Summary of bugfix/change:
3072 Fixed handling for custom elements that change when pushed by the player.
3074 Fixed/changed in version:
3078 Before 3.1.0, custom elements that "change when pushing" changed directly
3079 after the player started pushing them (until then handled in "DigField()").
3080 Since 3.1.0, these custom elements are not changed until the "pushing"
3081 move of the element is finished (now handled in "ContinueMoving()").
3083 Affected levels/tapes:
3084 The first condition is generally needed for all levels/tapes before version
3085 3.1.0, which might use the old behaviour before it was changed; known tapes
3086 that are affected are some tapes from the level set "Walpurgis Gardens" by
3088 The second condition is an exception from the above case and is needed for
3089 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3090 above (including some development versions of 3.1.0), but before it was
3091 known that this change would break tapes like the above and was fixed in
3092 3.1.1, so that the changed behaviour was active although the engine version
3093 while recording maybe was before 3.1.0. There is at least one tape that is
3094 affected by this exception, which is the tape for the one-level set "Bug
3095 Machine" by Juergen Bonhagen.
3098 game.use_change_when_pushing_bug =
3099 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3101 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3102 tape.game_version < VERSION_IDENT(3,1,1,0)));
3105 Summary of bugfix/change:
3106 Fixed handling for blocking the field the player leaves when moving.
3108 Fixed/changed in version:
3112 Before 3.1.1, when "block last field when moving" was enabled, the field
3113 the player is leaving when moving was blocked for the time of the move,
3114 and was directly unblocked afterwards. This resulted in the last field
3115 being blocked for exactly one less than the number of frames of one player
3116 move. Additionally, even when blocking was disabled, the last field was
3117 blocked for exactly one frame.
3118 Since 3.1.1, due to changes in player movement handling, the last field
3119 is not blocked at all when blocking is disabled. When blocking is enabled,
3120 the last field is blocked for exactly the number of frames of one player
3121 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3122 last field is blocked for exactly one more than the number of frames of
3125 Affected levels/tapes:
3126 (!!! yet to be determined -- probably many !!!)
3129 game.use_block_last_field_bug =
3130 (game.engine_version < VERSION_IDENT(3,1,1,0));
3132 /* various special flags and settings for native Emerald Mine game engine */
3134 game_em.use_single_button =
3135 (game.engine_version > VERSION_IDENT(4,0,0,2));
3137 game_em.use_push_delay =
3138 (game.engine_version > VERSION_IDENT(4,3,7,1));
3140 game_em.use_snap_key_bug =
3141 (game.engine_version < VERSION_IDENT(4,0,1,0));
3143 game_em.use_random_bug =
3144 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3146 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3148 game_em.use_old_explosions = use_old_em_engine;
3149 game_em.use_old_android = use_old_em_engine;
3150 game_em.use_old_push_elements = use_old_em_engine;
3151 game_em.use_old_push_into_acid = use_old_em_engine;
3153 game_em.use_wrap_around = !use_old_em_engine;
3155 // --------------------------------------------------------------------------
3157 // set maximal allowed number of custom element changes per game frame
3158 game.max_num_changes_per_frame = 1;
3160 // default scan direction: scan playfield from top/left to bottom/right
3161 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3163 // dynamically adjust element properties according to game engine version
3164 InitElementPropertiesEngine(game.engine_version);
3166 // ---------- initialize special element properties -------------------------
3168 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3169 if (use_amoeba_dropping_cannot_fall_bug)
3170 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3172 // ---------- initialize player's initial move delay ------------------------
3174 // dynamically adjust player properties according to level information
3175 for (i = 0; i < MAX_PLAYERS; i++)
3176 game.initial_move_delay_value[i] =
3177 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3179 // dynamically adjust player properties according to game engine version
3180 for (i = 0; i < MAX_PLAYERS; i++)
3181 game.initial_move_delay[i] =
3182 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3183 game.initial_move_delay_value[i] : 0);
3185 // ---------- initialize player's initial push delay ------------------------
3187 // dynamically adjust player properties according to game engine version
3188 game.initial_push_delay_value =
3189 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3191 // ---------- initialize changing elements ----------------------------------
3193 // initialize changing elements information
3194 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3196 struct ElementInfo *ei = &element_info[i];
3198 // this pointer might have been changed in the level editor
3199 ei->change = &ei->change_page[0];
3201 if (!IS_CUSTOM_ELEMENT(i))
3203 ei->change->target_element = EL_EMPTY_SPACE;
3204 ei->change->delay_fixed = 0;
3205 ei->change->delay_random = 0;
3206 ei->change->delay_frames = 1;
3209 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3211 ei->has_change_event[j] = FALSE;
3213 ei->event_page_nr[j] = 0;
3214 ei->event_page[j] = &ei->change_page[0];
3218 // add changing elements from pre-defined list
3219 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3221 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3222 struct ElementInfo *ei = &element_info[ch_delay->element];
3224 ei->change->target_element = ch_delay->target_element;
3225 ei->change->delay_fixed = ch_delay->change_delay;
3227 ei->change->pre_change_function = ch_delay->pre_change_function;
3228 ei->change->change_function = ch_delay->change_function;
3229 ei->change->post_change_function = ch_delay->post_change_function;
3231 ei->change->can_change = TRUE;
3232 ei->change->can_change_or_has_action = TRUE;
3234 ei->has_change_event[CE_DELAY] = TRUE;
3236 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3237 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3240 // ---------- initialize if element can trigger global animations -----------
3242 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3244 struct ElementInfo *ei = &element_info[i];
3246 ei->has_anim_event = FALSE;
3249 InitGlobalAnimEventsForCustomElements();
3251 // ---------- initialize internal run-time variables ------------------------
3253 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3255 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3257 for (j = 0; j < ei->num_change_pages; j++)
3259 ei->change_page[j].can_change_or_has_action =
3260 (ei->change_page[j].can_change |
3261 ei->change_page[j].has_action);
3265 // add change events from custom element configuration
3266 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3268 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3270 for (j = 0; j < ei->num_change_pages; j++)
3272 if (!ei->change_page[j].can_change_or_has_action)
3275 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3277 // only add event page for the first page found with this event
3278 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3280 ei->has_change_event[k] = TRUE;
3282 ei->event_page_nr[k] = j;
3283 ei->event_page[k] = &ei->change_page[j];
3289 // ---------- initialize reference elements in change conditions ------------
3291 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3293 int element = EL_CUSTOM_START + i;
3294 struct ElementInfo *ei = &element_info[element];
3296 for (j = 0; j < ei->num_change_pages; j++)
3298 int trigger_element = ei->change_page[j].initial_trigger_element;
3300 if (trigger_element >= EL_PREV_CE_8 &&
3301 trigger_element <= EL_NEXT_CE_8)
3302 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3304 ei->change_page[j].trigger_element = trigger_element;
3308 // ---------- initialize run-time trigger player and element ----------------
3310 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3312 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3314 for (j = 0; j < ei->num_change_pages; j++)
3316 struct ElementChangeInfo *change = &ei->change_page[j];
3318 change->actual_trigger_element = EL_EMPTY;
3319 change->actual_trigger_player = EL_EMPTY;
3320 change->actual_trigger_player_bits = CH_PLAYER_NONE;
3321 change->actual_trigger_side = CH_SIDE_NONE;
3322 change->actual_trigger_ce_value = 0;
3323 change->actual_trigger_ce_score = 0;
3324 change->actual_trigger_x = -1;
3325 change->actual_trigger_y = -1;
3329 // ---------- initialize trigger events -------------------------------------
3331 // initialize trigger events information
3332 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3333 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3334 trigger_events[i][j] = FALSE;
3336 // add trigger events from element change event properties
3337 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3339 struct ElementInfo *ei = &element_info[i];
3341 for (j = 0; j < ei->num_change_pages; j++)
3343 struct ElementChangeInfo *change = &ei->change_page[j];
3345 if (!change->can_change_or_has_action)
3348 if (change->has_event[CE_BY_OTHER_ACTION])
3350 int trigger_element = change->trigger_element;
3352 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3354 if (change->has_event[k])
3356 if (IS_GROUP_ELEMENT(trigger_element))
3358 struct ElementGroupInfo *group =
3359 element_info[trigger_element].group;
3361 for (l = 0; l < group->num_elements_resolved; l++)
3362 trigger_events[group->element_resolved[l]][k] = TRUE;
3364 else if (trigger_element == EL_ANY_ELEMENT)
3365 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3366 trigger_events[l][k] = TRUE;
3368 trigger_events[trigger_element][k] = TRUE;
3375 // ---------- initialize push delay -----------------------------------------
3377 // initialize push delay values to default
3378 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3380 if (!IS_CUSTOM_ELEMENT(i))
3382 // set default push delay values (corrected since version 3.0.7-1)
3383 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3385 element_info[i].push_delay_fixed = 2;
3386 element_info[i].push_delay_random = 8;
3390 element_info[i].push_delay_fixed = 8;
3391 element_info[i].push_delay_random = 8;
3396 // set push delay value for certain elements from pre-defined list
3397 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3399 int e = push_delay_list[i].element;
3401 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3402 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3405 // set push delay value for Supaplex elements for newer engine versions
3406 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3408 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3410 if (IS_SP_ELEMENT(i))
3412 // set SP push delay to just enough to push under a falling zonk
3413 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3415 element_info[i].push_delay_fixed = delay;
3416 element_info[i].push_delay_random = 0;
3421 // ---------- initialize move stepsize --------------------------------------
3423 // initialize move stepsize values to default
3424 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3425 if (!IS_CUSTOM_ELEMENT(i))
3426 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3428 // set move stepsize value for certain elements from pre-defined list
3429 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3431 int e = move_stepsize_list[i].element;
3433 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3435 // set move stepsize value for certain elements for older engine versions
3436 if (use_old_move_stepsize_for_magic_wall)
3438 if (e == EL_MAGIC_WALL_FILLING ||
3439 e == EL_MAGIC_WALL_EMPTYING ||
3440 e == EL_BD_MAGIC_WALL_FILLING ||
3441 e == EL_BD_MAGIC_WALL_EMPTYING)
3442 element_info[e].move_stepsize *= 2;
3446 // ---------- initialize collect score --------------------------------------
3448 // initialize collect score values for custom elements from initial value
3449 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3450 if (IS_CUSTOM_ELEMENT(i))
3451 element_info[i].collect_score = element_info[i].collect_score_initial;
3453 // ---------- initialize collect count --------------------------------------
3455 // initialize collect count values for non-custom elements
3456 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3457 if (!IS_CUSTOM_ELEMENT(i))
3458 element_info[i].collect_count_initial = 0;
3460 // add collect count values for all elements from pre-defined list
3461 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3462 element_info[collect_count_list[i].element].collect_count_initial =
3463 collect_count_list[i].count;
3465 // ---------- initialize access direction -----------------------------------
3467 // initialize access direction values to default (access from every side)
3468 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3469 if (!IS_CUSTOM_ELEMENT(i))
3470 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3472 // set access direction value for certain elements from pre-defined list
3473 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3474 element_info[access_direction_list[i].element].access_direction =
3475 access_direction_list[i].direction;
3477 // ---------- initialize explosion content ----------------------------------
3478 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3480 if (IS_CUSTOM_ELEMENT(i))
3483 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3485 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3487 element_info[i].content.e[x][y] =
3488 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3489 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3490 i == EL_PLAYER_3 ? EL_EMERALD :
3491 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3492 i == EL_MOLE ? EL_EMERALD_RED :
3493 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3494 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3495 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3496 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3497 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3498 i == EL_WALL_EMERALD ? EL_EMERALD :
3499 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3500 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3501 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3502 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3503 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3504 i == EL_WALL_PEARL ? EL_PEARL :
3505 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3510 // ---------- initialize recursion detection --------------------------------
3511 recursion_loop_depth = 0;
3512 recursion_loop_detected = FALSE;
3513 recursion_loop_element = EL_UNDEFINED;
3515 // ---------- initialize graphics engine ------------------------------------
3516 game.scroll_delay_value =
3517 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3518 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3519 !setup.forced_scroll_delay ? 0 :
3520 setup.scroll_delay ? setup.scroll_delay_value : 0);
3521 if (game.forced_scroll_delay_value == -1)
3522 game.scroll_delay_value =
3523 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3525 // ---------- initialize game engine snapshots ------------------------------
3526 for (i = 0; i < MAX_PLAYERS; i++)
3527 game.snapshot.last_action[i] = 0;
3528 game.snapshot.changed_action = FALSE;
3529 game.snapshot.collected_item = FALSE;
3530 game.snapshot.mode =
3531 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3532 SNAPSHOT_MODE_EVERY_STEP :
3533 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3534 SNAPSHOT_MODE_EVERY_MOVE :
3535 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3536 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3537 game.snapshot.save_snapshot = FALSE;
3539 // ---------- initialize level time for Supaplex engine ---------------------
3540 // Supaplex levels with time limit currently unsupported -- should be added
3541 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3544 // ---------- initialize flags for handling game actions --------------------
3546 // set flags for game actions to default values
3547 game.use_key_actions = TRUE;
3548 game.use_mouse_actions = FALSE;
3550 // when using Mirror Magic game engine, handle mouse events only
3551 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3553 game.use_key_actions = FALSE;
3554 game.use_mouse_actions = TRUE;
3557 // check for custom elements with mouse click events
3558 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3560 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3562 int element = EL_CUSTOM_START + i;
3564 if (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3565 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3566 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3567 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3568 game.use_mouse_actions = TRUE;
3573 static int get_num_special_action(int element, int action_first,
3576 int num_special_action = 0;
3579 for (i = action_first; i <= action_last; i++)
3581 boolean found = FALSE;
3583 for (j = 0; j < NUM_DIRECTIONS; j++)
3584 if (el_act_dir2img(element, i, j) !=
3585 el_act_dir2img(element, ACTION_DEFAULT, j))
3589 num_special_action++;
3594 return num_special_action;
3598 // ============================================================================
3600 // ----------------------------------------------------------------------------
3601 // initialize and start new game
3602 // ============================================================================
3604 #if DEBUG_INIT_PLAYER
3605 static void DebugPrintPlayerStatus(char *message)
3612 Debug("game:init:player", "%s:", message);
3614 for (i = 0; i < MAX_PLAYERS; i++)
3616 struct PlayerInfo *player = &stored_player[i];
3618 Debug("game:init:player",
3619 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3623 player->connected_locally,
3624 player->connected_network,
3626 (local_player == player ? " (local player)" : ""));
3633 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3634 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3635 int fade_mask = REDRAW_FIELD;
3636 boolean restarting = (game_status == GAME_MODE_PLAYING);
3637 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3638 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3639 int initial_move_dir = MV_DOWN;
3642 // required here to update video display before fading (FIX THIS)
3643 DrawMaskedBorder(REDRAW_DOOR_2);
3645 if (!game.restart_level)
3646 CloseDoor(DOOR_CLOSE_1);
3650 // force fading out global animations displayed during game play
3651 SetGameStatus(GAME_MODE_PSEUDO_RESTARTING);
3655 SetGameStatus(GAME_MODE_PLAYING);
3658 if (level_editor_test_game)
3659 FadeSkipNextFadeOut();
3661 FadeSetEnterScreen();
3664 fade_mask = REDRAW_ALL;
3666 FadeLevelSoundsAndMusic();
3668 ExpireSoundLoops(TRUE);
3674 // force restarting global animations displayed during game play
3675 RestartGlobalAnimsByStatus(GAME_MODE_PSEUDO_RESTARTING);
3677 // this is required for "transforming" fade modes like cross-fading
3678 // (else global animations will be stopped, but not restarted here)
3679 SetAnimStatusBeforeFading(GAME_MODE_PSEUDO_RESTARTING);
3681 SetGameStatus(GAME_MODE_PLAYING);
3684 if (level_editor_test_game)
3685 FadeSkipNextFadeIn();
3687 // needed if different viewport properties defined for playing
3688 ChangeViewportPropertiesIfNeeded();
3692 DrawCompleteVideoDisplay();
3694 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3697 InitGameControlValues();
3701 // initialize tape actions from game when recording tape
3702 tape.use_key_actions = game.use_key_actions;
3703 tape.use_mouse_actions = game.use_mouse_actions;
3705 // initialize visible playfield size when recording tape (for team mode)
3706 tape.scr_fieldx = SCR_FIELDX;
3707 tape.scr_fieldy = SCR_FIELDY;
3710 // don't play tapes over network
3711 network_playing = (network.enabled && !tape.playing);
3713 for (i = 0; i < MAX_PLAYERS; i++)
3715 struct PlayerInfo *player = &stored_player[i];
3717 player->index_nr = i;
3718 player->index_bit = (1 << i);
3719 player->element_nr = EL_PLAYER_1 + i;
3721 player->present = FALSE;
3722 player->active = FALSE;
3723 player->mapped = FALSE;
3725 player->killed = FALSE;
3726 player->reanimated = FALSE;
3727 player->buried = FALSE;
3730 player->effective_action = 0;
3731 player->programmed_action = 0;
3732 player->snap_action = 0;
3734 player->mouse_action.lx = 0;
3735 player->mouse_action.ly = 0;
3736 player->mouse_action.button = 0;
3737 player->mouse_action.button_hint = 0;
3739 player->effective_mouse_action.lx = 0;
3740 player->effective_mouse_action.ly = 0;
3741 player->effective_mouse_action.button = 0;
3742 player->effective_mouse_action.button_hint = 0;
3744 for (j = 0; j < MAX_NUM_KEYS; j++)
3745 player->key[j] = FALSE;
3747 player->num_white_keys = 0;
3749 player->dynabomb_count = 0;
3750 player->dynabomb_size = 1;
3751 player->dynabombs_left = 0;
3752 player->dynabomb_xl = FALSE;
3754 player->MovDir = initial_move_dir;
3757 player->GfxDir = initial_move_dir;
3758 player->GfxAction = ACTION_DEFAULT;
3760 player->StepFrame = 0;
3762 player->initial_element = player->element_nr;
3763 player->artwork_element =
3764 (level.use_artwork_element[i] ? level.artwork_element[i] :
3765 player->element_nr);
3766 player->use_murphy = FALSE;
3768 player->block_last_field = FALSE; // initialized in InitPlayerField()
3769 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3771 player->gravity = level.initial_player_gravity[i];
3773 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3775 player->actual_frame_counter.count = 0;
3776 player->actual_frame_counter.value = 1;
3778 player->step_counter = 0;
3780 player->last_move_dir = initial_move_dir;
3782 player->is_active = FALSE;
3784 player->is_waiting = FALSE;
3785 player->is_moving = FALSE;
3786 player->is_auto_moving = FALSE;
3787 player->is_digging = FALSE;
3788 player->is_snapping = FALSE;
3789 player->is_collecting = FALSE;
3790 player->is_pushing = FALSE;
3791 player->is_switching = FALSE;
3792 player->is_dropping = FALSE;
3793 player->is_dropping_pressed = FALSE;
3795 player->is_bored = FALSE;
3796 player->is_sleeping = FALSE;
3798 player->was_waiting = TRUE;
3799 player->was_moving = FALSE;
3800 player->was_snapping = FALSE;
3801 player->was_dropping = FALSE;
3803 player->force_dropping = FALSE;
3805 player->frame_counter_bored = -1;
3806 player->frame_counter_sleeping = -1;
3808 player->anim_delay_counter = 0;
3809 player->post_delay_counter = 0;
3811 player->dir_waiting = initial_move_dir;
3812 player->action_waiting = ACTION_DEFAULT;
3813 player->last_action_waiting = ACTION_DEFAULT;
3814 player->special_action_bored = ACTION_DEFAULT;
3815 player->special_action_sleeping = ACTION_DEFAULT;
3817 player->switch_x = -1;
3818 player->switch_y = -1;
3820 player->drop_x = -1;
3821 player->drop_y = -1;
3823 player->show_envelope = 0;
3825 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3827 player->push_delay = -1; // initialized when pushing starts
3828 player->push_delay_value = game.initial_push_delay_value;
3830 player->drop_delay = 0;
3831 player->drop_pressed_delay = 0;
3833 player->last_jx = -1;
3834 player->last_jy = -1;
3838 player->shield_normal_time_left = 0;
3839 player->shield_deadly_time_left = 0;
3841 player->last_removed_element = EL_UNDEFINED;
3843 player->inventory_infinite_element = EL_UNDEFINED;
3844 player->inventory_size = 0;
3846 if (level.use_initial_inventory[i])
3848 for (j = 0; j < level.initial_inventory_size[i]; j++)
3850 int element = level.initial_inventory_content[i][j];
3851 int collect_count = element_info[element].collect_count_initial;
3854 if (!IS_CUSTOM_ELEMENT(element))
3857 if (collect_count == 0)
3858 player->inventory_infinite_element = element;
3860 for (k = 0; k < collect_count; k++)
3861 if (player->inventory_size < MAX_INVENTORY_SIZE)
3862 player->inventory_element[player->inventory_size++] = element;
3866 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3867 SnapField(player, 0, 0);
3869 map_player_action[i] = i;
3872 network_player_action_received = FALSE;
3874 // initial null action
3875 if (network_playing)
3876 SendToServer_MovePlayer(MV_NONE);
3881 TimeLeft = level.time;
3886 ScreenMovDir = MV_NONE;
3890 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3892 game.robot_wheel_x = -1;
3893 game.robot_wheel_y = -1;
3898 game.all_players_gone = FALSE;
3900 game.LevelSolved = FALSE;
3901 game.GameOver = FALSE;
3903 game.GamePlayed = !tape.playing;
3905 game.LevelSolved_GameWon = FALSE;
3906 game.LevelSolved_GameEnd = FALSE;
3907 game.LevelSolved_SaveTape = FALSE;
3908 game.LevelSolved_SaveScore = FALSE;
3910 game.LevelSolved_CountingTime = 0;
3911 game.LevelSolved_CountingScore = 0;
3912 game.LevelSolved_CountingHealth = 0;
3914 game.RestartGameRequested = FALSE;
3916 game.panel.active = TRUE;
3918 game.no_level_time_limit = (level.time == 0);
3919 game.time_limit = (leveldir_current->time_limit && setup.time_limit);
3921 game.yamyam_content_nr = 0;
3922 game.robot_wheel_active = FALSE;
3923 game.magic_wall_active = FALSE;
3924 game.magic_wall_time_left = 0;
3925 game.light_time_left = 0;
3926 game.timegate_time_left = 0;
3927 game.switchgate_pos = 0;
3928 game.wind_direction = level.wind_direction_initial;
3930 game.time_final = 0;
3931 game.score_time_final = 0;
3934 game.score_final = 0;
3936 game.health = MAX_HEALTH;
3937 game.health_final = MAX_HEALTH;
3939 game.gems_still_needed = level.gems_needed;
3940 game.sokoban_fields_still_needed = 0;
3941 game.sokoban_objects_still_needed = 0;
3942 game.lights_still_needed = 0;
3943 game.players_still_needed = 0;
3944 game.friends_still_needed = 0;
3946 game.lenses_time_left = 0;
3947 game.magnify_time_left = 0;
3949 game.ball_active = level.ball_active_initial;
3950 game.ball_content_nr = 0;
3952 game.explosions_delayed = TRUE;
3954 game.envelope_active = FALSE;
3956 // special case: set custom artwork setting to initial value
3957 game.use_masked_elements = game.use_masked_elements_initial;
3959 for (i = 0; i < NUM_BELTS; i++)
3961 game.belt_dir[i] = MV_NONE;
3962 game.belt_dir_nr[i] = 3; // not moving, next moving left
3965 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3966 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3968 #if DEBUG_INIT_PLAYER
3969 DebugPrintPlayerStatus("Player status at level initialization");
3972 SCAN_PLAYFIELD(x, y)
3974 Tile[x][y] = Last[x][y] = level.field[x][y];
3975 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3976 ChangeDelay[x][y] = 0;
3977 ChangePage[x][y] = -1;
3978 CustomValue[x][y] = 0; // initialized in InitField()
3979 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3981 WasJustMoving[x][y] = 0;
3982 WasJustFalling[x][y] = 0;
3983 CheckCollision[x][y] = 0;
3984 CheckImpact[x][y] = 0;
3986 Pushed[x][y] = FALSE;
3988 ChangeCount[x][y] = 0;
3989 ChangeEvent[x][y] = -1;
3991 ExplodePhase[x][y] = 0;
3992 ExplodeDelay[x][y] = 0;
3993 ExplodeField[x][y] = EX_TYPE_NONE;
3995 RunnerVisit[x][y] = 0;
3996 PlayerVisit[x][y] = 0;
3999 GfxRandom[x][y] = INIT_GFX_RANDOM();
4000 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
4001 GfxElement[x][y] = EL_UNDEFINED;
4002 GfxElementEmpty[x][y] = EL_EMPTY;
4003 GfxAction[x][y] = ACTION_DEFAULT;
4004 GfxDir[x][y] = MV_NONE;
4005 GfxRedraw[x][y] = GFX_REDRAW_NONE;
4008 SCAN_PLAYFIELD(x, y)
4010 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
4012 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
4015 InitField(x, y, TRUE);
4017 ResetGfxAnimation(x, y);
4022 // required if level does not contain any "empty space" element
4023 if (element_info[EL_EMPTY].use_gfx_element)
4024 game.use_masked_elements = TRUE;
4026 for (i = 0; i < MAX_PLAYERS; i++)
4028 struct PlayerInfo *player = &stored_player[i];
4030 // set number of special actions for bored and sleeping animation
4031 player->num_special_action_bored =
4032 get_num_special_action(player->artwork_element,
4033 ACTION_BORING_1, ACTION_BORING_LAST);
4034 player->num_special_action_sleeping =
4035 get_num_special_action(player->artwork_element,
4036 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
4039 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
4040 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
4042 // initialize type of slippery elements
4043 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4045 if (!IS_CUSTOM_ELEMENT(i))
4047 // default: elements slip down either to the left or right randomly
4048 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
4050 // SP style elements prefer to slip down on the left side
4051 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
4052 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4054 // BD style elements prefer to slip down on the left side
4055 if (game.emulation == EMU_BOULDERDASH)
4056 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4060 // initialize explosion and ignition delay
4061 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4063 if (!IS_CUSTOM_ELEMENT(i))
4066 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
4067 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
4068 game.emulation == EMU_SUPAPLEX ? 3 : 2);
4069 int last_phase = (num_phase + 1) * delay;
4070 int half_phase = (num_phase / 2) * delay;
4072 element_info[i].explosion_delay = last_phase - 1;
4073 element_info[i].ignition_delay = half_phase;
4075 if (i == EL_BLACK_ORB)
4076 element_info[i].ignition_delay = 1;
4080 // correct non-moving belts to start moving left
4081 for (i = 0; i < NUM_BELTS; i++)
4082 if (game.belt_dir[i] == MV_NONE)
4083 game.belt_dir_nr[i] = 3; // not moving, next moving left
4085 #if USE_NEW_PLAYER_ASSIGNMENTS
4086 // use preferred player also in local single-player mode
4087 if (!network.enabled && !game.team_mode)
4089 int new_index_nr = setup.network_player_nr;
4091 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
4093 for (i = 0; i < MAX_PLAYERS; i++)
4094 stored_player[i].connected_locally = FALSE;
4096 stored_player[new_index_nr].connected_locally = TRUE;
4100 for (i = 0; i < MAX_PLAYERS; i++)
4102 stored_player[i].connected = FALSE;
4104 // in network game mode, the local player might not be the first player
4105 if (stored_player[i].connected_locally)
4106 local_player = &stored_player[i];
4109 if (!network.enabled)
4110 local_player->connected = TRUE;
4114 for (i = 0; i < MAX_PLAYERS; i++)
4115 stored_player[i].connected = tape.player_participates[i];
4117 else if (network.enabled)
4119 // add team mode players connected over the network (needed for correct
4120 // assignment of player figures from level to locally playing players)
4122 for (i = 0; i < MAX_PLAYERS; i++)
4123 if (stored_player[i].connected_network)
4124 stored_player[i].connected = TRUE;
4126 else if (game.team_mode)
4128 // try to guess locally connected team mode players (needed for correct
4129 // assignment of player figures from level to locally playing players)
4131 for (i = 0; i < MAX_PLAYERS; i++)
4132 if (setup.input[i].use_joystick ||
4133 setup.input[i].key.left != KSYM_UNDEFINED)
4134 stored_player[i].connected = TRUE;
4137 #if DEBUG_INIT_PLAYER
4138 DebugPrintPlayerStatus("Player status after level initialization");
4141 #if DEBUG_INIT_PLAYER
4142 Debug("game:init:player", "Reassigning players ...");
4145 // check if any connected player was not found in playfield
4146 for (i = 0; i < MAX_PLAYERS; i++)
4148 struct PlayerInfo *player = &stored_player[i];
4150 if (player->connected && !player->present)
4152 struct PlayerInfo *field_player = NULL;
4154 #if DEBUG_INIT_PLAYER
4155 Debug("game:init:player",
4156 "- looking for field player for player %d ...", i + 1);
4159 // assign first free player found that is present in the playfield
4161 // first try: look for unmapped playfield player that is not connected
4162 for (j = 0; j < MAX_PLAYERS; j++)
4163 if (field_player == NULL &&
4164 stored_player[j].present &&
4165 !stored_player[j].mapped &&
4166 !stored_player[j].connected)
4167 field_player = &stored_player[j];
4169 // second try: look for *any* unmapped playfield player
4170 for (j = 0; j < MAX_PLAYERS; j++)
4171 if (field_player == NULL &&
4172 stored_player[j].present &&
4173 !stored_player[j].mapped)
4174 field_player = &stored_player[j];
4176 if (field_player != NULL)
4178 int jx = field_player->jx, jy = field_player->jy;
4180 #if DEBUG_INIT_PLAYER
4181 Debug("game:init:player", "- found player %d",
4182 field_player->index_nr + 1);
4185 player->present = FALSE;
4186 player->active = FALSE;
4188 field_player->present = TRUE;
4189 field_player->active = TRUE;
4192 player->initial_element = field_player->initial_element;
4193 player->artwork_element = field_player->artwork_element;
4195 player->block_last_field = field_player->block_last_field;
4196 player->block_delay_adjustment = field_player->block_delay_adjustment;
4199 StorePlayer[jx][jy] = field_player->element_nr;
4201 field_player->jx = field_player->last_jx = jx;
4202 field_player->jy = field_player->last_jy = jy;
4204 if (local_player == player)
4205 local_player = field_player;
4207 map_player_action[field_player->index_nr] = i;
4209 field_player->mapped = TRUE;
4211 #if DEBUG_INIT_PLAYER
4212 Debug("game:init:player", "- map_player_action[%d] == %d",
4213 field_player->index_nr + 1, i + 1);
4218 if (player->connected && player->present)
4219 player->mapped = TRUE;
4222 #if DEBUG_INIT_PLAYER
4223 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4228 // check if any connected player was not found in playfield
4229 for (i = 0; i < MAX_PLAYERS; i++)
4231 struct PlayerInfo *player = &stored_player[i];
4233 if (player->connected && !player->present)
4235 for (j = 0; j < MAX_PLAYERS; j++)
4237 struct PlayerInfo *field_player = &stored_player[j];
4238 int jx = field_player->jx, jy = field_player->jy;
4240 // assign first free player found that is present in the playfield
4241 if (field_player->present && !field_player->connected)
4243 player->present = TRUE;
4244 player->active = TRUE;
4246 field_player->present = FALSE;
4247 field_player->active = FALSE;
4249 player->initial_element = field_player->initial_element;
4250 player->artwork_element = field_player->artwork_element;
4252 player->block_last_field = field_player->block_last_field;
4253 player->block_delay_adjustment = field_player->block_delay_adjustment;
4255 StorePlayer[jx][jy] = player->element_nr;
4257 player->jx = player->last_jx = jx;
4258 player->jy = player->last_jy = jy;
4268 Debug("game:init:player", "local_player->present == %d",
4269 local_player->present);
4272 // set focus to local player for network games, else to all players
4273 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4274 game.centered_player_nr_next = game.centered_player_nr;
4275 game.set_centered_player = FALSE;
4276 game.set_centered_player_wrap = FALSE;
4278 if (network_playing && tape.recording)
4280 // store client dependent player focus when recording network games
4281 tape.centered_player_nr_next = game.centered_player_nr_next;
4282 tape.set_centered_player = TRUE;
4287 // when playing a tape, eliminate all players who do not participate
4289 #if USE_NEW_PLAYER_ASSIGNMENTS
4291 if (!game.team_mode)
4293 for (i = 0; i < MAX_PLAYERS; i++)
4295 if (stored_player[i].active &&
4296 !tape.player_participates[map_player_action[i]])
4298 struct PlayerInfo *player = &stored_player[i];
4299 int jx = player->jx, jy = player->jy;
4301 #if DEBUG_INIT_PLAYER
4302 Debug("game:init:player", "Removing player %d at (%d, %d)",
4306 player->active = FALSE;
4307 StorePlayer[jx][jy] = 0;
4308 Tile[jx][jy] = EL_EMPTY;
4315 for (i = 0; i < MAX_PLAYERS; i++)
4317 if (stored_player[i].active &&
4318 !tape.player_participates[i])
4320 struct PlayerInfo *player = &stored_player[i];
4321 int jx = player->jx, jy = player->jy;
4323 player->active = FALSE;
4324 StorePlayer[jx][jy] = 0;
4325 Tile[jx][jy] = EL_EMPTY;
4330 else if (!network.enabled && !game.team_mode) // && !tape.playing
4332 // when in single player mode, eliminate all but the local player
4334 for (i = 0; i < MAX_PLAYERS; i++)
4336 struct PlayerInfo *player = &stored_player[i];
4338 if (player->active && player != local_player)
4340 int jx = player->jx, jy = player->jy;
4342 player->active = FALSE;
4343 player->present = FALSE;
4345 StorePlayer[jx][jy] = 0;
4346 Tile[jx][jy] = EL_EMPTY;
4351 for (i = 0; i < MAX_PLAYERS; i++)
4352 if (stored_player[i].active)
4353 game.players_still_needed++;
4355 if (level.solved_by_one_player)
4356 game.players_still_needed = 1;
4358 // when recording the game, store which players take part in the game
4361 #if USE_NEW_PLAYER_ASSIGNMENTS
4362 for (i = 0; i < MAX_PLAYERS; i++)
4363 if (stored_player[i].connected)
4364 tape.player_participates[i] = TRUE;
4366 for (i = 0; i < MAX_PLAYERS; i++)
4367 if (stored_player[i].active)
4368 tape.player_participates[i] = TRUE;
4372 #if DEBUG_INIT_PLAYER
4373 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4376 if (BorderElement == EL_EMPTY)
4379 SBX_Right = lev_fieldx - SCR_FIELDX;
4381 SBY_Lower = lev_fieldy - SCR_FIELDY;
4386 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4388 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4391 if (full_lev_fieldx <= SCR_FIELDX)
4392 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4393 if (full_lev_fieldy <= SCR_FIELDY)
4394 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4396 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4398 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4401 // if local player not found, look for custom element that might create
4402 // the player (make some assumptions about the right custom element)
4403 if (!local_player->present)
4405 int start_x = 0, start_y = 0;
4406 int found_rating = 0;
4407 int found_element = EL_UNDEFINED;
4408 int player_nr = local_player->index_nr;
4410 SCAN_PLAYFIELD(x, y)
4412 int element = Tile[x][y];
4417 if (level.use_start_element[player_nr] &&
4418 level.start_element[player_nr] == element &&
4425 found_element = element;
4428 if (!IS_CUSTOM_ELEMENT(element))
4431 if (CAN_CHANGE(element))
4433 for (i = 0; i < element_info[element].num_change_pages; i++)
4435 // check for player created from custom element as single target
4436 content = element_info[element].change_page[i].target_element;
4437 is_player = IS_PLAYER_ELEMENT(content);
4439 if (is_player && (found_rating < 3 ||
4440 (found_rating == 3 && element < found_element)))
4446 found_element = element;
4451 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4453 // check for player created from custom element as explosion content
4454 content = element_info[element].content.e[xx][yy];
4455 is_player = IS_PLAYER_ELEMENT(content);
4457 if (is_player && (found_rating < 2 ||
4458 (found_rating == 2 && element < found_element)))
4460 start_x = x + xx - 1;
4461 start_y = y + yy - 1;
4464 found_element = element;
4467 if (!CAN_CHANGE(element))
4470 for (i = 0; i < element_info[element].num_change_pages; i++)
4472 // check for player created from custom element as extended target
4474 element_info[element].change_page[i].target_content.e[xx][yy];
4476 is_player = IS_PLAYER_ELEMENT(content);
4478 if (is_player && (found_rating < 1 ||
4479 (found_rating == 1 && element < found_element)))
4481 start_x = x + xx - 1;
4482 start_y = y + yy - 1;
4485 found_element = element;
4491 scroll_x = SCROLL_POSITION_X(start_x);
4492 scroll_y = SCROLL_POSITION_Y(start_y);
4496 scroll_x = SCROLL_POSITION_X(local_player->jx);
4497 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4500 if (game.forced_scroll_x != ARG_UNDEFINED_VALUE)
4501 scroll_x = game.forced_scroll_x;
4502 if (game.forced_scroll_y != ARG_UNDEFINED_VALUE)
4503 scroll_y = game.forced_scroll_y;
4505 // !!! FIX THIS (START) !!!
4506 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
4508 InitGameEngine_BD();
4510 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4512 InitGameEngine_EM();
4514 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4516 InitGameEngine_SP();
4518 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4520 InitGameEngine_MM();
4524 DrawLevel(REDRAW_FIELD);
4527 // after drawing the level, correct some elements
4528 if (game.timegate_time_left == 0)
4529 CloseAllOpenTimegates();
4532 // blit playfield from scroll buffer to normal back buffer for fading in
4533 BlitScreenToBitmap(backbuffer);
4534 // !!! FIX THIS (END) !!!
4536 DrawMaskedBorder(fade_mask);
4541 // full screen redraw is required at this point in the following cases:
4542 // - special editor door undrawn when game was started from level editor
4543 // - drawing area (playfield) was changed and has to be removed completely
4544 redraw_mask = REDRAW_ALL;
4548 if (!game.restart_level)
4550 // copy default game door content to main double buffer
4552 // !!! CHECK AGAIN !!!
4553 SetPanelBackground();
4554 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4555 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4558 SetPanelBackground();
4559 SetDrawBackgroundMask(REDRAW_DOOR_1);
4561 UpdateAndDisplayGameControlValues();
4563 if (!game.restart_level)
4569 CreateGameButtons();
4574 // copy actual game door content to door double buffer for OpenDoor()
4575 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4577 OpenDoor(DOOR_OPEN_ALL);
4579 KeyboardAutoRepeatOffUnlessAutoplay();
4581 #if DEBUG_INIT_PLAYER
4582 DebugPrintPlayerStatus("Player status (final)");
4591 if (!game.restart_level && !tape.playing)
4593 LevelStats_incPlayed(level_nr);
4595 SaveLevelSetup_SeriesInfo();
4598 game.restart_level = FALSE;
4599 game.request_active = FALSE;
4601 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4602 InitGameActions_MM();
4604 SaveEngineSnapshotToListInitial();
4606 if (!game.restart_level)
4608 PlaySound(SND_GAME_STARTING);
4610 if (setup.sound_music)
4614 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4617 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4618 int actual_player_x, int actual_player_y)
4620 // this is used for non-R'n'D game engines to update certain engine values
4622 // needed to determine if sounds are played within the visible screen area
4623 scroll_x = actual_scroll_x;
4624 scroll_y = actual_scroll_y;
4626 // needed to get player position for "follow finger" playing input method
4627 local_player->jx = actual_player_x;
4628 local_player->jy = actual_player_y;
4631 void InitMovDir(int x, int y)
4633 int i, element = Tile[x][y];
4634 static int xy[4][2] =
4641 static int direction[3][4] =
4643 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4644 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4645 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4654 Tile[x][y] = EL_BUG;
4655 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4658 case EL_SPACESHIP_RIGHT:
4659 case EL_SPACESHIP_UP:
4660 case EL_SPACESHIP_LEFT:
4661 case EL_SPACESHIP_DOWN:
4662 Tile[x][y] = EL_SPACESHIP;
4663 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4666 case EL_BD_BUTTERFLY_RIGHT:
4667 case EL_BD_BUTTERFLY_UP:
4668 case EL_BD_BUTTERFLY_LEFT:
4669 case EL_BD_BUTTERFLY_DOWN:
4670 Tile[x][y] = EL_BD_BUTTERFLY;
4671 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4674 case EL_BD_FIREFLY_RIGHT:
4675 case EL_BD_FIREFLY_UP:
4676 case EL_BD_FIREFLY_LEFT:
4677 case EL_BD_FIREFLY_DOWN:
4678 Tile[x][y] = EL_BD_FIREFLY;
4679 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4682 case EL_PACMAN_RIGHT:
4684 case EL_PACMAN_LEFT:
4685 case EL_PACMAN_DOWN:
4686 Tile[x][y] = EL_PACMAN;
4687 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4690 case EL_YAMYAM_LEFT:
4691 case EL_YAMYAM_RIGHT:
4693 case EL_YAMYAM_DOWN:
4694 Tile[x][y] = EL_YAMYAM;
4695 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4698 case EL_SP_SNIKSNAK:
4699 MovDir[x][y] = MV_UP;
4702 case EL_SP_ELECTRON:
4703 MovDir[x][y] = MV_LEFT;
4710 Tile[x][y] = EL_MOLE;
4711 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4714 case EL_SPRING_LEFT:
4715 case EL_SPRING_RIGHT:
4716 Tile[x][y] = EL_SPRING;
4717 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4721 if (IS_CUSTOM_ELEMENT(element))
4723 struct ElementInfo *ei = &element_info[element];
4724 int move_direction_initial = ei->move_direction_initial;
4725 int move_pattern = ei->move_pattern;
4727 if (move_direction_initial == MV_START_PREVIOUS)
4729 if (MovDir[x][y] != MV_NONE)
4732 move_direction_initial = MV_START_AUTOMATIC;
4735 if (move_direction_initial == MV_START_RANDOM)
4736 MovDir[x][y] = 1 << RND(4);
4737 else if (move_direction_initial & MV_ANY_DIRECTION)
4738 MovDir[x][y] = move_direction_initial;
4739 else if (move_pattern == MV_ALL_DIRECTIONS ||
4740 move_pattern == MV_TURNING_LEFT ||
4741 move_pattern == MV_TURNING_RIGHT ||
4742 move_pattern == MV_TURNING_LEFT_RIGHT ||
4743 move_pattern == MV_TURNING_RIGHT_LEFT ||
4744 move_pattern == MV_TURNING_RANDOM)
4745 MovDir[x][y] = 1 << RND(4);
4746 else if (move_pattern == MV_HORIZONTAL)
4747 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4748 else if (move_pattern == MV_VERTICAL)
4749 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4750 else if (move_pattern & MV_ANY_DIRECTION)
4751 MovDir[x][y] = element_info[element].move_pattern;
4752 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4753 move_pattern == MV_ALONG_RIGHT_SIDE)
4755 // use random direction as default start direction
4756 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4757 MovDir[x][y] = 1 << RND(4);
4759 for (i = 0; i < NUM_DIRECTIONS; i++)
4761 int x1 = x + xy[i][0];
4762 int y1 = y + xy[i][1];
4764 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4766 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4767 MovDir[x][y] = direction[0][i];
4769 MovDir[x][y] = direction[1][i];
4778 MovDir[x][y] = 1 << RND(4);
4780 if (element != EL_BUG &&
4781 element != EL_SPACESHIP &&
4782 element != EL_BD_BUTTERFLY &&
4783 element != EL_BD_FIREFLY)
4786 for (i = 0; i < NUM_DIRECTIONS; i++)
4788 int x1 = x + xy[i][0];
4789 int y1 = y + xy[i][1];
4791 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4793 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4795 MovDir[x][y] = direction[0][i];
4798 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4799 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4801 MovDir[x][y] = direction[1][i];
4810 GfxDir[x][y] = MovDir[x][y];
4813 void InitAmoebaNr(int x, int y)
4816 int group_nr = AmoebaNeighbourNr(x, y);
4820 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4822 if (AmoebaCnt[i] == 0)
4830 AmoebaNr[x][y] = group_nr;
4831 AmoebaCnt[group_nr]++;
4832 AmoebaCnt2[group_nr]++;
4835 static void LevelSolved_SetFinalGameValues(void)
4837 game.time_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.time_played :
4838 game.no_level_time_limit ? TimePlayed : TimeLeft);
4839 game.score_time_final = (level.use_step_counter ? TimePlayed :
4840 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4842 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.score :
4843 level.game_engine_type == GAME_ENGINE_TYPE_EM ? game_em.lev->score :
4844 level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.score :
4847 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4848 MM_HEALTH(game_mm.laser_overload_value) :
4851 game.LevelSolved_CountingTime = game.time_final;
4852 game.LevelSolved_CountingScore = game.score_final;
4853 game.LevelSolved_CountingHealth = game.health_final;
4856 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4858 game.LevelSolved_CountingTime = time;
4859 game.LevelSolved_CountingScore = score;
4860 game.LevelSolved_CountingHealth = health;
4862 game_panel_controls[GAME_PANEL_TIME].value = time;
4863 game_panel_controls[GAME_PANEL_SCORE].value = score;
4864 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4866 DisplayGameControlValues();
4869 static void LevelSolved(void)
4871 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4872 game.players_still_needed > 0)
4875 game.LevelSolved = TRUE;
4876 game.GameOver = TRUE;
4880 // needed here to display correct panel values while player walks into exit
4881 LevelSolved_SetFinalGameValues();
4886 static int time_count_steps;
4887 static int time, time_final;
4888 static float score, score_final; // needed for time score < 10 for 10 seconds
4889 static int health, health_final;
4890 static int game_over_delay_1 = 0;
4891 static int game_over_delay_2 = 0;
4892 static int game_over_delay_3 = 0;
4893 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4894 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4896 if (!game.LevelSolved_GameWon)
4900 // do not start end game actions before the player stops moving (to exit)
4901 if (local_player->active && local_player->MovPos)
4904 // calculate final game values after player finished walking into exit
4905 LevelSolved_SetFinalGameValues();
4907 game.LevelSolved_GameWon = TRUE;
4908 game.LevelSolved_SaveTape = tape.recording;
4909 game.LevelSolved_SaveScore = !tape.playing;
4913 LevelStats_incSolved(level_nr);
4915 SaveLevelSetup_SeriesInfo();
4918 if (tape.auto_play) // tape might already be stopped here
4919 tape.auto_play_level_solved = TRUE;
4923 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4924 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4925 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4927 time = time_final = game.time_final;
4928 score = score_final = game.score_final;
4929 health = health_final = game.health_final;
4931 // update game panel values before (delayed) counting of score (if any)
4932 LevelSolved_DisplayFinalGameValues(time, score, health);
4934 // if level has time score defined, calculate new final game values
4937 int time_final_max = 999;
4938 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4939 int time_frames = 0;
4940 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4941 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4946 time_frames = time_frames_left;
4948 else if (game.no_level_time_limit && TimePlayed < time_final_max)
4950 time_final = time_final_max;
4951 time_frames = time_frames_final_max - time_frames_played;
4954 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4956 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4958 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
4960 // keep previous values (final values already processed here)
4962 score_final = score;
4964 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4967 score_final += health * time_score;
4970 game.score_final = score_final;
4971 game.health_final = health_final;
4974 // if not counting score after game, immediately update game panel values
4975 if (level_editor_test_game || !setup.count_score_after_game ||
4976 level.game_engine_type == GAME_ENGINE_TYPE_BD)
4979 score = score_final;
4981 LevelSolved_DisplayFinalGameValues(time, score, health);
4984 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4986 // check if last player has left the level
4987 if (game.exit_x >= 0 &&
4990 int x = game.exit_x;
4991 int y = game.exit_y;
4992 int element = Tile[x][y];
4994 // close exit door after last player
4995 if ((game.all_players_gone &&
4996 (element == EL_EXIT_OPEN ||
4997 element == EL_SP_EXIT_OPEN ||
4998 element == EL_STEEL_EXIT_OPEN)) ||
4999 element == EL_EM_EXIT_OPEN ||
5000 element == EL_EM_STEEL_EXIT_OPEN)
5004 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
5005 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
5006 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
5007 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
5008 EL_EM_STEEL_EXIT_CLOSING);
5010 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
5013 // player disappears
5014 DrawLevelField(x, y);
5017 for (i = 0; i < MAX_PLAYERS; i++)
5019 struct PlayerInfo *player = &stored_player[i];
5021 if (player->present)
5023 RemovePlayer(player);
5025 // player disappears
5026 DrawLevelField(player->jx, player->jy);
5031 PlaySound(SND_GAME_WINNING);
5034 if (setup.count_score_after_game)
5036 if (time != time_final)
5038 if (game_over_delay_1 > 0)
5040 game_over_delay_1--;
5045 int time_to_go = ABS(time_final - time);
5046 int time_count_dir = (time < time_final ? +1 : -1);
5048 if (time_to_go < time_count_steps)
5049 time_count_steps = 1;
5051 time += time_count_steps * time_count_dir;
5052 score += time_count_steps * time_score;
5054 // set final score to correct rounding differences after counting score
5055 if (time == time_final)
5056 score = score_final;
5058 LevelSolved_DisplayFinalGameValues(time, score, health);
5060 if (time == time_final)
5061 StopSound(SND_GAME_LEVELTIME_BONUS);
5062 else if (setup.sound_loops)
5063 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5065 PlaySound(SND_GAME_LEVELTIME_BONUS);
5070 if (health != health_final)
5072 if (game_over_delay_2 > 0)
5074 game_over_delay_2--;
5079 int health_count_dir = (health < health_final ? +1 : -1);
5081 health += health_count_dir;
5082 score += time_score;
5084 LevelSolved_DisplayFinalGameValues(time, score, health);
5086 if (health == health_final)
5087 StopSound(SND_GAME_LEVELTIME_BONUS);
5088 else if (setup.sound_loops)
5089 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5091 PlaySound(SND_GAME_LEVELTIME_BONUS);
5097 game.panel.active = FALSE;
5099 if (game_over_delay_3 > 0)
5101 game_over_delay_3--;
5111 // used instead of "level_nr" (needed for network games)
5112 int last_level_nr = levelset.level_nr;
5113 boolean tape_saved = FALSE;
5115 game.LevelSolved_GameEnd = TRUE;
5117 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5119 // make sure that request dialog to save tape does not open door again
5120 if (!global.use_envelope_request)
5121 CloseDoor(DOOR_CLOSE_1);
5124 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5126 // set unique basename for score tape (also saved in high score table)
5127 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5130 // if no tape is to be saved, close both doors simultaneously
5131 CloseDoor(DOOR_CLOSE_ALL);
5133 if (level_editor_test_game || score_info_tape_play)
5135 SetGameStatus(GAME_MODE_MAIN);
5142 if (!game.LevelSolved_SaveScore)
5144 SetGameStatus(GAME_MODE_MAIN);
5151 if (level_nr == leveldir_current->handicap_level)
5153 leveldir_current->handicap_level++;
5155 SaveLevelSetup_SeriesInfo();
5158 // save score and score tape before potentially erasing tape below
5159 NewHighScore(last_level_nr, tape_saved);
5161 if (setup.increment_levels &&
5162 level_nr < leveldir_current->last_level &&
5165 level_nr++; // advance to next level
5166 TapeErase(); // start with empty tape
5168 if (setup.auto_play_next_level)
5170 scores.continue_playing = TRUE;
5171 scores.next_level_nr = level_nr;
5173 LoadLevel(level_nr);
5175 SaveLevelSetup_SeriesInfo();
5179 if (scores.last_added >= 0 && setup.show_scores_after_game)
5181 SetGameStatus(GAME_MODE_SCORES);
5183 DrawHallOfFame(last_level_nr);
5185 else if (scores.continue_playing)
5187 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5191 SetGameStatus(GAME_MODE_MAIN);
5197 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5198 boolean one_score_entry_per_name)
5202 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5205 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5207 struct ScoreEntry *entry = &list->entry[i];
5208 boolean score_is_better = (new_entry->score > entry->score);
5209 boolean score_is_equal = (new_entry->score == entry->score);
5210 boolean time_is_better = (new_entry->time < entry->time);
5211 boolean time_is_equal = (new_entry->time == entry->time);
5212 boolean better_by_score = (score_is_better ||
5213 (score_is_equal && time_is_better));
5214 boolean better_by_time = (time_is_better ||
5215 (time_is_equal && score_is_better));
5216 boolean is_better = (level.rate_time_over_score ? better_by_time :
5218 boolean entry_is_empty = (entry->score == 0 &&
5221 // prevent adding server score entries if also existing in local score file
5222 // (special case: historic score entries have an empty tape basename entry)
5223 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5224 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5226 // add fields from server score entry not stored in local score entry
5227 // (currently, this means setting platform, version and country fields;
5228 // in rare cases, this may also correct an invalid score value, as
5229 // historic scores might have been truncated to 16-bit values locally)
5230 *entry = *new_entry;
5235 if (is_better || entry_is_empty)
5237 // player has made it to the hall of fame
5239 if (i < MAX_SCORE_ENTRIES - 1)
5241 int m = MAX_SCORE_ENTRIES - 1;
5244 if (one_score_entry_per_name)
5246 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5247 if (strEqual(list->entry[l].name, new_entry->name))
5250 if (m == i) // player's new highscore overwrites his old one
5254 for (l = m; l > i; l--)
5255 list->entry[l] = list->entry[l - 1];
5260 *entry = *new_entry;
5264 else if (one_score_entry_per_name &&
5265 strEqual(entry->name, new_entry->name))
5267 // player already in high score list with better score or time
5273 // special case: new score is beyond the last high score list position
5274 return MAX_SCORE_ENTRIES;
5277 void NewHighScore(int level_nr, boolean tape_saved)
5279 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5280 boolean one_per_name = FALSE;
5282 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5283 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5285 new_entry.score = game.score_final;
5286 new_entry.time = game.score_time_final;
5288 LoadScore(level_nr);
5290 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5292 if (scores.last_added >= MAX_SCORE_ENTRIES)
5294 scores.last_added = MAX_SCORE_ENTRIES - 1;
5295 scores.force_last_added = TRUE;
5297 scores.entry[scores.last_added] = new_entry;
5299 // store last added local score entry (before merging server scores)
5300 scores.last_added_local = scores.last_added;
5305 if (scores.last_added < 0)
5308 SaveScore(level_nr);
5310 // store last added local score entry (before merging server scores)
5311 scores.last_added_local = scores.last_added;
5313 if (!game.LevelSolved_SaveTape)
5316 SaveScoreTape(level_nr);
5318 if (setup.ask_for_using_api_server)
5320 setup.use_api_server =
5321 Request("Upload your score and tape to the high score server?", REQ_ASK);
5323 if (!setup.use_api_server)
5324 Request("Not using high score server! Use setup menu to enable again!",
5327 runtime.use_api_server = setup.use_api_server;
5329 // after asking for using API server once, do not ask again
5330 setup.ask_for_using_api_server = FALSE;
5332 SaveSetup_ServerSetup();
5335 SaveServerScore(level_nr, tape_saved);
5338 void MergeServerScore(void)
5340 struct ScoreEntry last_added_entry;
5341 boolean one_per_name = FALSE;
5344 if (scores.last_added >= 0)
5345 last_added_entry = scores.entry[scores.last_added];
5347 for (i = 0; i < server_scores.num_entries; i++)
5349 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5351 if (pos >= 0 && pos <= scores.last_added)
5352 scores.last_added++;
5355 if (scores.last_added >= MAX_SCORE_ENTRIES)
5357 scores.last_added = MAX_SCORE_ENTRIES - 1;
5358 scores.force_last_added = TRUE;
5360 scores.entry[scores.last_added] = last_added_entry;
5364 static int getElementMoveStepsizeExt(int x, int y, int direction)
5366 int element = Tile[x][y];
5367 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5368 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5369 int horiz_move = (dx != 0);
5370 int sign = (horiz_move ? dx : dy);
5371 int step = sign * element_info[element].move_stepsize;
5373 // special values for move stepsize for spring and things on conveyor belt
5376 if (CAN_FALL(element) &&
5377 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5378 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5379 else if (element == EL_SPRING)
5380 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5386 static int getElementMoveStepsize(int x, int y)
5388 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5391 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5393 if (player->GfxAction != action || player->GfxDir != dir)
5395 player->GfxAction = action;
5396 player->GfxDir = dir;
5398 player->StepFrame = 0;
5402 static void ResetGfxFrame(int x, int y)
5404 // profiling showed that "autotest" spends 10~20% of its time in this function
5405 if (DrawingDeactivatedField())
5408 int element = Tile[x][y];
5409 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5411 if (graphic_info[graphic].anim_global_sync)
5412 GfxFrame[x][y] = FrameCounter;
5413 else if (graphic_info[graphic].anim_global_anim_sync)
5414 GfxFrame[x][y] = getGlobalAnimSyncFrame();
5415 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5416 GfxFrame[x][y] = CustomValue[x][y];
5417 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5418 GfxFrame[x][y] = element_info[element].collect_score;
5419 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5420 GfxFrame[x][y] = ChangeDelay[x][y];
5423 static void ResetGfxAnimation(int x, int y)
5425 GfxAction[x][y] = ACTION_DEFAULT;
5426 GfxDir[x][y] = MovDir[x][y];
5429 ResetGfxFrame(x, y);
5432 static void ResetRandomAnimationValue(int x, int y)
5434 GfxRandom[x][y] = INIT_GFX_RANDOM();
5437 static void InitMovingField(int x, int y, int direction)
5439 int element = Tile[x][y];
5440 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5441 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5444 boolean is_moving_before, is_moving_after;
5446 // check if element was/is moving or being moved before/after mode change
5447 is_moving_before = (WasJustMoving[x][y] != 0);
5448 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5450 // reset animation only for moving elements which change direction of moving
5451 // or which just started or stopped moving
5452 // (else CEs with property "can move" / "not moving" are reset each frame)
5453 if (is_moving_before != is_moving_after ||
5454 direction != MovDir[x][y])
5455 ResetGfxAnimation(x, y);
5457 MovDir[x][y] = direction;
5458 GfxDir[x][y] = direction;
5460 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5461 direction == MV_DOWN && CAN_FALL(element) ?
5462 ACTION_FALLING : ACTION_MOVING);
5464 // this is needed for CEs with property "can move" / "not moving"
5466 if (is_moving_after)
5468 if (Tile[newx][newy] == EL_EMPTY)
5469 Tile[newx][newy] = EL_BLOCKED;
5471 MovDir[newx][newy] = MovDir[x][y];
5473 CustomValue[newx][newy] = CustomValue[x][y];
5475 GfxFrame[newx][newy] = GfxFrame[x][y];
5476 GfxRandom[newx][newy] = GfxRandom[x][y];
5477 GfxAction[newx][newy] = GfxAction[x][y];
5478 GfxDir[newx][newy] = GfxDir[x][y];
5482 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5484 int direction = MovDir[x][y];
5485 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5486 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5492 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5494 int direction = MovDir[x][y];
5495 int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0);
5496 int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0);
5498 *comes_from_x = oldx;
5499 *comes_from_y = oldy;
5502 static int MovingOrBlocked2Element(int x, int y)
5504 int element = Tile[x][y];
5506 if (element == EL_BLOCKED)
5510 Blocked2Moving(x, y, &oldx, &oldy);
5512 return Tile[oldx][oldy];
5518 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5520 // like MovingOrBlocked2Element(), but if element is moving
5521 // and (x, y) is the field the moving element is just leaving,
5522 // return EL_BLOCKED instead of the element value
5523 int element = Tile[x][y];
5525 if (IS_MOVING(x, y))
5527 if (element == EL_BLOCKED)
5531 Blocked2Moving(x, y, &oldx, &oldy);
5532 return Tile[oldx][oldy];
5541 static void RemoveField(int x, int y)
5543 Tile[x][y] = EL_EMPTY;
5549 CustomValue[x][y] = 0;
5552 ChangeDelay[x][y] = 0;
5553 ChangePage[x][y] = -1;
5554 Pushed[x][y] = FALSE;
5556 GfxElement[x][y] = EL_UNDEFINED;
5557 GfxAction[x][y] = ACTION_DEFAULT;
5558 GfxDir[x][y] = MV_NONE;
5561 static void RemoveMovingField(int x, int y)
5563 int oldx = x, oldy = y, newx = x, newy = y;
5564 int element = Tile[x][y];
5565 int next_element = EL_UNDEFINED;
5567 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5570 if (IS_MOVING(x, y))
5572 Moving2Blocked(x, y, &newx, &newy);
5574 if (Tile[newx][newy] != EL_BLOCKED)
5576 // element is moving, but target field is not free (blocked), but
5577 // already occupied by something different (example: acid pool);
5578 // in this case, only remove the moving field, but not the target
5580 RemoveField(oldx, oldy);
5582 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5584 TEST_DrawLevelField(oldx, oldy);
5589 else if (element == EL_BLOCKED)
5591 Blocked2Moving(x, y, &oldx, &oldy);
5592 if (!IS_MOVING(oldx, oldy))
5596 if (element == EL_BLOCKED &&
5597 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5598 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5599 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5600 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5601 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5602 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5603 next_element = get_next_element(Tile[oldx][oldy]);
5605 RemoveField(oldx, oldy);
5606 RemoveField(newx, newy);
5608 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5610 if (next_element != EL_UNDEFINED)
5611 Tile[oldx][oldy] = next_element;
5613 TEST_DrawLevelField(oldx, oldy);
5614 TEST_DrawLevelField(newx, newy);
5617 void DrawDynamite(int x, int y)
5619 int sx = SCREENX(x), sy = SCREENY(y);
5620 int graphic = el2img(Tile[x][y]);
5623 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5626 if (IS_WALKABLE_INSIDE(Back[x][y]))
5630 DrawLevelElement(x, y, Back[x][y]);
5631 else if (Store[x][y])
5632 DrawLevelElement(x, y, Store[x][y]);
5633 else if (game.use_masked_elements)
5634 DrawLevelElement(x, y, EL_EMPTY);
5636 frame = getGraphicAnimationFrameXY(graphic, x, y);
5638 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5639 DrawGraphicThruMask(sx, sy, graphic, frame);
5641 DrawGraphic(sx, sy, graphic, frame);
5644 static void CheckDynamite(int x, int y)
5646 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5650 if (MovDelay[x][y] != 0)
5653 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5659 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5664 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5666 boolean num_checked_players = 0;
5669 for (i = 0; i < MAX_PLAYERS; i++)
5671 if (stored_player[i].active)
5673 int sx = stored_player[i].jx;
5674 int sy = stored_player[i].jy;
5676 if (num_checked_players == 0)
5683 *sx1 = MIN(*sx1, sx);
5684 *sy1 = MIN(*sy1, sy);
5685 *sx2 = MAX(*sx2, sx);
5686 *sy2 = MAX(*sy2, sy);
5689 num_checked_players++;
5694 static boolean checkIfAllPlayersFitToScreen_RND(void)
5696 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5698 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5700 return (sx2 - sx1 < SCR_FIELDX &&
5701 sy2 - sy1 < SCR_FIELDY);
5704 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5706 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5708 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5710 *sx = (sx1 + sx2) / 2;
5711 *sy = (sy1 + sy2) / 2;
5714 static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
5715 boolean center_screen, boolean quick_relocation)
5717 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5718 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5719 boolean no_delay = (tape.warp_forward);
5720 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5721 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5722 int new_scroll_x, new_scroll_y;
5724 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5726 // case 1: quick relocation inside visible screen (without scrolling)
5733 if (!level.shifted_relocation || center_screen)
5735 // relocation _with_ centering of screen
5737 new_scroll_x = SCROLL_POSITION_X(x);
5738 new_scroll_y = SCROLL_POSITION_Y(y);
5742 // relocation _without_ centering of screen
5744 // apply distance between old and new player position to scroll position
5745 int shifted_scroll_x = scroll_x + (x - old_x);
5746 int shifted_scroll_y = scroll_y + (y - old_y);
5748 // make sure that shifted scroll position does not scroll beyond screen
5749 new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
5750 new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
5752 // special case for teleporting from one end of the playfield to the other
5753 // (this kludge prevents the destination area to be shifted by half a tile
5754 // against the source destination for even screen width or screen height;
5755 // probably most useful when used with high "game.forced_scroll_delay_value"
5756 // in combination with "game.forced_scroll_x" and "game.forced_scroll_y")
5757 if (quick_relocation)
5759 if (EVEN(SCR_FIELDX))
5761 // relocate (teleport) between left and right border (half or full)
5762 if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1)
5763 new_scroll_x = SBX_Right;
5764 else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right)
5765 new_scroll_x = SBX_Right - 1;
5766 else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1)
5767 new_scroll_x = SBX_Left;
5768 else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left)
5769 new_scroll_x = SBX_Left + 1;
5772 if (EVEN(SCR_FIELDY))
5774 // relocate (teleport) between top and bottom border (half or full)
5775 if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1)
5776 new_scroll_y = SBY_Lower;
5777 else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower)
5778 new_scroll_y = SBY_Lower - 1;
5779 else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1)
5780 new_scroll_y = SBY_Upper;
5781 else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper)
5782 new_scroll_y = SBY_Upper + 1;
5787 if (quick_relocation)
5789 // case 2: quick relocation (redraw without visible scrolling)
5791 scroll_x = new_scroll_x;
5792 scroll_y = new_scroll_y;
5799 // case 3: visible relocation (with scrolling to new position)
5801 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5803 SetVideoFrameDelay(wait_delay_value);
5805 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5807 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5808 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5810 if (dx == 0 && dy == 0) // no scrolling needed at all
5816 // set values for horizontal/vertical screen scrolling (half tile size)
5817 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5818 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5819 int pos_x = dx * TILEX / 2;
5820 int pos_y = dy * TILEY / 2;
5821 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5822 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5824 ScrollLevel(dx, dy);
5827 // scroll in two steps of half tile size to make things smoother
5828 BlitScreenToBitmapExt_RND(window, fx, fy);
5830 // scroll second step to align at full tile size
5831 BlitScreenToBitmap(window);
5837 SetVideoFrameDelay(frame_delay_value_old);
5840 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5842 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5843 int player_nr = GET_PLAYER_NR(el_player);
5844 struct PlayerInfo *player = &stored_player[player_nr];
5845 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5846 boolean no_delay = (tape.warp_forward);
5847 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5848 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5849 int old_jx = player->jx;
5850 int old_jy = player->jy;
5851 int old_element = Tile[old_jx][old_jy];
5852 int element = Tile[jx][jy];
5853 boolean player_relocated = (old_jx != jx || old_jy != jy);
5855 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5856 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5857 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5858 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5859 int leave_side_horiz = move_dir_horiz;
5860 int leave_side_vert = move_dir_vert;
5861 int enter_side = enter_side_horiz | enter_side_vert;
5862 int leave_side = leave_side_horiz | leave_side_vert;
5864 if (player->buried) // do not reanimate dead player
5867 if (!player_relocated) // no need to relocate the player
5870 if (IS_PLAYER(jx, jy)) // player already placed at new position
5872 RemoveField(jx, jy); // temporarily remove newly placed player
5873 DrawLevelField(jx, jy);
5876 if (player->present)
5878 while (player->MovPos)
5880 ScrollPlayer(player, SCROLL_GO_ON);
5881 ScrollScreen(NULL, SCROLL_GO_ON);
5883 AdvanceFrameAndPlayerCounters(player->index_nr);
5887 BackToFront_WithFrameDelay(wait_delay_value);
5890 DrawPlayer(player); // needed here only to cleanup last field
5891 DrawLevelField(player->jx, player->jy); // remove player graphic
5893 player->is_moving = FALSE;
5896 if (IS_CUSTOM_ELEMENT(old_element))
5897 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5899 player->index_bit, leave_side);
5901 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5903 player->index_bit, leave_side);
5905 Tile[jx][jy] = el_player;
5906 InitPlayerField(jx, jy, el_player, TRUE);
5908 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5909 possible that the relocation target field did not contain a player element,
5910 but a walkable element, to which the new player was relocated -- in this
5911 case, restore that (already initialized!) element on the player field */
5912 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5914 Tile[jx][jy] = element; // restore previously existing element
5917 // only visually relocate centered player
5918 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy,
5919 FALSE, level.instant_relocation);
5921 TestIfPlayerTouchesBadThing(jx, jy);
5922 TestIfPlayerTouchesCustomElement(jx, jy);
5924 if (IS_CUSTOM_ELEMENT(element))
5925 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5926 player->index_bit, enter_side);
5928 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5929 player->index_bit, enter_side);
5931 if (player->is_switching)
5933 /* ensure that relocation while still switching an element does not cause
5934 a new element to be treated as also switched directly after relocation
5935 (this is important for teleporter switches that teleport the player to
5936 a place where another teleporter switch is in the same direction, which
5937 would then incorrectly be treated as immediately switched before the
5938 direction key that caused the switch was released) */
5940 player->switch_x += jx - old_jx;
5941 player->switch_y += jy - old_jy;
5945 static void Explode(int ex, int ey, int phase, int mode)
5951 if (game.explosions_delayed)
5953 ExplodeField[ex][ey] = mode;
5957 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5959 int center_element = Tile[ex][ey];
5960 int ce_value = CustomValue[ex][ey];
5961 int ce_score = element_info[center_element].collect_score;
5962 int artwork_element, explosion_element; // set these values later
5964 // remove things displayed in background while burning dynamite
5965 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5968 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5970 // put moving element to center field (and let it explode there)
5971 center_element = MovingOrBlocked2Element(ex, ey);
5972 RemoveMovingField(ex, ey);
5973 Tile[ex][ey] = center_element;
5976 // now "center_element" is finally determined -- set related values now
5977 artwork_element = center_element; // for custom player artwork
5978 explosion_element = center_element; // for custom player artwork
5980 if (IS_PLAYER(ex, ey))
5982 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5984 artwork_element = stored_player[player_nr].artwork_element;
5986 if (level.use_explosion_element[player_nr])
5988 explosion_element = level.explosion_element[player_nr];
5989 artwork_element = explosion_element;
5993 if (mode == EX_TYPE_NORMAL ||
5994 mode == EX_TYPE_CENTER ||
5995 mode == EX_TYPE_CROSS)
5996 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5998 last_phase = element_info[explosion_element].explosion_delay + 1;
6000 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
6002 int xx = x - ex + 1;
6003 int yy = y - ey + 1;
6006 if (!IN_LEV_FIELD(x, y) ||
6007 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
6008 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
6011 element = Tile[x][y];
6013 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
6015 element = MovingOrBlocked2Element(x, y);
6017 if (!IS_EXPLOSION_PROOF(element))
6018 RemoveMovingField(x, y);
6021 // indestructible elements can only explode in center (but not flames)
6022 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
6023 mode == EX_TYPE_BORDER)) ||
6024 element == EL_FLAMES)
6027 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
6028 behaviour, for example when touching a yamyam that explodes to rocks
6029 with active deadly shield, a rock is created under the player !!! */
6030 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
6032 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
6033 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
6034 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
6036 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
6039 if (IS_ACTIVE_BOMB(element))
6041 // re-activate things under the bomb like gate or penguin
6042 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
6049 // save walkable background elements while explosion on same tile
6050 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
6051 (x != ex || y != ey || mode == EX_TYPE_BORDER))
6052 Back[x][y] = element;
6054 // ignite explodable elements reached by other explosion
6055 if (element == EL_EXPLOSION)
6056 element = Store2[x][y];
6058 if (AmoebaNr[x][y] &&
6059 (element == EL_AMOEBA_FULL ||
6060 element == EL_BD_AMOEBA ||
6061 element == EL_AMOEBA_GROWING))
6063 AmoebaCnt[AmoebaNr[x][y]]--;
6064 AmoebaCnt2[AmoebaNr[x][y]]--;
6069 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
6071 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
6073 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
6075 if (PLAYERINFO(ex, ey)->use_murphy)
6076 Store[x][y] = EL_EMPTY;
6079 // !!! check this case -- currently needed for rnd_rado_negundo_v,
6080 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
6081 else if (IS_PLAYER_ELEMENT(center_element))
6082 Store[x][y] = EL_EMPTY;
6083 else if (center_element == EL_YAMYAM)
6084 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
6085 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
6086 Store[x][y] = element_info[center_element].content.e[xx][yy];
6088 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
6089 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
6090 // otherwise) -- FIX THIS !!!
6091 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
6092 Store[x][y] = element_info[element].content.e[1][1];
6094 else if (!CAN_EXPLODE(element))
6095 Store[x][y] = element_info[element].content.e[1][1];
6098 Store[x][y] = EL_EMPTY;
6100 if (IS_CUSTOM_ELEMENT(center_element))
6101 Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value :
6102 Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score :
6103 Store[x][y] >= EL_PREV_CE_8 &&
6104 Store[x][y] <= EL_NEXT_CE_8 ?
6105 RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) :
6108 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
6109 center_element == EL_AMOEBA_TO_DIAMOND)
6110 Store2[x][y] = element;
6112 Tile[x][y] = EL_EXPLOSION;
6113 GfxElement[x][y] = artwork_element;
6115 ExplodePhase[x][y] = 1;
6116 ExplodeDelay[x][y] = last_phase;
6121 if (center_element == EL_YAMYAM)
6122 game.yamyam_content_nr =
6123 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
6135 GfxFrame[x][y] = 0; // restart explosion animation
6137 last_phase = ExplodeDelay[x][y];
6139 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
6141 // this can happen if the player leaves an explosion just in time
6142 if (GfxElement[x][y] == EL_UNDEFINED)
6143 GfxElement[x][y] = EL_EMPTY;
6145 border_element = Store2[x][y];
6146 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6147 border_element = StorePlayer[x][y];
6149 if (phase == element_info[border_element].ignition_delay ||
6150 phase == last_phase)
6152 boolean border_explosion = FALSE;
6154 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6155 !PLAYER_EXPLOSION_PROTECTED(x, y))
6157 KillPlayerUnlessExplosionProtected(x, y);
6158 border_explosion = TRUE;
6160 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6162 Tile[x][y] = Store2[x][y];
6165 border_explosion = TRUE;
6167 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6169 AmoebaToDiamond(x, y);
6171 border_explosion = TRUE;
6174 // if an element just explodes due to another explosion (chain-reaction),
6175 // do not immediately end the new explosion when it was the last frame of
6176 // the explosion (as it would be done in the following "if"-statement!)
6177 if (border_explosion && phase == last_phase)
6181 // this can happen if the player was just killed by an explosion
6182 if (GfxElement[x][y] == EL_UNDEFINED)
6183 GfxElement[x][y] = EL_EMPTY;
6185 if (phase == last_phase)
6189 element = Tile[x][y] = Store[x][y];
6190 Store[x][y] = Store2[x][y] = 0;
6191 GfxElement[x][y] = EL_UNDEFINED;
6193 // player can escape from explosions and might therefore be still alive
6194 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6195 element <= EL_PLAYER_IS_EXPLODING_4)
6197 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6198 int explosion_element = EL_PLAYER_1 + player_nr;
6199 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6200 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6202 if (level.use_explosion_element[player_nr])
6203 explosion_element = level.explosion_element[player_nr];
6205 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6206 element_info[explosion_element].content.e[xx][yy]);
6209 // restore probably existing indestructible background element
6210 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6211 element = Tile[x][y] = Back[x][y];
6214 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6215 GfxDir[x][y] = MV_NONE;
6216 ChangeDelay[x][y] = 0;
6217 ChangePage[x][y] = -1;
6219 CustomValue[x][y] = 0;
6221 InitField_WithBug2(x, y, FALSE);
6223 TEST_DrawLevelField(x, y);
6225 TestIfElementTouchesCustomElement(x, y);
6227 if (GFX_CRUMBLED(element))
6228 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6230 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6231 StorePlayer[x][y] = 0;
6233 if (IS_PLAYER_ELEMENT(element))
6234 RelocatePlayer(x, y, element);
6236 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6238 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6239 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6242 TEST_DrawLevelFieldCrumbled(x, y);
6244 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6246 DrawLevelElement(x, y, Back[x][y]);
6247 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6249 else if (IS_WALKABLE_UNDER(Back[x][y]))
6251 DrawLevelGraphic(x, y, graphic, frame);
6252 DrawLevelElementThruMask(x, y, Back[x][y]);
6254 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6255 DrawLevelGraphic(x, y, graphic, frame);
6259 static void DynaExplode(int ex, int ey)
6262 int dynabomb_element = Tile[ex][ey];
6263 int dynabomb_size = 1;
6264 boolean dynabomb_xl = FALSE;
6265 struct PlayerInfo *player;
6266 struct XY *xy = xy_topdown;
6268 if (IS_ACTIVE_BOMB(dynabomb_element))
6270 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6271 dynabomb_size = player->dynabomb_size;
6272 dynabomb_xl = player->dynabomb_xl;
6273 player->dynabombs_left++;
6276 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6278 for (i = 0; i < NUM_DIRECTIONS; i++)
6280 for (j = 1; j <= dynabomb_size; j++)
6282 int x = ex + j * xy[i].x;
6283 int y = ey + j * xy[i].y;
6286 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6289 element = Tile[x][y];
6291 // do not restart explosions of fields with active bombs
6292 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6295 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6297 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6298 !IS_DIGGABLE(element) && !dynabomb_xl)
6304 void Bang(int x, int y)
6306 int element = MovingOrBlocked2Element(x, y);
6307 int explosion_type = EX_TYPE_NORMAL;
6309 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6311 struct PlayerInfo *player = PLAYERINFO(x, y);
6313 element = Tile[x][y] = player->initial_element;
6315 if (level.use_explosion_element[player->index_nr])
6317 int explosion_element = level.explosion_element[player->index_nr];
6319 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6320 explosion_type = EX_TYPE_CROSS;
6321 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6322 explosion_type = EX_TYPE_CENTER;
6330 case EL_BD_BUTTERFLY:
6333 case EL_DARK_YAMYAM:
6337 RaiseScoreElement(element);
6340 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6341 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6342 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6343 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6344 case EL_DYNABOMB_INCREASE_NUMBER:
6345 case EL_DYNABOMB_INCREASE_SIZE:
6346 case EL_DYNABOMB_INCREASE_POWER:
6347 explosion_type = EX_TYPE_DYNA;
6350 case EL_DC_LANDMINE:
6351 explosion_type = EX_TYPE_CENTER;
6356 case EL_LAMP_ACTIVE:
6357 case EL_AMOEBA_TO_DIAMOND:
6358 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6359 explosion_type = EX_TYPE_CENTER;
6363 if (element_info[element].explosion_type == EXPLODES_CROSS)
6364 explosion_type = EX_TYPE_CROSS;
6365 else if (element_info[element].explosion_type == EXPLODES_1X1)
6366 explosion_type = EX_TYPE_CENTER;
6370 if (explosion_type == EX_TYPE_DYNA)
6373 Explode(x, y, EX_PHASE_START, explosion_type);
6375 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6378 static void SplashAcid(int x, int y)
6380 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6381 (!IN_LEV_FIELD(x - 1, y - 2) ||
6382 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6383 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6385 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6386 (!IN_LEV_FIELD(x + 1, y - 2) ||
6387 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6388 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6390 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6393 static void InitBeltMovement(void)
6395 static int belt_base_element[4] =
6397 EL_CONVEYOR_BELT_1_LEFT,
6398 EL_CONVEYOR_BELT_2_LEFT,
6399 EL_CONVEYOR_BELT_3_LEFT,
6400 EL_CONVEYOR_BELT_4_LEFT
6402 static int belt_base_active_element[4] =
6404 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6405 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6406 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6407 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6412 // set frame order for belt animation graphic according to belt direction
6413 for (i = 0; i < NUM_BELTS; i++)
6417 for (j = 0; j < NUM_BELT_PARTS; j++)
6419 int element = belt_base_active_element[belt_nr] + j;
6420 int graphic_1 = el2img(element);
6421 int graphic_2 = el2panelimg(element);
6423 if (game.belt_dir[i] == MV_LEFT)
6425 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6426 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6430 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6431 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6436 SCAN_PLAYFIELD(x, y)
6438 int element = Tile[x][y];
6440 for (i = 0; i < NUM_BELTS; i++)
6442 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6444 int e_belt_nr = getBeltNrFromBeltElement(element);
6447 if (e_belt_nr == belt_nr)
6449 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6451 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6458 static void ToggleBeltSwitch(int x, int y)
6460 static int belt_base_element[4] =
6462 EL_CONVEYOR_BELT_1_LEFT,
6463 EL_CONVEYOR_BELT_2_LEFT,
6464 EL_CONVEYOR_BELT_3_LEFT,
6465 EL_CONVEYOR_BELT_4_LEFT
6467 static int belt_base_active_element[4] =
6469 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6470 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6471 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6472 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6474 static int belt_base_switch_element[4] =
6476 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6477 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6478 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6479 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6481 static int belt_move_dir[4] =
6489 int element = Tile[x][y];
6490 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6491 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6492 int belt_dir = belt_move_dir[belt_dir_nr];
6495 if (!IS_BELT_SWITCH(element))
6498 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6499 game.belt_dir[belt_nr] = belt_dir;
6501 if (belt_dir_nr == 3)
6504 // set frame order for belt animation graphic according to belt direction
6505 for (i = 0; i < NUM_BELT_PARTS; i++)
6507 int element = belt_base_active_element[belt_nr] + i;
6508 int graphic_1 = el2img(element);
6509 int graphic_2 = el2panelimg(element);
6511 if (belt_dir == MV_LEFT)
6513 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6514 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6518 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6519 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6523 SCAN_PLAYFIELD(xx, yy)
6525 int element = Tile[xx][yy];
6527 if (IS_BELT_SWITCH(element))
6529 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6531 if (e_belt_nr == belt_nr)
6533 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6534 TEST_DrawLevelField(xx, yy);
6537 else if (IS_BELT(element) && belt_dir != MV_NONE)
6539 int e_belt_nr = getBeltNrFromBeltElement(element);
6541 if (e_belt_nr == belt_nr)
6543 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6545 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6546 TEST_DrawLevelField(xx, yy);
6549 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6551 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6553 if (e_belt_nr == belt_nr)
6555 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6557 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6558 TEST_DrawLevelField(xx, yy);
6564 static void ToggleSwitchgateSwitch(void)
6568 game.switchgate_pos = !game.switchgate_pos;
6570 SCAN_PLAYFIELD(xx, yy)
6572 int element = Tile[xx][yy];
6574 if (element == EL_SWITCHGATE_SWITCH_UP)
6576 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6577 TEST_DrawLevelField(xx, yy);
6579 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6581 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6582 TEST_DrawLevelField(xx, yy);
6584 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6586 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6587 TEST_DrawLevelField(xx, yy);
6589 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6591 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6592 TEST_DrawLevelField(xx, yy);
6594 else if (element == EL_SWITCHGATE_OPEN ||
6595 element == EL_SWITCHGATE_OPENING)
6597 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6599 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6601 else if (element == EL_SWITCHGATE_CLOSED ||
6602 element == EL_SWITCHGATE_CLOSING)
6604 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6606 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6611 static int getInvisibleActiveFromInvisibleElement(int element)
6613 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6614 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6615 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6619 static int getInvisibleFromInvisibleActiveElement(int element)
6621 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6622 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6623 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6627 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6631 SCAN_PLAYFIELD(x, y)
6633 int element = Tile[x][y];
6635 if (element == EL_LIGHT_SWITCH &&
6636 game.light_time_left > 0)
6638 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6639 TEST_DrawLevelField(x, y);
6641 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6642 game.light_time_left == 0)
6644 Tile[x][y] = EL_LIGHT_SWITCH;
6645 TEST_DrawLevelField(x, y);
6647 else if (element == EL_EMC_DRIPPER &&
6648 game.light_time_left > 0)
6650 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6651 TEST_DrawLevelField(x, y);
6653 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6654 game.light_time_left == 0)
6656 Tile[x][y] = EL_EMC_DRIPPER;
6657 TEST_DrawLevelField(x, y);
6659 else if (element == EL_INVISIBLE_STEELWALL ||
6660 element == EL_INVISIBLE_WALL ||
6661 element == EL_INVISIBLE_SAND)
6663 if (game.light_time_left > 0)
6664 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6666 TEST_DrawLevelField(x, y);
6668 // uncrumble neighbour fields, if needed
6669 if (element == EL_INVISIBLE_SAND)
6670 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6672 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6673 element == EL_INVISIBLE_WALL_ACTIVE ||
6674 element == EL_INVISIBLE_SAND_ACTIVE)
6676 if (game.light_time_left == 0)
6677 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6679 TEST_DrawLevelField(x, y);
6681 // re-crumble neighbour fields, if needed
6682 if (element == EL_INVISIBLE_SAND)
6683 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6688 static void RedrawAllInvisibleElementsForLenses(void)
6692 SCAN_PLAYFIELD(x, y)
6694 int element = Tile[x][y];
6696 if (element == EL_EMC_DRIPPER &&
6697 game.lenses_time_left > 0)
6699 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6700 TEST_DrawLevelField(x, y);
6702 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6703 game.lenses_time_left == 0)
6705 Tile[x][y] = EL_EMC_DRIPPER;
6706 TEST_DrawLevelField(x, y);
6708 else if (element == EL_INVISIBLE_STEELWALL ||
6709 element == EL_INVISIBLE_WALL ||
6710 element == EL_INVISIBLE_SAND)
6712 if (game.lenses_time_left > 0)
6713 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6715 TEST_DrawLevelField(x, y);
6717 // uncrumble neighbour fields, if needed
6718 if (element == EL_INVISIBLE_SAND)
6719 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6721 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6722 element == EL_INVISIBLE_WALL_ACTIVE ||
6723 element == EL_INVISIBLE_SAND_ACTIVE)
6725 if (game.lenses_time_left == 0)
6726 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6728 TEST_DrawLevelField(x, y);
6730 // re-crumble neighbour fields, if needed
6731 if (element == EL_INVISIBLE_SAND)
6732 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6737 static void RedrawAllInvisibleElementsForMagnifier(void)
6741 SCAN_PLAYFIELD(x, y)
6743 int element = Tile[x][y];
6745 if (element == EL_EMC_FAKE_GRASS &&
6746 game.magnify_time_left > 0)
6748 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6749 TEST_DrawLevelField(x, y);
6751 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6752 game.magnify_time_left == 0)
6754 Tile[x][y] = EL_EMC_FAKE_GRASS;
6755 TEST_DrawLevelField(x, y);
6757 else if (IS_GATE_GRAY(element) &&
6758 game.magnify_time_left > 0)
6760 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6761 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6762 IS_EM_GATE_GRAY(element) ?
6763 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6764 IS_EMC_GATE_GRAY(element) ?
6765 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6766 IS_DC_GATE_GRAY(element) ?
6767 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6769 TEST_DrawLevelField(x, y);
6771 else if (IS_GATE_GRAY_ACTIVE(element) &&
6772 game.magnify_time_left == 0)
6774 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6775 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6776 IS_EM_GATE_GRAY_ACTIVE(element) ?
6777 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6778 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6779 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6780 IS_DC_GATE_GRAY_ACTIVE(element) ?
6781 EL_DC_GATE_WHITE_GRAY :
6783 TEST_DrawLevelField(x, y);
6788 static void ToggleLightSwitch(int x, int y)
6790 int element = Tile[x][y];
6792 game.light_time_left =
6793 (element == EL_LIGHT_SWITCH ?
6794 level.time_light * FRAMES_PER_SECOND : 0);
6796 RedrawAllLightSwitchesAndInvisibleElements();
6799 static void ActivateTimegateSwitch(int x, int y)
6803 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6805 SCAN_PLAYFIELD(xx, yy)
6807 int element = Tile[xx][yy];
6809 if (element == EL_TIMEGATE_CLOSED ||
6810 element == EL_TIMEGATE_CLOSING)
6812 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6813 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6817 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6819 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6820 TEST_DrawLevelField(xx, yy);
6826 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6827 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6830 static void Impact(int x, int y)
6832 boolean last_line = (y == lev_fieldy - 1);
6833 boolean object_hit = FALSE;
6834 boolean impact = (last_line || object_hit);
6835 int element = Tile[x][y];
6836 int smashed = EL_STEELWALL;
6838 if (!last_line) // check if element below was hit
6840 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6843 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6844 MovDir[x][y + 1] != MV_DOWN ||
6845 MovPos[x][y + 1] <= TILEY / 2));
6847 // do not smash moving elements that left the smashed field in time
6848 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6849 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6852 #if USE_QUICKSAND_IMPACT_BUGFIX
6853 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6855 RemoveMovingField(x, y + 1);
6856 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6857 Tile[x][y + 2] = EL_ROCK;
6858 TEST_DrawLevelField(x, y + 2);
6863 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6865 RemoveMovingField(x, y + 1);
6866 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6867 Tile[x][y + 2] = EL_ROCK;
6868 TEST_DrawLevelField(x, y + 2);
6875 smashed = MovingOrBlocked2Element(x, y + 1);
6877 impact = (last_line || object_hit);
6880 if (!last_line && smashed == EL_ACID) // element falls into acid
6882 SplashAcid(x, y + 1);
6886 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6887 // only reset graphic animation if graphic really changes after impact
6889 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6891 ResetGfxAnimation(x, y);
6892 TEST_DrawLevelField(x, y);
6895 if (impact && CAN_EXPLODE_IMPACT(element))
6900 else if (impact && element == EL_PEARL &&
6901 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6903 ResetGfxAnimation(x, y);
6905 Tile[x][y] = EL_PEARL_BREAKING;
6906 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6909 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6911 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6916 if (impact && element == EL_AMOEBA_DROP)
6918 if (object_hit && IS_PLAYER(x, y + 1))
6919 KillPlayerUnlessEnemyProtected(x, y + 1);
6920 else if (object_hit && smashed == EL_PENGUIN)
6924 Tile[x][y] = EL_AMOEBA_GROWING;
6925 Store[x][y] = EL_AMOEBA_WET;
6927 ResetRandomAnimationValue(x, y);
6932 if (object_hit) // check which object was hit
6934 if ((CAN_PASS_MAGIC_WALL(element) &&
6935 (smashed == EL_MAGIC_WALL ||
6936 smashed == EL_BD_MAGIC_WALL)) ||
6937 (CAN_PASS_DC_MAGIC_WALL(element) &&
6938 smashed == EL_DC_MAGIC_WALL))
6941 int activated_magic_wall =
6942 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6943 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6944 EL_DC_MAGIC_WALL_ACTIVE);
6946 // activate magic wall / mill
6947 SCAN_PLAYFIELD(xx, yy)
6949 if (Tile[xx][yy] == smashed)
6950 Tile[xx][yy] = activated_magic_wall;
6953 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6954 game.magic_wall_active = TRUE;
6956 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6957 SND_MAGIC_WALL_ACTIVATING :
6958 smashed == EL_BD_MAGIC_WALL ?
6959 SND_BD_MAGIC_WALL_ACTIVATING :
6960 SND_DC_MAGIC_WALL_ACTIVATING));
6963 if (IS_PLAYER(x, y + 1))
6965 if (CAN_SMASH_PLAYER(element))
6967 KillPlayerUnlessEnemyProtected(x, y + 1);
6971 else if (smashed == EL_PENGUIN)
6973 if (CAN_SMASH_PLAYER(element))
6979 else if (element == EL_BD_DIAMOND)
6981 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6987 else if (((element == EL_SP_INFOTRON ||
6988 element == EL_SP_ZONK) &&
6989 (smashed == EL_SP_SNIKSNAK ||
6990 smashed == EL_SP_ELECTRON ||
6991 smashed == EL_SP_DISK_ORANGE)) ||
6992 (element == EL_SP_INFOTRON &&
6993 smashed == EL_SP_DISK_YELLOW))
6998 else if (CAN_SMASH_EVERYTHING(element))
7000 if (IS_CLASSIC_ENEMY(smashed) ||
7001 CAN_EXPLODE_SMASHED(smashed))
7006 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
7008 if (smashed == EL_LAMP ||
7009 smashed == EL_LAMP_ACTIVE)
7014 else if (smashed == EL_NUT)
7016 Tile[x][y + 1] = EL_NUT_BREAKING;
7017 PlayLevelSound(x, y, SND_NUT_BREAKING);
7018 RaiseScoreElement(EL_NUT);
7021 else if (smashed == EL_PEARL)
7023 ResetGfxAnimation(x, y);
7025 Tile[x][y + 1] = EL_PEARL_BREAKING;
7026 PlayLevelSound(x, y, SND_PEARL_BREAKING);
7029 else if (smashed == EL_DIAMOND)
7031 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
7032 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
7035 else if (IS_BELT_SWITCH(smashed))
7037 ToggleBeltSwitch(x, y + 1);
7039 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
7040 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
7041 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
7042 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
7044 ToggleSwitchgateSwitch();
7046 else if (smashed == EL_LIGHT_SWITCH ||
7047 smashed == EL_LIGHT_SWITCH_ACTIVE)
7049 ToggleLightSwitch(x, y + 1);
7053 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7055 CheckElementChangeBySide(x, y + 1, smashed, element,
7056 CE_SWITCHED, CH_SIDE_TOP);
7057 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
7063 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7068 // play sound of magic wall / mill
7070 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7071 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
7072 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
7074 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7075 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
7076 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7077 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
7078 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7079 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
7084 // play sound of object that hits the ground
7085 if (last_line || object_hit)
7086 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
7089 static void TurnRoundExt(int x, int y)
7101 { 0, 0 }, { 0, 0 }, { 0, 0 },
7106 int left, right, back;
7110 { MV_DOWN, MV_UP, MV_RIGHT },
7111 { MV_UP, MV_DOWN, MV_LEFT },
7113 { MV_LEFT, MV_RIGHT, MV_DOWN },
7117 { MV_RIGHT, MV_LEFT, MV_UP }
7120 int element = Tile[x][y];
7121 int move_pattern = element_info[element].move_pattern;
7123 int old_move_dir = MovDir[x][y];
7124 int left_dir = turn[old_move_dir].left;
7125 int right_dir = turn[old_move_dir].right;
7126 int back_dir = turn[old_move_dir].back;
7128 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
7129 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
7130 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
7131 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
7133 int left_x = x + left_dx, left_y = y + left_dy;
7134 int right_x = x + right_dx, right_y = y + right_dy;
7135 int move_x = x + move_dx, move_y = y + move_dy;
7139 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
7141 TestIfBadThingTouchesOtherBadThing(x, y);
7143 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
7144 MovDir[x][y] = right_dir;
7145 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7146 MovDir[x][y] = left_dir;
7148 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7150 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7153 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7155 TestIfBadThingTouchesOtherBadThing(x, y);
7157 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7158 MovDir[x][y] = left_dir;
7159 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7160 MovDir[x][y] = right_dir;
7162 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7164 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7167 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7169 TestIfBadThingTouchesOtherBadThing(x, y);
7171 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7172 MovDir[x][y] = left_dir;
7173 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7174 MovDir[x][y] = right_dir;
7176 if (MovDir[x][y] != old_move_dir)
7179 else if (element == EL_YAMYAM)
7181 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7182 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7184 if (can_turn_left && can_turn_right)
7185 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7186 else if (can_turn_left)
7187 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7188 else if (can_turn_right)
7189 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7191 MovDir[x][y] = back_dir;
7193 MovDelay[x][y] = 16 + 16 * RND(3);
7195 else if (element == EL_DARK_YAMYAM)
7197 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7199 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7202 if (can_turn_left && can_turn_right)
7203 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7204 else if (can_turn_left)
7205 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7206 else if (can_turn_right)
7207 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7209 MovDir[x][y] = back_dir;
7211 MovDelay[x][y] = 16 + 16 * RND(3);
7213 else if (element == EL_PACMAN)
7215 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7216 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7218 if (can_turn_left && can_turn_right)
7219 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7220 else if (can_turn_left)
7221 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7222 else if (can_turn_right)
7223 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7225 MovDir[x][y] = back_dir;
7227 MovDelay[x][y] = 6 + RND(40);
7229 else if (element == EL_PIG)
7231 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7232 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7233 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7234 boolean should_turn_left, should_turn_right, should_move_on;
7236 int rnd = RND(rnd_value);
7238 should_turn_left = (can_turn_left &&
7240 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7241 y + back_dy + left_dy)));
7242 should_turn_right = (can_turn_right &&
7244 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7245 y + back_dy + right_dy)));
7246 should_move_on = (can_move_on &&
7249 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7250 y + move_dy + left_dy) ||
7251 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7252 y + move_dy + right_dy)));
7254 if (should_turn_left || should_turn_right || should_move_on)
7256 if (should_turn_left && should_turn_right && should_move_on)
7257 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7258 rnd < 2 * rnd_value / 3 ? right_dir :
7260 else if (should_turn_left && should_turn_right)
7261 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7262 else if (should_turn_left && should_move_on)
7263 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7264 else if (should_turn_right && should_move_on)
7265 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7266 else if (should_turn_left)
7267 MovDir[x][y] = left_dir;
7268 else if (should_turn_right)
7269 MovDir[x][y] = right_dir;
7270 else if (should_move_on)
7271 MovDir[x][y] = old_move_dir;
7273 else if (can_move_on && rnd > rnd_value / 8)
7274 MovDir[x][y] = old_move_dir;
7275 else if (can_turn_left && can_turn_right)
7276 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7277 else if (can_turn_left && rnd > rnd_value / 8)
7278 MovDir[x][y] = left_dir;
7279 else if (can_turn_right && rnd > rnd_value/8)
7280 MovDir[x][y] = right_dir;
7282 MovDir[x][y] = back_dir;
7284 xx = x + move_xy[MovDir[x][y]].dx;
7285 yy = y + move_xy[MovDir[x][y]].dy;
7287 if (!IN_LEV_FIELD(xx, yy) ||
7288 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7289 MovDir[x][y] = old_move_dir;
7293 else if (element == EL_DRAGON)
7295 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7296 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7297 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7299 int rnd = RND(rnd_value);
7301 if (can_move_on && rnd > rnd_value / 8)
7302 MovDir[x][y] = old_move_dir;
7303 else if (can_turn_left && can_turn_right)
7304 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7305 else if (can_turn_left && rnd > rnd_value / 8)
7306 MovDir[x][y] = left_dir;
7307 else if (can_turn_right && rnd > rnd_value / 8)
7308 MovDir[x][y] = right_dir;
7310 MovDir[x][y] = back_dir;
7312 xx = x + move_xy[MovDir[x][y]].dx;
7313 yy = y + move_xy[MovDir[x][y]].dy;
7315 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7316 MovDir[x][y] = old_move_dir;
7320 else if (element == EL_MOLE)
7322 boolean can_move_on =
7323 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7324 IS_AMOEBOID(Tile[move_x][move_y]) ||
7325 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7328 boolean can_turn_left =
7329 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7330 IS_AMOEBOID(Tile[left_x][left_y])));
7332 boolean can_turn_right =
7333 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7334 IS_AMOEBOID(Tile[right_x][right_y])));
7336 if (can_turn_left && can_turn_right)
7337 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7338 else if (can_turn_left)
7339 MovDir[x][y] = left_dir;
7341 MovDir[x][y] = right_dir;
7344 if (MovDir[x][y] != old_move_dir)
7347 else if (element == EL_BALLOON)
7349 MovDir[x][y] = game.wind_direction;
7352 else if (element == EL_SPRING)
7354 if (MovDir[x][y] & MV_HORIZONTAL)
7356 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7357 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7359 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7360 ResetGfxAnimation(move_x, move_y);
7361 TEST_DrawLevelField(move_x, move_y);
7363 MovDir[x][y] = back_dir;
7365 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7366 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7367 MovDir[x][y] = MV_NONE;
7372 else if (element == EL_ROBOT ||
7373 element == EL_SATELLITE ||
7374 element == EL_PENGUIN ||
7375 element == EL_EMC_ANDROID)
7377 int attr_x = -1, attr_y = -1;
7379 if (game.all_players_gone)
7381 attr_x = game.exit_x;
7382 attr_y = game.exit_y;
7388 for (i = 0; i < MAX_PLAYERS; i++)
7390 struct PlayerInfo *player = &stored_player[i];
7391 int jx = player->jx, jy = player->jy;
7393 if (!player->active)
7397 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7405 if (element == EL_ROBOT &&
7406 game.robot_wheel_x >= 0 &&
7407 game.robot_wheel_y >= 0 &&
7408 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7409 game.engine_version < VERSION_IDENT(3,1,0,0)))
7411 attr_x = game.robot_wheel_x;
7412 attr_y = game.robot_wheel_y;
7415 if (element == EL_PENGUIN)
7418 struct XY *xy = xy_topdown;
7420 for (i = 0; i < NUM_DIRECTIONS; i++)
7422 int ex = x + xy[i].x;
7423 int ey = y + xy[i].y;
7425 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7426 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7427 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7428 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7437 MovDir[x][y] = MV_NONE;
7439 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7440 else if (attr_x > x)
7441 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7443 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7444 else if (attr_y > y)
7445 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7447 if (element == EL_ROBOT)
7451 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7452 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7453 Moving2Blocked(x, y, &newx, &newy);
7455 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7456 MovDelay[x][y] = 8 + 8 * !RND(3);
7458 MovDelay[x][y] = 16;
7460 else if (element == EL_PENGUIN)
7466 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7468 boolean first_horiz = RND(2);
7469 int new_move_dir = MovDir[x][y];
7472 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7473 Moving2Blocked(x, y, &newx, &newy);
7475 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7479 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7480 Moving2Blocked(x, y, &newx, &newy);
7482 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7485 MovDir[x][y] = old_move_dir;
7489 else if (element == EL_SATELLITE)
7495 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7497 boolean first_horiz = RND(2);
7498 int new_move_dir = MovDir[x][y];
7501 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7502 Moving2Blocked(x, y, &newx, &newy);
7504 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7508 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7509 Moving2Blocked(x, y, &newx, &newy);
7511 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7514 MovDir[x][y] = old_move_dir;
7518 else if (element == EL_EMC_ANDROID)
7520 static int check_pos[16] =
7522 -1, // 0 => (invalid)
7525 -1, // 3 => (invalid)
7527 0, // 5 => MV_LEFT | MV_UP
7528 2, // 6 => MV_RIGHT | MV_UP
7529 -1, // 7 => (invalid)
7531 6, // 9 => MV_LEFT | MV_DOWN
7532 4, // 10 => MV_RIGHT | MV_DOWN
7533 -1, // 11 => (invalid)
7534 -1, // 12 => (invalid)
7535 -1, // 13 => (invalid)
7536 -1, // 14 => (invalid)
7537 -1, // 15 => (invalid)
7545 { -1, -1, MV_LEFT | MV_UP },
7547 { +1, -1, MV_RIGHT | MV_UP },
7548 { +1, 0, MV_RIGHT },
7549 { +1, +1, MV_RIGHT | MV_DOWN },
7551 { -1, +1, MV_LEFT | MV_DOWN },
7554 int start_pos, check_order;
7555 boolean can_clone = FALSE;
7558 // check if there is any free field around current position
7559 for (i = 0; i < 8; i++)
7561 int newx = x + check_xy[i].dx;
7562 int newy = y + check_xy[i].dy;
7564 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7572 if (can_clone) // randomly find an element to clone
7576 start_pos = check_pos[RND(8)];
7577 check_order = (RND(2) ? -1 : +1);
7579 for (i = 0; i < 8; i++)
7581 int pos_raw = start_pos + i * check_order;
7582 int pos = (pos_raw + 8) % 8;
7583 int newx = x + check_xy[pos].dx;
7584 int newy = y + check_xy[pos].dy;
7586 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7588 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7589 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7591 Store[x][y] = Tile[newx][newy];
7600 if (can_clone) // randomly find a direction to move
7604 start_pos = check_pos[RND(8)];
7605 check_order = (RND(2) ? -1 : +1);
7607 for (i = 0; i < 8; i++)
7609 int pos_raw = start_pos + i * check_order;
7610 int pos = (pos_raw + 8) % 8;
7611 int newx = x + check_xy[pos].dx;
7612 int newy = y + check_xy[pos].dy;
7613 int new_move_dir = check_xy[pos].dir;
7615 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7617 MovDir[x][y] = new_move_dir;
7618 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7627 if (can_clone) // cloning and moving successful
7630 // cannot clone -- try to move towards player
7632 start_pos = check_pos[MovDir[x][y] & 0x0f];
7633 check_order = (RND(2) ? -1 : +1);
7635 for (i = 0; i < 3; i++)
7637 // first check start_pos, then previous/next or (next/previous) pos
7638 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7639 int pos = (pos_raw + 8) % 8;
7640 int newx = x + check_xy[pos].dx;
7641 int newy = y + check_xy[pos].dy;
7642 int new_move_dir = check_xy[pos].dir;
7644 if (IS_PLAYER(newx, newy))
7647 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7649 MovDir[x][y] = new_move_dir;
7650 MovDelay[x][y] = level.android_move_time * 8 + 1;
7657 else if (move_pattern == MV_TURNING_LEFT ||
7658 move_pattern == MV_TURNING_RIGHT ||
7659 move_pattern == MV_TURNING_LEFT_RIGHT ||
7660 move_pattern == MV_TURNING_RIGHT_LEFT ||
7661 move_pattern == MV_TURNING_RANDOM ||
7662 move_pattern == MV_ALL_DIRECTIONS)
7664 boolean can_turn_left =
7665 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7666 boolean can_turn_right =
7667 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y);
7669 if (element_info[element].move_stepsize == 0) // "not moving"
7672 if (move_pattern == MV_TURNING_LEFT)
7673 MovDir[x][y] = left_dir;
7674 else if (move_pattern == MV_TURNING_RIGHT)
7675 MovDir[x][y] = right_dir;
7676 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7677 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7678 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7679 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7680 else if (move_pattern == MV_TURNING_RANDOM)
7681 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7682 can_turn_right && !can_turn_left ? right_dir :
7683 RND(2) ? left_dir : right_dir);
7684 else if (can_turn_left && can_turn_right)
7685 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7686 else if (can_turn_left)
7687 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7688 else if (can_turn_right)
7689 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7691 MovDir[x][y] = back_dir;
7693 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7695 else if (move_pattern == MV_HORIZONTAL ||
7696 move_pattern == MV_VERTICAL)
7698 if (move_pattern & old_move_dir)
7699 MovDir[x][y] = back_dir;
7700 else if (move_pattern == MV_HORIZONTAL)
7701 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7702 else if (move_pattern == MV_VERTICAL)
7703 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7705 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7707 else if (move_pattern & MV_ANY_DIRECTION)
7709 MovDir[x][y] = move_pattern;
7710 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7712 else if (move_pattern & MV_WIND_DIRECTION)
7714 MovDir[x][y] = game.wind_direction;
7715 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7717 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7719 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7720 MovDir[x][y] = left_dir;
7721 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7722 MovDir[x][y] = right_dir;
7724 if (MovDir[x][y] != old_move_dir)
7725 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7727 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7729 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7730 MovDir[x][y] = right_dir;
7731 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7732 MovDir[x][y] = left_dir;
7734 if (MovDir[x][y] != old_move_dir)
7735 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7737 else if (move_pattern == MV_TOWARDS_PLAYER ||
7738 move_pattern == MV_AWAY_FROM_PLAYER)
7740 int attr_x = -1, attr_y = -1;
7742 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7744 if (game.all_players_gone)
7746 attr_x = game.exit_x;
7747 attr_y = game.exit_y;
7753 for (i = 0; i < MAX_PLAYERS; i++)
7755 struct PlayerInfo *player = &stored_player[i];
7756 int jx = player->jx, jy = player->jy;
7758 if (!player->active)
7762 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7770 MovDir[x][y] = MV_NONE;
7772 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7773 else if (attr_x > x)
7774 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7776 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7777 else if (attr_y > y)
7778 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7780 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7782 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7784 boolean first_horiz = RND(2);
7785 int new_move_dir = MovDir[x][y];
7787 if (element_info[element].move_stepsize == 0) // "not moving"
7789 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7790 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7796 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7797 Moving2Blocked(x, y, &newx, &newy);
7799 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7803 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7804 Moving2Blocked(x, y, &newx, &newy);
7806 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7809 MovDir[x][y] = old_move_dir;
7812 else if (move_pattern == MV_WHEN_PUSHED ||
7813 move_pattern == MV_WHEN_DROPPED)
7815 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7816 MovDir[x][y] = MV_NONE;
7820 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7822 struct XY *test_xy = xy_topdown;
7823 static int test_dir[4] =
7830 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7831 int move_preference = -1000000; // start with very low preference
7832 int new_move_dir = MV_NONE;
7833 int start_test = RND(4);
7836 for (i = 0; i < NUM_DIRECTIONS; i++)
7838 int j = (start_test + i) % 4;
7839 int move_dir = test_dir[j];
7840 int move_dir_preference;
7842 xx = x + test_xy[j].x;
7843 yy = y + test_xy[j].y;
7845 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7846 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7848 new_move_dir = move_dir;
7853 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7856 move_dir_preference = -1 * RunnerVisit[xx][yy];
7857 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7858 move_dir_preference = PlayerVisit[xx][yy];
7860 if (move_dir_preference > move_preference)
7862 // prefer field that has not been visited for the longest time
7863 move_preference = move_dir_preference;
7864 new_move_dir = move_dir;
7866 else if (move_dir_preference == move_preference &&
7867 move_dir == old_move_dir)
7869 // prefer last direction when all directions are preferred equally
7870 move_preference = move_dir_preference;
7871 new_move_dir = move_dir;
7875 MovDir[x][y] = new_move_dir;
7876 if (old_move_dir != new_move_dir)
7877 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7881 static void TurnRound(int x, int y)
7883 int direction = MovDir[x][y];
7887 GfxDir[x][y] = MovDir[x][y];
7889 if (direction != MovDir[x][y])
7893 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7895 ResetGfxFrame(x, y);
7898 static boolean JustBeingPushed(int x, int y)
7902 for (i = 0; i < MAX_PLAYERS; i++)
7904 struct PlayerInfo *player = &stored_player[i];
7906 if (player->active && player->is_pushing && player->MovPos)
7908 int next_jx = player->jx + (player->jx - player->last_jx);
7909 int next_jy = player->jy + (player->jy - player->last_jy);
7911 if (x == next_jx && y == next_jy)
7919 static void StartMoving(int x, int y)
7921 boolean started_moving = FALSE; // some elements can fall _and_ move
7922 int element = Tile[x][y];
7927 if (MovDelay[x][y] == 0)
7928 GfxAction[x][y] = ACTION_DEFAULT;
7930 if (CAN_FALL(element) && y < lev_fieldy - 1)
7932 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7933 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7934 if (JustBeingPushed(x, y))
7937 if (element == EL_QUICKSAND_FULL)
7939 if (IS_FREE(x, y + 1))
7941 InitMovingField(x, y, MV_DOWN);
7942 started_moving = TRUE;
7944 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7945 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7946 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7947 Store[x][y] = EL_ROCK;
7949 Store[x][y] = EL_ROCK;
7952 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7954 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7956 if (!MovDelay[x][y])
7958 MovDelay[x][y] = TILEY + 1;
7960 ResetGfxAnimation(x, y);
7961 ResetGfxAnimation(x, y + 1);
7966 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7967 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7974 Tile[x][y] = EL_QUICKSAND_EMPTY;
7975 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7976 Store[x][y + 1] = Store[x][y];
7979 PlayLevelSoundAction(x, y, ACTION_FILLING);
7981 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7983 if (!MovDelay[x][y])
7985 MovDelay[x][y] = TILEY + 1;
7987 ResetGfxAnimation(x, y);
7988 ResetGfxAnimation(x, y + 1);
7993 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7994 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8001 Tile[x][y] = EL_QUICKSAND_EMPTY;
8002 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8003 Store[x][y + 1] = Store[x][y];
8006 PlayLevelSoundAction(x, y, ACTION_FILLING);
8009 else if (element == EL_QUICKSAND_FAST_FULL)
8011 if (IS_FREE(x, y + 1))
8013 InitMovingField(x, y, MV_DOWN);
8014 started_moving = TRUE;
8016 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
8017 #if USE_QUICKSAND_BD_ROCK_BUGFIX
8018 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
8019 Store[x][y] = EL_ROCK;
8021 Store[x][y] = EL_ROCK;
8024 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
8026 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8028 if (!MovDelay[x][y])
8030 MovDelay[x][y] = TILEY + 1;
8032 ResetGfxAnimation(x, y);
8033 ResetGfxAnimation(x, y + 1);
8038 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8039 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8046 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8047 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8048 Store[x][y + 1] = Store[x][y];
8051 PlayLevelSoundAction(x, y, ACTION_FILLING);
8053 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8055 if (!MovDelay[x][y])
8057 MovDelay[x][y] = TILEY + 1;
8059 ResetGfxAnimation(x, y);
8060 ResetGfxAnimation(x, y + 1);
8065 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8066 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8073 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8074 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8075 Store[x][y + 1] = Store[x][y];
8078 PlayLevelSoundAction(x, y, ACTION_FILLING);
8081 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8082 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8084 InitMovingField(x, y, MV_DOWN);
8085 started_moving = TRUE;
8087 Tile[x][y] = EL_QUICKSAND_FILLING;
8088 Store[x][y] = element;
8090 PlayLevelSoundAction(x, y, ACTION_FILLING);
8092 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8093 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8095 InitMovingField(x, y, MV_DOWN);
8096 started_moving = TRUE;
8098 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
8099 Store[x][y] = element;
8101 PlayLevelSoundAction(x, y, ACTION_FILLING);
8103 else if (element == EL_MAGIC_WALL_FULL)
8105 if (IS_FREE(x, y + 1))
8107 InitMovingField(x, y, MV_DOWN);
8108 started_moving = TRUE;
8110 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
8111 Store[x][y] = EL_CHANGED(Store[x][y]);
8113 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
8115 if (!MovDelay[x][y])
8116 MovDelay[x][y] = TILEY / 4 + 1;
8125 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
8126 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
8127 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8131 else if (element == EL_BD_MAGIC_WALL_FULL)
8133 if (IS_FREE(x, y + 1))
8135 InitMovingField(x, y, MV_DOWN);
8136 started_moving = TRUE;
8138 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8139 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8141 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8143 if (!MovDelay[x][y])
8144 MovDelay[x][y] = TILEY / 4 + 1;
8153 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8154 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8155 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8159 else if (element == EL_DC_MAGIC_WALL_FULL)
8161 if (IS_FREE(x, y + 1))
8163 InitMovingField(x, y, MV_DOWN);
8164 started_moving = TRUE;
8166 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8167 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8169 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8171 if (!MovDelay[x][y])
8172 MovDelay[x][y] = TILEY / 4 + 1;
8181 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8182 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8183 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8187 else if ((CAN_PASS_MAGIC_WALL(element) &&
8188 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8189 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8190 (CAN_PASS_DC_MAGIC_WALL(element) &&
8191 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8194 InitMovingField(x, y, MV_DOWN);
8195 started_moving = TRUE;
8198 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8199 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8200 EL_DC_MAGIC_WALL_FILLING);
8201 Store[x][y] = element;
8203 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8205 SplashAcid(x, y + 1);
8207 InitMovingField(x, y, MV_DOWN);
8208 started_moving = TRUE;
8210 Store[x][y] = EL_ACID;
8213 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8214 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8215 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8216 CAN_FALL(element) && WasJustFalling[x][y] &&
8217 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8219 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8220 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8221 (Tile[x][y + 1] == EL_BLOCKED)))
8223 /* this is needed for a special case not covered by calling "Impact()"
8224 from "ContinueMoving()": if an element moves to a tile directly below
8225 another element which was just falling on that tile (which was empty
8226 in the previous frame), the falling element above would just stop
8227 instead of smashing the element below (in previous version, the above
8228 element was just checked for "moving" instead of "falling", resulting
8229 in incorrect smashes caused by horizontal movement of the above
8230 element; also, the case of the player being the element to smash was
8231 simply not covered here... :-/ ) */
8233 CheckCollision[x][y] = 0;
8234 CheckImpact[x][y] = 0;
8238 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8240 if (MovDir[x][y] == MV_NONE)
8242 InitMovingField(x, y, MV_DOWN);
8243 started_moving = TRUE;
8246 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8248 if (WasJustFalling[x][y]) // prevent animation from being restarted
8249 MovDir[x][y] = MV_DOWN;
8251 InitMovingField(x, y, MV_DOWN);
8252 started_moving = TRUE;
8254 else if (element == EL_AMOEBA_DROP)
8256 Tile[x][y] = EL_AMOEBA_GROWING;
8257 Store[x][y] = EL_AMOEBA_WET;
8259 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8260 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8261 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8262 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8264 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8265 (IS_FREE(x - 1, y + 1) ||
8266 Tile[x - 1][y + 1] == EL_ACID));
8267 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8268 (IS_FREE(x + 1, y + 1) ||
8269 Tile[x + 1][y + 1] == EL_ACID));
8270 boolean can_fall_any = (can_fall_left || can_fall_right);
8271 boolean can_fall_both = (can_fall_left && can_fall_right);
8272 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8274 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8276 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8277 can_fall_right = FALSE;
8278 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8279 can_fall_left = FALSE;
8280 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8281 can_fall_right = FALSE;
8282 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8283 can_fall_left = FALSE;
8285 can_fall_any = (can_fall_left || can_fall_right);
8286 can_fall_both = FALSE;
8291 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8292 can_fall_right = FALSE; // slip down on left side
8294 can_fall_left = !(can_fall_right = RND(2));
8296 can_fall_both = FALSE;
8301 // if not determined otherwise, prefer left side for slipping down
8302 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8303 started_moving = TRUE;
8306 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8308 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8309 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8310 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8311 int belt_dir = game.belt_dir[belt_nr];
8313 if ((belt_dir == MV_LEFT && left_is_free) ||
8314 (belt_dir == MV_RIGHT && right_is_free))
8316 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8318 InitMovingField(x, y, belt_dir);
8319 started_moving = TRUE;
8321 Pushed[x][y] = TRUE;
8322 Pushed[nextx][y] = TRUE;
8324 GfxAction[x][y] = ACTION_DEFAULT;
8328 MovDir[x][y] = 0; // if element was moving, stop it
8333 // not "else if" because of elements that can fall and move (EL_SPRING)
8334 if (CAN_MOVE(element) && !started_moving)
8336 int move_pattern = element_info[element].move_pattern;
8339 Moving2Blocked(x, y, &newx, &newy);
8341 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8344 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8345 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8347 WasJustMoving[x][y] = 0;
8348 CheckCollision[x][y] = 0;
8350 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8352 if (Tile[x][y] != element) // element has changed
8356 if (!MovDelay[x][y]) // start new movement phase
8358 // all objects that can change their move direction after each step
8359 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8361 if (element != EL_YAMYAM &&
8362 element != EL_DARK_YAMYAM &&
8363 element != EL_PACMAN &&
8364 !(move_pattern & MV_ANY_DIRECTION) &&
8365 move_pattern != MV_TURNING_LEFT &&
8366 move_pattern != MV_TURNING_RIGHT &&
8367 move_pattern != MV_TURNING_LEFT_RIGHT &&
8368 move_pattern != MV_TURNING_RIGHT_LEFT &&
8369 move_pattern != MV_TURNING_RANDOM)
8373 if (MovDelay[x][y] && (element == EL_BUG ||
8374 element == EL_SPACESHIP ||
8375 element == EL_SP_SNIKSNAK ||
8376 element == EL_SP_ELECTRON ||
8377 element == EL_MOLE))
8378 TEST_DrawLevelField(x, y);
8382 if (MovDelay[x][y]) // wait some time before next movement
8386 if (element == EL_ROBOT ||
8387 element == EL_YAMYAM ||
8388 element == EL_DARK_YAMYAM)
8390 DrawLevelElementAnimationIfNeeded(x, y, element);
8391 PlayLevelSoundAction(x, y, ACTION_WAITING);
8393 else if (element == EL_SP_ELECTRON)
8394 DrawLevelElementAnimationIfNeeded(x, y, element);
8395 else if (element == EL_DRAGON)
8398 int dir = MovDir[x][y];
8399 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8400 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8401 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8402 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8403 dir == MV_UP ? IMG_FLAMES_1_UP :
8404 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8405 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8407 GfxAction[x][y] = ACTION_ATTACKING;
8409 if (IS_PLAYER(x, y))
8410 DrawPlayerField(x, y);
8412 TEST_DrawLevelField(x, y);
8414 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8416 for (i = 1; i <= 3; i++)
8418 int xx = x + i * dx;
8419 int yy = y + i * dy;
8420 int sx = SCREENX(xx);
8421 int sy = SCREENY(yy);
8422 int flame_graphic = graphic + (i - 1);
8424 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8429 int flamed = MovingOrBlocked2Element(xx, yy);
8431 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8434 RemoveMovingField(xx, yy);
8436 ChangeDelay[xx][yy] = 0;
8438 Tile[xx][yy] = EL_FLAMES;
8440 if (IN_SCR_FIELD(sx, sy))
8442 TEST_DrawLevelFieldCrumbled(xx, yy);
8443 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8448 if (Tile[xx][yy] == EL_FLAMES)
8449 Tile[xx][yy] = EL_EMPTY;
8450 TEST_DrawLevelField(xx, yy);
8455 if (MovDelay[x][y]) // element still has to wait some time
8457 PlayLevelSoundAction(x, y, ACTION_WAITING);
8463 // now make next step
8465 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8467 if (DONT_COLLIDE_WITH(element) &&
8468 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8469 !PLAYER_ENEMY_PROTECTED(newx, newy))
8471 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8476 else if (CAN_MOVE_INTO_ACID(element) &&
8477 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8478 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8479 (MovDir[x][y] == MV_DOWN ||
8480 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8482 SplashAcid(newx, newy);
8483 Store[x][y] = EL_ACID;
8485 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8487 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8488 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8489 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8490 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8493 TEST_DrawLevelField(x, y);
8495 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8496 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8497 DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0);
8499 game.friends_still_needed--;
8500 if (!game.friends_still_needed &&
8502 game.all_players_gone)
8507 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8509 if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING)
8510 TEST_DrawLevelField(newx, newy);
8512 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8514 else if (!IS_FREE(newx, newy))
8516 GfxAction[x][y] = ACTION_WAITING;
8518 if (IS_PLAYER(x, y))
8519 DrawPlayerField(x, y);
8521 TEST_DrawLevelField(x, y);
8526 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8528 if (IS_FOOD_PIG(Tile[newx][newy]))
8530 if (IS_MOVING(newx, newy))
8531 RemoveMovingField(newx, newy);
8534 Tile[newx][newy] = EL_EMPTY;
8535 TEST_DrawLevelField(newx, newy);
8538 PlayLevelSound(x, y, SND_PIG_DIGGING);
8540 else if (!IS_FREE(newx, newy))
8542 if (IS_PLAYER(x, y))
8543 DrawPlayerField(x, y);
8545 TEST_DrawLevelField(x, y);
8550 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8552 if (Store[x][y] != EL_EMPTY)
8554 boolean can_clone = FALSE;
8557 // check if element to clone is still there
8558 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8560 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8568 // cannot clone or target field not free anymore -- do not clone
8569 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8570 Store[x][y] = EL_EMPTY;
8573 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8575 if (IS_MV_DIAGONAL(MovDir[x][y]))
8577 int diagonal_move_dir = MovDir[x][y];
8578 int stored = Store[x][y];
8579 int change_delay = 8;
8582 // android is moving diagonally
8584 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8586 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8587 GfxElement[x][y] = EL_EMC_ANDROID;
8588 GfxAction[x][y] = ACTION_SHRINKING;
8589 GfxDir[x][y] = diagonal_move_dir;
8590 ChangeDelay[x][y] = change_delay;
8592 if (Store[x][y] == EL_EMPTY)
8593 Store[x][y] = GfxElementEmpty[x][y];
8595 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8598 DrawLevelGraphicAnimation(x, y, graphic);
8599 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8601 if (Tile[newx][newy] == EL_ACID)
8603 SplashAcid(newx, newy);
8608 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8610 Store[newx][newy] = EL_EMC_ANDROID;
8611 GfxElement[newx][newy] = EL_EMC_ANDROID;
8612 GfxAction[newx][newy] = ACTION_GROWING;
8613 GfxDir[newx][newy] = diagonal_move_dir;
8614 ChangeDelay[newx][newy] = change_delay;
8616 graphic = el_act_dir2img(GfxElement[newx][newy],
8617 GfxAction[newx][newy], GfxDir[newx][newy]);
8619 DrawLevelGraphicAnimation(newx, newy, graphic);
8620 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8626 Tile[newx][newy] = EL_EMPTY;
8627 TEST_DrawLevelField(newx, newy);
8629 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8632 else if (!IS_FREE(newx, newy))
8637 else if (IS_CUSTOM_ELEMENT(element) &&
8638 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8640 if (!DigFieldByCE(newx, newy, element))
8643 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8645 RunnerVisit[x][y] = FrameCounter;
8646 PlayerVisit[x][y] /= 8; // expire player visit path
8649 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8651 if (!IS_FREE(newx, newy))
8653 if (IS_PLAYER(x, y))
8654 DrawPlayerField(x, y);
8656 TEST_DrawLevelField(x, y);
8662 boolean wanna_flame = !RND(10);
8663 int dx = newx - x, dy = newy - y;
8664 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8665 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8666 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8667 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8668 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8669 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8672 IS_CLASSIC_ENEMY(element1) ||
8673 IS_CLASSIC_ENEMY(element2)) &&
8674 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8675 element1 != EL_FLAMES && element2 != EL_FLAMES)
8677 ResetGfxAnimation(x, y);
8678 GfxAction[x][y] = ACTION_ATTACKING;
8680 if (IS_PLAYER(x, y))
8681 DrawPlayerField(x, y);
8683 TEST_DrawLevelField(x, y);
8685 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8687 MovDelay[x][y] = 50;
8689 Tile[newx][newy] = EL_FLAMES;
8690 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8691 Tile[newx1][newy1] = EL_FLAMES;
8692 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8693 Tile[newx2][newy2] = EL_FLAMES;
8699 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8700 Tile[newx][newy] == EL_DIAMOND)
8702 if (IS_MOVING(newx, newy))
8703 RemoveMovingField(newx, newy);
8706 Tile[newx][newy] = EL_EMPTY;
8707 TEST_DrawLevelField(newx, newy);
8710 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8712 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8713 IS_FOOD_DARK_YAMYAM(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 (IS_MOVING(newx, newy))
8725 RemoveMovingField(newx, newy);
8729 Tile[newx][newy] = EL_EMPTY;
8730 TEST_DrawLevelField(newx, newy);
8733 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8735 else if ((element == EL_PACMAN || element == EL_MOLE)
8736 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8738 if (AmoebaNr[newx][newy])
8740 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8741 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8742 Tile[newx][newy] == EL_BD_AMOEBA)
8743 AmoebaCnt[AmoebaNr[newx][newy]]--;
8746 if (element == EL_MOLE)
8748 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8749 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8751 ResetGfxAnimation(x, y);
8752 GfxAction[x][y] = ACTION_DIGGING;
8753 TEST_DrawLevelField(x, y);
8755 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8757 return; // wait for shrinking amoeba
8759 else // element == EL_PACMAN
8761 Tile[newx][newy] = EL_EMPTY;
8762 TEST_DrawLevelField(newx, newy);
8763 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8766 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8767 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8768 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8770 // wait for shrinking amoeba to completely disappear
8773 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8775 // object was running against a wall
8779 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8780 DrawLevelElementAnimation(x, y, element);
8782 if (DONT_TOUCH(element))
8783 TestIfBadThingTouchesPlayer(x, y);
8788 InitMovingField(x, y, MovDir[x][y]);
8790 PlayLevelSoundAction(x, y, ACTION_MOVING);
8794 ContinueMoving(x, y);
8797 void ContinueMoving(int x, int y)
8799 int element = Tile[x][y];
8800 struct ElementInfo *ei = &element_info[element];
8801 int direction = MovDir[x][y];
8802 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8803 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8804 int newx = x + dx, newy = y + dy;
8805 int stored = Store[x][y];
8806 int stored_new = Store[newx][newy];
8807 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8808 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8809 boolean last_line = (newy == lev_fieldy - 1);
8810 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8812 if (pushed_by_player) // special case: moving object pushed by player
8814 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos));
8816 else if (use_step_delay) // special case: moving object has step delay
8818 if (!MovDelay[x][y])
8819 MovPos[x][y] += getElementMoveStepsize(x, y);
8824 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8828 TEST_DrawLevelField(x, y);
8830 return; // element is still waiting
8833 else // normal case: generically moving object
8835 MovPos[x][y] += getElementMoveStepsize(x, y);
8838 if (ABS(MovPos[x][y]) < TILEX)
8840 TEST_DrawLevelField(x, y);
8842 return; // element is still moving
8845 // element reached destination field
8847 Tile[x][y] = EL_EMPTY;
8848 Tile[newx][newy] = element;
8849 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8851 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8853 element = Tile[newx][newy] = EL_ACID;
8855 else if (element == EL_MOLE)
8857 Tile[x][y] = EL_SAND;
8859 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8861 else if (element == EL_QUICKSAND_FILLING)
8863 element = Tile[newx][newy] = get_next_element(element);
8864 Store[newx][newy] = Store[x][y];
8866 else if (element == EL_QUICKSAND_EMPTYING)
8868 Tile[x][y] = get_next_element(element);
8869 element = Tile[newx][newy] = Store[x][y];
8871 else if (element == EL_QUICKSAND_FAST_FILLING)
8873 element = Tile[newx][newy] = get_next_element(element);
8874 Store[newx][newy] = Store[x][y];
8876 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8878 Tile[x][y] = get_next_element(element);
8879 element = Tile[newx][newy] = Store[x][y];
8881 else if (element == EL_MAGIC_WALL_FILLING)
8883 element = Tile[newx][newy] = get_next_element(element);
8884 if (!game.magic_wall_active)
8885 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8886 Store[newx][newy] = Store[x][y];
8888 else if (element == EL_MAGIC_WALL_EMPTYING)
8890 Tile[x][y] = get_next_element(element);
8891 if (!game.magic_wall_active)
8892 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8893 element = Tile[newx][newy] = Store[x][y];
8895 InitField(newx, newy, FALSE);
8897 else if (element == EL_BD_MAGIC_WALL_FILLING)
8899 element = Tile[newx][newy] = get_next_element(element);
8900 if (!game.magic_wall_active)
8901 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8902 Store[newx][newy] = Store[x][y];
8904 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8906 Tile[x][y] = get_next_element(element);
8907 if (!game.magic_wall_active)
8908 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8909 element = Tile[newx][newy] = Store[x][y];
8911 InitField(newx, newy, FALSE);
8913 else if (element == EL_DC_MAGIC_WALL_FILLING)
8915 element = Tile[newx][newy] = get_next_element(element);
8916 if (!game.magic_wall_active)
8917 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8918 Store[newx][newy] = Store[x][y];
8920 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8922 Tile[x][y] = get_next_element(element);
8923 if (!game.magic_wall_active)
8924 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8925 element = Tile[newx][newy] = Store[x][y];
8927 InitField(newx, newy, FALSE);
8929 else if (element == EL_AMOEBA_DROPPING)
8931 Tile[x][y] = get_next_element(element);
8932 element = Tile[newx][newy] = Store[x][y];
8934 else if (element == EL_SOKOBAN_OBJECT)
8937 Tile[x][y] = Back[x][y];
8939 if (Back[newx][newy])
8940 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8942 Back[x][y] = Back[newx][newy] = 0;
8945 Store[x][y] = EL_EMPTY;
8950 MovDelay[newx][newy] = 0;
8952 if (CAN_CHANGE_OR_HAS_ACTION(element))
8954 // copy element change control values to new field
8955 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8956 ChangePage[newx][newy] = ChangePage[x][y];
8957 ChangeCount[newx][newy] = ChangeCount[x][y];
8958 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8961 CustomValue[newx][newy] = CustomValue[x][y];
8963 ChangeDelay[x][y] = 0;
8964 ChangePage[x][y] = -1;
8965 ChangeCount[x][y] = 0;
8966 ChangeEvent[x][y] = -1;
8968 CustomValue[x][y] = 0;
8970 // copy animation control values to new field
8971 GfxFrame[newx][newy] = GfxFrame[x][y];
8972 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8973 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8974 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8976 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8978 // some elements can leave other elements behind after moving
8979 if (ei->move_leave_element != EL_EMPTY &&
8980 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8981 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8983 int move_leave_element = ei->move_leave_element;
8985 // this makes it possible to leave the removed element again
8986 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8987 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8989 Tile[x][y] = move_leave_element;
8991 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8992 MovDir[x][y] = direction;
8994 InitField(x, y, FALSE);
8996 if (GFX_CRUMBLED(Tile[x][y]))
8997 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8999 if (IS_PLAYER_ELEMENT(move_leave_element))
9000 RelocatePlayer(x, y, move_leave_element);
9003 // do this after checking for left-behind element
9004 ResetGfxAnimation(x, y); // reset animation values for old field
9006 if (!CAN_MOVE(element) ||
9007 (CAN_FALL(element) && direction == MV_DOWN &&
9008 (element == EL_SPRING ||
9009 element_info[element].move_pattern == MV_WHEN_PUSHED ||
9010 element_info[element].move_pattern == MV_WHEN_DROPPED)))
9011 GfxDir[x][y] = MovDir[newx][newy] = 0;
9013 TEST_DrawLevelField(x, y);
9014 TEST_DrawLevelField(newx, newy);
9016 Stop[newx][newy] = TRUE; // ignore this element until the next frame
9018 // prevent pushed element from moving on in pushed direction
9019 if (pushed_by_player && CAN_MOVE(element) &&
9020 element_info[element].move_pattern & MV_ANY_DIRECTION &&
9021 !(element_info[element].move_pattern & direction))
9022 TurnRound(newx, newy);
9024 // prevent elements on conveyor belt from moving on in last direction
9025 if (pushed_by_conveyor && CAN_FALL(element) &&
9026 direction & MV_HORIZONTAL)
9027 MovDir[newx][newy] = 0;
9029 if (!pushed_by_player)
9031 int nextx = newx + dx, nexty = newy + dy;
9032 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
9034 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
9036 if (CAN_FALL(element) && direction == MV_DOWN)
9037 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
9039 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
9040 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
9042 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
9043 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
9046 if (DONT_TOUCH(element)) // object may be nasty to player or others
9048 TestIfBadThingTouchesPlayer(newx, newy);
9049 TestIfBadThingTouchesFriend(newx, newy);
9051 if (!IS_CUSTOM_ELEMENT(element))
9052 TestIfBadThingTouchesOtherBadThing(newx, newy);
9054 else if (element == EL_PENGUIN)
9055 TestIfFriendTouchesBadThing(newx, newy);
9057 if (DONT_GET_HIT_BY(element))
9059 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
9062 // give the player one last chance (one more frame) to move away
9063 if (CAN_FALL(element) && direction == MV_DOWN &&
9064 (last_line || (!IS_FREE(x, newy + 1) &&
9065 (!IS_PLAYER(x, newy + 1) ||
9066 game.engine_version < VERSION_IDENT(3,1,1,0)))))
9069 if (pushed_by_player && !game.use_change_when_pushing_bug)
9071 int push_side = MV_DIR_OPPOSITE(direction);
9072 struct PlayerInfo *player = PLAYERINFO(x, y);
9074 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
9075 player->index_bit, push_side);
9076 CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X,
9077 player->index_bit, push_side);
9080 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
9081 MovDelay[newx][newy] = 1;
9083 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
9085 TestIfElementTouchesCustomElement(x, y); // empty or new element
9086 TestIfElementHitsCustomElement(newx, newy, direction);
9087 TestIfPlayerTouchesCustomElement(newx, newy);
9088 TestIfElementTouchesCustomElement(newx, newy);
9090 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
9091 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
9092 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
9093 MV_DIR_OPPOSITE(direction));
9096 int AmoebaNeighbourNr(int ax, int ay)
9099 int element = Tile[ax][ay];
9101 struct XY *xy = xy_topdown;
9103 for (i = 0; i < NUM_DIRECTIONS; i++)
9105 int x = ax + xy[i].x;
9106 int y = ay + xy[i].y;
9108 if (!IN_LEV_FIELD(x, y))
9111 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
9112 group_nr = AmoebaNr[x][y];
9118 static void AmoebaMerge(int ax, int ay)
9120 int i, x, y, xx, yy;
9121 int new_group_nr = AmoebaNr[ax][ay];
9122 struct XY *xy = xy_topdown;
9124 if (new_group_nr == 0)
9127 for (i = 0; i < NUM_DIRECTIONS; i++)
9132 if (!IN_LEV_FIELD(x, y))
9135 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9136 Tile[x][y] == EL_BD_AMOEBA ||
9137 Tile[x][y] == EL_AMOEBA_DEAD) &&
9138 AmoebaNr[x][y] != new_group_nr)
9140 int old_group_nr = AmoebaNr[x][y];
9142 if (old_group_nr == 0)
9145 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9146 AmoebaCnt[old_group_nr] = 0;
9147 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9148 AmoebaCnt2[old_group_nr] = 0;
9150 SCAN_PLAYFIELD(xx, yy)
9152 if (AmoebaNr[xx][yy] == old_group_nr)
9153 AmoebaNr[xx][yy] = new_group_nr;
9159 void AmoebaToDiamond(int ax, int ay)
9163 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9165 int group_nr = AmoebaNr[ax][ay];
9170 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9171 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9177 SCAN_PLAYFIELD(x, y)
9179 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9182 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9186 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9187 SND_AMOEBA_TURNING_TO_GEM :
9188 SND_AMOEBA_TURNING_TO_ROCK));
9193 struct XY *xy = xy_topdown;
9195 for (i = 0; i < NUM_DIRECTIONS; i++)
9200 if (!IN_LEV_FIELD(x, y))
9203 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9205 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9206 SND_AMOEBA_TURNING_TO_GEM :
9207 SND_AMOEBA_TURNING_TO_ROCK));
9214 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9217 int group_nr = AmoebaNr[ax][ay];
9218 boolean done = FALSE;
9223 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9224 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9230 SCAN_PLAYFIELD(x, y)
9232 if (AmoebaNr[x][y] == group_nr &&
9233 (Tile[x][y] == EL_AMOEBA_DEAD ||
9234 Tile[x][y] == EL_BD_AMOEBA ||
9235 Tile[x][y] == EL_AMOEBA_GROWING))
9238 Tile[x][y] = new_element;
9239 InitField(x, y, FALSE);
9240 TEST_DrawLevelField(x, y);
9246 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9247 SND_BD_AMOEBA_TURNING_TO_ROCK :
9248 SND_BD_AMOEBA_TURNING_TO_GEM));
9251 static void AmoebaGrowing(int x, int y)
9253 static DelayCounter sound_delay = { 0 };
9255 if (!MovDelay[x][y]) // start new growing cycle
9259 if (DelayReached(&sound_delay))
9261 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9262 sound_delay.value = 30;
9266 if (MovDelay[x][y]) // wait some time before growing bigger
9269 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9271 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9272 6 - MovDelay[x][y]);
9274 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9277 if (!MovDelay[x][y])
9279 Tile[x][y] = Store[x][y];
9281 TEST_DrawLevelField(x, y);
9286 static void AmoebaShrinking(int x, int y)
9288 static DelayCounter sound_delay = { 0 };
9290 if (!MovDelay[x][y]) // start new shrinking cycle
9294 if (DelayReached(&sound_delay))
9295 sound_delay.value = 30;
9298 if (MovDelay[x][y]) // wait some time before shrinking
9301 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9303 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9304 6 - MovDelay[x][y]);
9306 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9309 if (!MovDelay[x][y])
9311 Tile[x][y] = EL_EMPTY;
9312 TEST_DrawLevelField(x, y);
9314 // don't let mole enter this field in this cycle;
9315 // (give priority to objects falling to this field from above)
9321 static void AmoebaReproduce(int ax, int ay)
9324 int element = Tile[ax][ay];
9325 int graphic = el2img(element);
9326 int newax = ax, neway = ay;
9327 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9328 struct XY *xy = xy_topdown;
9330 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9332 Tile[ax][ay] = EL_AMOEBA_DEAD;
9333 TEST_DrawLevelField(ax, ay);
9337 if (IS_ANIMATED(graphic))
9338 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9340 if (!MovDelay[ax][ay]) // start making new amoeba field
9341 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9343 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9346 if (MovDelay[ax][ay])
9350 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9353 int x = ax + xy[start].x;
9354 int y = ay + xy[start].y;
9356 if (!IN_LEV_FIELD(x, y))
9359 if (IS_FREE(x, y) ||
9360 CAN_GROW_INTO(Tile[x][y]) ||
9361 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9362 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9368 if (newax == ax && neway == ay)
9371 else // normal or "filled" (BD style) amoeba
9374 boolean waiting_for_player = FALSE;
9376 for (i = 0; i < NUM_DIRECTIONS; i++)
9378 int j = (start + i) % 4;
9379 int x = ax + xy[j].x;
9380 int y = ay + xy[j].y;
9382 if (!IN_LEV_FIELD(x, y))
9385 if (IS_FREE(x, y) ||
9386 CAN_GROW_INTO(Tile[x][y]) ||
9387 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9388 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9394 else if (IS_PLAYER(x, y))
9395 waiting_for_player = TRUE;
9398 if (newax == ax && neway == ay) // amoeba cannot grow
9400 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9402 Tile[ax][ay] = EL_AMOEBA_DEAD;
9403 TEST_DrawLevelField(ax, ay);
9404 AmoebaCnt[AmoebaNr[ax][ay]]--;
9406 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9408 if (element == EL_AMOEBA_FULL)
9409 AmoebaToDiamond(ax, ay);
9410 else if (element == EL_BD_AMOEBA)
9411 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9416 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9418 // amoeba gets larger by growing in some direction
9420 int new_group_nr = AmoebaNr[ax][ay];
9423 if (new_group_nr == 0)
9425 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9427 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9433 AmoebaNr[newax][neway] = new_group_nr;
9434 AmoebaCnt[new_group_nr]++;
9435 AmoebaCnt2[new_group_nr]++;
9437 // if amoeba touches other amoeba(s) after growing, unify them
9438 AmoebaMerge(newax, neway);
9440 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9442 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9448 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9449 (neway == lev_fieldy - 1 && newax != ax))
9451 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9452 Store[newax][neway] = element;
9454 else if (neway == ay || element == EL_EMC_DRIPPER)
9456 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9458 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9462 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9463 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9464 Store[ax][ay] = EL_AMOEBA_DROP;
9465 ContinueMoving(ax, ay);
9469 TEST_DrawLevelField(newax, neway);
9472 static void Life(int ax, int ay)
9476 int element = Tile[ax][ay];
9477 int graphic = el2img(element);
9478 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9480 boolean changed = FALSE;
9482 if (IS_ANIMATED(graphic))
9483 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9488 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9489 MovDelay[ax][ay] = life_time;
9491 if (MovDelay[ax][ay]) // wait some time before next cycle
9494 if (MovDelay[ax][ay])
9498 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9500 int xx = ax + x1, yy = ay + y1;
9501 int old_element = Tile[xx][yy];
9502 int num_neighbours = 0;
9504 if (!IN_LEV_FIELD(xx, yy))
9507 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9509 int x = xx + x2, y = yy + y2;
9511 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9514 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9515 boolean is_neighbour = FALSE;
9517 if (level.use_life_bugs)
9519 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9520 (IS_FREE(x, y) && Stop[x][y]));
9523 (Last[x][y] == element || is_player_cell);
9529 boolean is_free = FALSE;
9531 if (level.use_life_bugs)
9532 is_free = (IS_FREE(xx, yy));
9534 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9536 if (xx == ax && yy == ay) // field in the middle
9538 if (num_neighbours < life_parameter[0] ||
9539 num_neighbours > life_parameter[1])
9541 Tile[xx][yy] = EL_EMPTY;
9542 if (Tile[xx][yy] != old_element)
9543 TEST_DrawLevelField(xx, yy);
9544 Stop[xx][yy] = TRUE;
9548 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9549 { // free border field
9550 if (num_neighbours >= life_parameter[2] &&
9551 num_neighbours <= life_parameter[3])
9553 Tile[xx][yy] = element;
9554 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9555 if (Tile[xx][yy] != old_element)
9556 TEST_DrawLevelField(xx, yy);
9557 Stop[xx][yy] = TRUE;
9564 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9565 SND_GAME_OF_LIFE_GROWING);
9568 static void InitRobotWheel(int x, int y)
9570 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9573 static void RunRobotWheel(int x, int y)
9575 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9578 static void StopRobotWheel(int x, int y)
9580 if (game.robot_wheel_x == x &&
9581 game.robot_wheel_y == y)
9583 game.robot_wheel_x = -1;
9584 game.robot_wheel_y = -1;
9585 game.robot_wheel_active = FALSE;
9589 static void InitTimegateWheel(int x, int y)
9591 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9594 static void RunTimegateWheel(int x, int y)
9596 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9599 static void InitMagicBallDelay(int x, int y)
9601 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9604 static void ActivateMagicBall(int bx, int by)
9608 if (level.ball_random)
9610 int pos_border = RND(8); // select one of the eight border elements
9611 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9612 int xx = pos_content % 3;
9613 int yy = pos_content / 3;
9618 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9619 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9623 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9625 int xx = x - bx + 1;
9626 int yy = y - by + 1;
9628 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9629 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9633 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9636 static void CheckExit(int x, int y)
9638 if (game.gems_still_needed > 0 ||
9639 game.sokoban_fields_still_needed > 0 ||
9640 game.sokoban_objects_still_needed > 0 ||
9641 game.lights_still_needed > 0)
9643 int element = Tile[x][y];
9644 int graphic = el2img(element);
9646 if (IS_ANIMATED(graphic))
9647 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9652 // do not re-open exit door closed after last player
9653 if (game.all_players_gone)
9656 Tile[x][y] = EL_EXIT_OPENING;
9658 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9661 static void CheckExitEM(int x, int y)
9663 if (game.gems_still_needed > 0 ||
9664 game.sokoban_fields_still_needed > 0 ||
9665 game.sokoban_objects_still_needed > 0 ||
9666 game.lights_still_needed > 0)
9668 int element = Tile[x][y];
9669 int graphic = el2img(element);
9671 if (IS_ANIMATED(graphic))
9672 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9677 // do not re-open exit door closed after last player
9678 if (game.all_players_gone)
9681 Tile[x][y] = EL_EM_EXIT_OPENING;
9683 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9686 static void CheckExitSteel(int x, int y)
9688 if (game.gems_still_needed > 0 ||
9689 game.sokoban_fields_still_needed > 0 ||
9690 game.sokoban_objects_still_needed > 0 ||
9691 game.lights_still_needed > 0)
9693 int element = Tile[x][y];
9694 int graphic = el2img(element);
9696 if (IS_ANIMATED(graphic))
9697 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9702 // do not re-open exit door closed after last player
9703 if (game.all_players_gone)
9706 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9708 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9711 static void CheckExitSteelEM(int x, int y)
9713 if (game.gems_still_needed > 0 ||
9714 game.sokoban_fields_still_needed > 0 ||
9715 game.sokoban_objects_still_needed > 0 ||
9716 game.lights_still_needed > 0)
9718 int element = Tile[x][y];
9719 int graphic = el2img(element);
9721 if (IS_ANIMATED(graphic))
9722 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9727 // do not re-open exit door closed after last player
9728 if (game.all_players_gone)
9731 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9733 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9736 static void CheckExitSP(int x, int y)
9738 if (game.gems_still_needed > 0)
9740 int element = Tile[x][y];
9741 int graphic = el2img(element);
9743 if (IS_ANIMATED(graphic))
9744 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9749 // do not re-open exit door closed after last player
9750 if (game.all_players_gone)
9753 Tile[x][y] = EL_SP_EXIT_OPENING;
9755 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9758 static void CloseAllOpenTimegates(void)
9762 SCAN_PLAYFIELD(x, y)
9764 int element = Tile[x][y];
9766 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9768 Tile[x][y] = EL_TIMEGATE_CLOSING;
9770 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9775 static void DrawTwinkleOnField(int x, int y)
9777 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9780 if (Tile[x][y] == EL_BD_DIAMOND)
9783 if (MovDelay[x][y] == 0) // next animation frame
9784 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9786 if (MovDelay[x][y] != 0) // wait some time before next frame
9790 DrawLevelElementAnimation(x, y, Tile[x][y]);
9792 if (MovDelay[x][y] != 0)
9794 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9795 10 - MovDelay[x][y]);
9797 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9802 static void WallGrowing(int x, int y)
9806 if (!MovDelay[x][y]) // next animation frame
9807 MovDelay[x][y] = 3 * delay;
9809 if (MovDelay[x][y]) // wait some time before next frame
9813 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9815 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9816 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9818 DrawLevelGraphic(x, y, graphic, frame);
9821 if (!MovDelay[x][y])
9823 if (MovDir[x][y] == MV_LEFT)
9825 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9826 TEST_DrawLevelField(x - 1, y);
9828 else if (MovDir[x][y] == MV_RIGHT)
9830 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9831 TEST_DrawLevelField(x + 1, y);
9833 else if (MovDir[x][y] == MV_UP)
9835 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9836 TEST_DrawLevelField(x, y - 1);
9840 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9841 TEST_DrawLevelField(x, y + 1);
9844 Tile[x][y] = Store[x][y];
9846 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9847 TEST_DrawLevelField(x, y);
9852 static void CheckWallGrowing(int ax, int ay)
9854 int element = Tile[ax][ay];
9855 int graphic = el2img(element);
9856 boolean free_top = FALSE;
9857 boolean free_bottom = FALSE;
9858 boolean free_left = FALSE;
9859 boolean free_right = FALSE;
9860 boolean stop_top = FALSE;
9861 boolean stop_bottom = FALSE;
9862 boolean stop_left = FALSE;
9863 boolean stop_right = FALSE;
9864 boolean new_wall = FALSE;
9866 boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9867 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9868 element == EL_EXPANDABLE_STEELWALL_ANY);
9870 boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9871 element == EL_EXPANDABLE_WALL_ANY ||
9872 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9873 element == EL_EXPANDABLE_STEELWALL_ANY);
9875 boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9876 element == EL_EXPANDABLE_WALL_ANY ||
9877 element == EL_EXPANDABLE_WALL ||
9878 element == EL_BD_EXPANDABLE_WALL ||
9879 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9880 element == EL_EXPANDABLE_STEELWALL_ANY);
9882 boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9883 element == EL_EXPANDABLE_STEELWALL_VERTICAL);
9885 boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9886 element == EL_EXPANDABLE_WALL ||
9887 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL);
9889 int wall_growing = (is_steelwall ?
9890 EL_EXPANDABLE_STEELWALL_GROWING :
9891 EL_EXPANDABLE_WALL_GROWING);
9893 int gfx_wall_growing_up = (is_steelwall ?
9894 IMG_EXPANDABLE_STEELWALL_GROWING_UP :
9895 IMG_EXPANDABLE_WALL_GROWING_UP);
9896 int gfx_wall_growing_down = (is_steelwall ?
9897 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN :
9898 IMG_EXPANDABLE_WALL_GROWING_DOWN);
9899 int gfx_wall_growing_left = (is_steelwall ?
9900 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT :
9901 IMG_EXPANDABLE_WALL_GROWING_LEFT);
9902 int gfx_wall_growing_right = (is_steelwall ?
9903 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT :
9904 IMG_EXPANDABLE_WALL_GROWING_RIGHT);
9906 if (IS_ANIMATED(graphic))
9907 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9909 if (!MovDelay[ax][ay]) // start building new wall
9910 MovDelay[ax][ay] = 6;
9912 if (MovDelay[ax][ay]) // wait some time before building new wall
9915 if (MovDelay[ax][ay])
9919 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9921 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9923 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9925 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9932 Tile[ax][ay - 1] = wall_growing;
9933 Store[ax][ay - 1] = element;
9934 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9936 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9937 DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0);
9944 Tile[ax][ay + 1] = wall_growing;
9945 Store[ax][ay + 1] = element;
9946 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9948 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9949 DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0);
9955 if (grow_horizontal)
9959 Tile[ax - 1][ay] = wall_growing;
9960 Store[ax - 1][ay] = element;
9961 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9963 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9964 DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0);
9971 Tile[ax + 1][ay] = wall_growing;
9972 Store[ax + 1][ay] = element;
9973 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
9975 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
9976 DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0);
9982 if (element == EL_EXPANDABLE_WALL && (free_left || free_right))
9983 TEST_DrawLevelField(ax, ay);
9985 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
9987 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
9989 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
9991 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
9994 if (((stop_top && stop_bottom) || stop_horizontal) &&
9995 ((stop_left && stop_right) || stop_vertical))
9996 Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL);
9999 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
10002 static void CheckForDragon(int x, int y)
10005 boolean dragon_found = FALSE;
10006 struct XY *xy = xy_topdown;
10008 for (i = 0; i < NUM_DIRECTIONS; i++)
10010 for (j = 0; j < 4; j++)
10012 int xx = x + j * xy[i].x;
10013 int yy = y + j * xy[i].y;
10015 if (IN_LEV_FIELD(xx, yy) &&
10016 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
10018 if (Tile[xx][yy] == EL_DRAGON)
10019 dragon_found = TRUE;
10028 for (i = 0; i < NUM_DIRECTIONS; i++)
10030 for (j = 0; j < 3; j++)
10032 int xx = x + j * xy[i].x;
10033 int yy = y + j * xy[i].y;
10035 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
10037 Tile[xx][yy] = EL_EMPTY;
10038 TEST_DrawLevelField(xx, yy);
10047 static void InitBuggyBase(int x, int y)
10049 int element = Tile[x][y];
10050 int activating_delay = FRAMES_PER_SECOND / 4;
10052 ChangeDelay[x][y] =
10053 (element == EL_SP_BUGGY_BASE ?
10054 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10055 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10057 element == EL_SP_BUGGY_BASE_ACTIVE ?
10058 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10061 static void WarnBuggyBase(int x, int y)
10064 struct XY *xy = xy_topdown;
10066 for (i = 0; i < NUM_DIRECTIONS; i++)
10068 int xx = x + xy[i].x;
10069 int yy = y + xy[i].y;
10071 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10073 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10080 static void InitTrap(int x, int y)
10082 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10085 static void ActivateTrap(int x, int y)
10087 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10090 static void ChangeActiveTrap(int x, int y)
10092 int graphic = IMG_TRAP_ACTIVE;
10094 // if new animation frame was drawn, correct crumbled sand border
10095 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10096 TEST_DrawLevelFieldCrumbled(x, y);
10099 static int getSpecialActionElement(int element, int number, int base_element)
10101 return (element != EL_EMPTY ? element :
10102 number != -1 ? base_element + number - 1 :
10106 static int getModifiedActionNumber(int value_old, int operator, int operand,
10107 int value_min, int value_max)
10109 int value_new = (operator == CA_MODE_SET ? operand :
10110 operator == CA_MODE_ADD ? value_old + operand :
10111 operator == CA_MODE_SUBTRACT ? value_old - operand :
10112 operator == CA_MODE_MULTIPLY ? value_old * operand :
10113 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10114 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10117 return (value_new < value_min ? value_min :
10118 value_new > value_max ? value_max :
10122 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10124 struct ElementInfo *ei = &element_info[element];
10125 struct ElementChangeInfo *change = &ei->change_page[page];
10126 int target_element = change->target_element;
10127 int action_type = change->action_type;
10128 int action_mode = change->action_mode;
10129 int action_arg = change->action_arg;
10130 int action_element = change->action_element;
10133 if (!change->has_action)
10136 // ---------- determine action paramater values -----------------------------
10138 int level_time_value =
10139 (level.time > 0 ? TimeLeft :
10142 int action_arg_element_raw =
10143 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10144 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10145 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10146 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10147 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10148 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10149 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10151 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10153 int action_arg_direction =
10154 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10155 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10156 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10157 change->actual_trigger_side :
10158 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10159 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10162 int action_arg_number_min =
10163 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10166 int action_arg_number_max =
10167 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10168 action_type == CA_SET_LEVEL_GEMS ? 999 :
10169 action_type == CA_SET_LEVEL_TIME ? 9999 :
10170 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10171 action_type == CA_SET_CE_VALUE ? 9999 :
10172 action_type == CA_SET_CE_SCORE ? 9999 :
10175 int action_arg_number_reset =
10176 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10177 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10178 action_type == CA_SET_LEVEL_TIME ? level.time :
10179 action_type == CA_SET_LEVEL_SCORE ? 0 :
10180 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10181 action_type == CA_SET_CE_SCORE ? 0 :
10184 int action_arg_number =
10185 (action_arg <= CA_ARG_MAX ? action_arg :
10186 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10187 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10188 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10189 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10190 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10191 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10192 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10193 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10194 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10195 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10196 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10197 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10198 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10199 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10200 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10201 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10202 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10203 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10204 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10205 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10206 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10209 int action_arg_number_old =
10210 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10211 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10212 action_type == CA_SET_LEVEL_SCORE ? game.score :
10213 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10214 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10217 int action_arg_number_new =
10218 getModifiedActionNumber(action_arg_number_old,
10219 action_mode, action_arg_number,
10220 action_arg_number_min, action_arg_number_max);
10222 int trigger_player_bits =
10223 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10224 change->actual_trigger_player_bits : change->trigger_player);
10226 int action_arg_player_bits =
10227 (action_arg >= CA_ARG_PLAYER_1 &&
10228 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10229 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10230 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10233 // ---------- execute action -----------------------------------------------
10235 switch (action_type)
10242 // ---------- level actions ----------------------------------------------
10244 case CA_RESTART_LEVEL:
10246 game.restart_level = TRUE;
10251 case CA_SHOW_ENVELOPE:
10253 int element = getSpecialActionElement(action_arg_element,
10254 action_arg_number, EL_ENVELOPE_1);
10256 if (IS_ENVELOPE(element))
10257 local_player->show_envelope = element;
10262 case CA_SET_LEVEL_TIME:
10264 if (level.time > 0) // only modify limited time value
10266 TimeLeft = action_arg_number_new;
10268 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10270 DisplayGameControlValues();
10272 if (!TimeLeft && game.time_limit)
10273 for (i = 0; i < MAX_PLAYERS; i++)
10274 KillPlayer(&stored_player[i]);
10280 case CA_SET_LEVEL_SCORE:
10282 game.score = action_arg_number_new;
10284 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10286 DisplayGameControlValues();
10291 case CA_SET_LEVEL_GEMS:
10293 game.gems_still_needed = action_arg_number_new;
10295 game.snapshot.collected_item = TRUE;
10297 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10299 DisplayGameControlValues();
10304 case CA_SET_LEVEL_WIND:
10306 game.wind_direction = action_arg_direction;
10311 case CA_SET_LEVEL_RANDOM_SEED:
10313 // ensure that setting a new random seed while playing is predictable
10314 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10319 // ---------- player actions ---------------------------------------------
10321 case CA_MOVE_PLAYER:
10322 case CA_MOVE_PLAYER_NEW:
10324 // automatically move to the next field in specified direction
10325 for (i = 0; i < MAX_PLAYERS; i++)
10326 if (trigger_player_bits & (1 << i))
10327 if (action_type == CA_MOVE_PLAYER ||
10328 stored_player[i].MovPos == 0)
10329 stored_player[i].programmed_action = action_arg_direction;
10334 case CA_EXIT_PLAYER:
10336 for (i = 0; i < MAX_PLAYERS; i++)
10337 if (action_arg_player_bits & (1 << i))
10338 ExitPlayer(&stored_player[i]);
10340 if (game.players_still_needed == 0)
10346 case CA_KILL_PLAYER:
10348 for (i = 0; i < MAX_PLAYERS; i++)
10349 if (action_arg_player_bits & (1 << i))
10350 KillPlayer(&stored_player[i]);
10355 case CA_SET_PLAYER_KEYS:
10357 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10358 int element = getSpecialActionElement(action_arg_element,
10359 action_arg_number, EL_KEY_1);
10361 if (IS_KEY(element))
10363 for (i = 0; i < MAX_PLAYERS; i++)
10365 if (trigger_player_bits & (1 << i))
10367 stored_player[i].key[KEY_NR(element)] = key_state;
10369 DrawGameDoorValues();
10377 case CA_SET_PLAYER_SPEED:
10379 for (i = 0; i < MAX_PLAYERS; i++)
10381 if (trigger_player_bits & (1 << i))
10383 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10385 if (action_arg == CA_ARG_SPEED_FASTER &&
10386 stored_player[i].cannot_move)
10388 action_arg_number = STEPSIZE_VERY_SLOW;
10390 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10391 action_arg == CA_ARG_SPEED_FASTER)
10393 action_arg_number = 2;
10394 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10397 else if (action_arg == CA_ARG_NUMBER_RESET)
10399 action_arg_number = level.initial_player_stepsize[i];
10403 getModifiedActionNumber(move_stepsize,
10406 action_arg_number_min,
10407 action_arg_number_max);
10409 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10416 case CA_SET_PLAYER_SHIELD:
10418 for (i = 0; i < MAX_PLAYERS; i++)
10420 if (trigger_player_bits & (1 << i))
10422 if (action_arg == CA_ARG_SHIELD_OFF)
10424 stored_player[i].shield_normal_time_left = 0;
10425 stored_player[i].shield_deadly_time_left = 0;
10427 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10429 stored_player[i].shield_normal_time_left = 999999;
10431 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10433 stored_player[i].shield_normal_time_left = 999999;
10434 stored_player[i].shield_deadly_time_left = 999999;
10442 case CA_SET_PLAYER_GRAVITY:
10444 for (i = 0; i < MAX_PLAYERS; i++)
10446 if (trigger_player_bits & (1 << i))
10448 stored_player[i].gravity =
10449 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10450 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10451 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10452 stored_player[i].gravity);
10459 case CA_SET_PLAYER_ARTWORK:
10461 for (i = 0; i < MAX_PLAYERS; i++)
10463 if (trigger_player_bits & (1 << i))
10465 int artwork_element = action_arg_element;
10467 if (action_arg == CA_ARG_ELEMENT_RESET)
10469 (level.use_artwork_element[i] ? level.artwork_element[i] :
10470 stored_player[i].element_nr);
10472 if (stored_player[i].artwork_element != artwork_element)
10473 stored_player[i].Frame = 0;
10475 stored_player[i].artwork_element = artwork_element;
10477 SetPlayerWaiting(&stored_player[i], FALSE);
10479 // set number of special actions for bored and sleeping animation
10480 stored_player[i].num_special_action_bored =
10481 get_num_special_action(artwork_element,
10482 ACTION_BORING_1, ACTION_BORING_LAST);
10483 stored_player[i].num_special_action_sleeping =
10484 get_num_special_action(artwork_element,
10485 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10492 case CA_SET_PLAYER_INVENTORY:
10494 for (i = 0; i < MAX_PLAYERS; i++)
10496 struct PlayerInfo *player = &stored_player[i];
10499 if (trigger_player_bits & (1 << i))
10501 int inventory_element = action_arg_element;
10503 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10504 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10505 action_arg == CA_ARG_ELEMENT_ACTION)
10507 int element = inventory_element;
10508 int collect_count = element_info[element].collect_count_initial;
10510 if (!IS_CUSTOM_ELEMENT(element))
10513 if (collect_count == 0)
10514 player->inventory_infinite_element = element;
10516 for (k = 0; k < collect_count; k++)
10517 if (player->inventory_size < MAX_INVENTORY_SIZE)
10518 player->inventory_element[player->inventory_size++] =
10521 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10522 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10523 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10525 if (player->inventory_infinite_element != EL_UNDEFINED &&
10526 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10527 action_arg_element_raw))
10528 player->inventory_infinite_element = EL_UNDEFINED;
10530 for (k = 0, j = 0; j < player->inventory_size; j++)
10532 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10533 action_arg_element_raw))
10534 player->inventory_element[k++] = player->inventory_element[j];
10537 player->inventory_size = k;
10539 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10541 if (player->inventory_size > 0)
10543 for (j = 0; j < player->inventory_size - 1; j++)
10544 player->inventory_element[j] = player->inventory_element[j + 1];
10546 player->inventory_size--;
10549 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10551 if (player->inventory_size > 0)
10552 player->inventory_size--;
10554 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10556 player->inventory_infinite_element = EL_UNDEFINED;
10557 player->inventory_size = 0;
10559 else if (action_arg == CA_ARG_INVENTORY_RESET)
10561 player->inventory_infinite_element = EL_UNDEFINED;
10562 player->inventory_size = 0;
10564 if (level.use_initial_inventory[i])
10566 for (j = 0; j < level.initial_inventory_size[i]; j++)
10568 int element = level.initial_inventory_content[i][j];
10569 int collect_count = element_info[element].collect_count_initial;
10571 if (!IS_CUSTOM_ELEMENT(element))
10574 if (collect_count == 0)
10575 player->inventory_infinite_element = element;
10577 for (k = 0; k < collect_count; k++)
10578 if (player->inventory_size < MAX_INVENTORY_SIZE)
10579 player->inventory_element[player->inventory_size++] =
10590 // ---------- CE actions -------------------------------------------------
10592 case CA_SET_CE_VALUE:
10594 int last_ce_value = CustomValue[x][y];
10596 CustomValue[x][y] = action_arg_number_new;
10598 if (CustomValue[x][y] != last_ce_value)
10600 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10601 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10603 if (CustomValue[x][y] == 0)
10605 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10606 ChangeCount[x][y] = 0; // allow at least one more change
10608 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10609 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10616 case CA_SET_CE_SCORE:
10618 int last_ce_score = ei->collect_score;
10620 ei->collect_score = action_arg_number_new;
10622 if (ei->collect_score != last_ce_score)
10624 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10625 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10627 if (ei->collect_score == 0)
10631 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10632 ChangeCount[x][y] = 0; // allow at least one more change
10634 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10635 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10638 This is a very special case that seems to be a mixture between
10639 CheckElementChange() and CheckTriggeredElementChange(): while
10640 the first one only affects single elements that are triggered
10641 directly, the second one affects multiple elements in the playfield
10642 that are triggered indirectly by another element. This is a third
10643 case: Changing the CE score always affects multiple identical CEs,
10644 so every affected CE must be checked, not only the single CE for
10645 which the CE score was changed in the first place (as every instance
10646 of that CE shares the same CE score, and therefore also can change)!
10648 SCAN_PLAYFIELD(xx, yy)
10650 if (Tile[xx][yy] == element)
10651 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10652 CE_SCORE_GETS_ZERO);
10660 case CA_SET_CE_ARTWORK:
10662 int artwork_element = action_arg_element;
10663 boolean reset_frame = FALSE;
10666 if (action_arg == CA_ARG_ELEMENT_RESET)
10667 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10670 if (ei->gfx_element != artwork_element)
10671 reset_frame = TRUE;
10673 ei->gfx_element = artwork_element;
10675 SCAN_PLAYFIELD(xx, yy)
10677 if (Tile[xx][yy] == element)
10681 ResetGfxAnimation(xx, yy);
10682 ResetRandomAnimationValue(xx, yy);
10685 TEST_DrawLevelField(xx, yy);
10692 // ---------- engine actions ---------------------------------------------
10694 case CA_SET_ENGINE_SCAN_MODE:
10696 InitPlayfieldScanMode(action_arg);
10706 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10708 int old_element = Tile[x][y];
10709 int new_element = GetElementFromGroupElement(element);
10710 int previous_move_direction = MovDir[x][y];
10711 int last_ce_value = CustomValue[x][y];
10712 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10713 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10714 boolean add_player_onto_element = (new_element_is_player &&
10715 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10716 IS_WALKABLE(old_element));
10718 if (!add_player_onto_element)
10720 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10721 RemoveMovingField(x, y);
10725 Tile[x][y] = new_element;
10727 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10728 MovDir[x][y] = previous_move_direction;
10730 if (element_info[new_element].use_last_ce_value)
10731 CustomValue[x][y] = last_ce_value;
10733 InitField_WithBug1(x, y, FALSE);
10735 new_element = Tile[x][y]; // element may have changed
10737 ResetGfxAnimation(x, y);
10738 ResetRandomAnimationValue(x, y);
10740 TEST_DrawLevelField(x, y);
10742 if (GFX_CRUMBLED(new_element))
10743 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10745 if (old_element == EL_EXPLOSION)
10747 Store[x][y] = Store2[x][y] = 0;
10749 // check if new element replaces an exploding player, requiring cleanup
10750 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
10751 StorePlayer[x][y] = 0;
10754 // check if element under the player changes from accessible to unaccessible
10755 // (needed for special case of dropping element which then changes)
10756 // (must be checked after creating new element for walkable group elements)
10757 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10758 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10760 KillPlayer(PLAYERINFO(x, y));
10766 // "ChangeCount" not set yet to allow "entered by player" change one time
10767 if (new_element_is_player)
10768 RelocatePlayer(x, y, new_element);
10771 ChangeCount[x][y]++; // count number of changes in the same frame
10773 TestIfBadThingTouchesPlayer(x, y);
10774 TestIfPlayerTouchesCustomElement(x, y);
10775 TestIfElementTouchesCustomElement(x, y);
10778 static void CreateField(int x, int y, int element)
10780 CreateFieldExt(x, y, element, FALSE);
10783 static void CreateElementFromChange(int x, int y, int element)
10785 element = GET_VALID_RUNTIME_ELEMENT(element);
10787 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10789 int old_element = Tile[x][y];
10791 // prevent changed element from moving in same engine frame
10792 // unless both old and new element can either fall or move
10793 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10794 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10798 CreateFieldExt(x, y, element, TRUE);
10801 static boolean ChangeElement(int x, int y, int element, int page)
10803 struct ElementInfo *ei = &element_info[element];
10804 struct ElementChangeInfo *change = &ei->change_page[page];
10805 int ce_value = CustomValue[x][y];
10806 int ce_score = ei->collect_score;
10807 int target_element;
10808 int old_element = Tile[x][y];
10810 // always use default change event to prevent running into a loop
10811 if (ChangeEvent[x][y] == -1)
10812 ChangeEvent[x][y] = CE_DELAY;
10814 if (ChangeEvent[x][y] == CE_DELAY)
10816 // reset actual trigger element, trigger player and action element
10817 change->actual_trigger_element = EL_EMPTY;
10818 change->actual_trigger_player = EL_EMPTY;
10819 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10820 change->actual_trigger_side = CH_SIDE_NONE;
10821 change->actual_trigger_ce_value = 0;
10822 change->actual_trigger_ce_score = 0;
10823 change->actual_trigger_x = -1;
10824 change->actual_trigger_y = -1;
10827 // do not change elements more than a specified maximum number of changes
10828 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10831 ChangeCount[x][y]++; // count number of changes in the same frame
10833 if (ei->has_anim_event)
10834 HandleGlobalAnimEventByElementChange(element, page, x, y,
10835 change->actual_trigger_x,
10836 change->actual_trigger_y);
10838 if (change->explode)
10845 if (change->use_target_content)
10847 boolean complete_replace = TRUE;
10848 boolean can_replace[3][3];
10851 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10854 boolean is_walkable;
10855 boolean is_diggable;
10856 boolean is_collectible;
10857 boolean is_removable;
10858 boolean is_destructible;
10859 int ex = x + xx - 1;
10860 int ey = y + yy - 1;
10861 int content_element = change->target_content.e[xx][yy];
10864 can_replace[xx][yy] = TRUE;
10866 if (ex == x && ey == y) // do not check changing element itself
10869 if (content_element == EL_EMPTY_SPACE)
10871 can_replace[xx][yy] = FALSE; // do not replace border with space
10876 if (!IN_LEV_FIELD(ex, ey))
10878 can_replace[xx][yy] = FALSE;
10879 complete_replace = FALSE;
10886 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10887 e = MovingOrBlocked2Element(ex, ey);
10889 is_empty = (IS_FREE(ex, ey) ||
10890 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10892 is_walkable = (is_empty || IS_WALKABLE(e));
10893 is_diggable = (is_empty || IS_DIGGABLE(e));
10894 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10895 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10896 is_removable = (is_diggable || is_collectible);
10898 can_replace[xx][yy] =
10899 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10900 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10901 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10902 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10903 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10904 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10905 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10907 if (!can_replace[xx][yy])
10908 complete_replace = FALSE;
10911 if (!change->only_if_complete || complete_replace)
10913 boolean something_has_changed = FALSE;
10915 if (change->only_if_complete && change->use_random_replace &&
10916 RND(100) < change->random_percentage)
10919 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10921 int ex = x + xx - 1;
10922 int ey = y + yy - 1;
10923 int content_element;
10925 if (can_replace[xx][yy] && (!change->use_random_replace ||
10926 RND(100) < change->random_percentage))
10928 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10929 RemoveMovingField(ex, ey);
10931 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10933 content_element = change->target_content.e[xx][yy];
10934 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10935 ce_value, ce_score);
10937 CreateElementFromChange(ex, ey, target_element);
10939 something_has_changed = TRUE;
10941 // for symmetry reasons, freeze newly created border elements
10942 if (ex != x || ey != y)
10943 Stop[ex][ey] = TRUE; // no more moving in this frame
10947 if (something_has_changed)
10949 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10950 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10956 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10957 ce_value, ce_score);
10959 if (element == EL_DIAGONAL_GROWING ||
10960 element == EL_DIAGONAL_SHRINKING)
10962 target_element = Store[x][y];
10964 Store[x][y] = EL_EMPTY;
10967 // special case: element changes to player (and may be kept if walkable)
10968 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10969 CreateElementFromChange(x, y, EL_EMPTY);
10971 CreateElementFromChange(x, y, target_element);
10973 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10974 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10977 // this uses direct change before indirect change
10978 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10983 static void HandleElementChange(int x, int y, int page)
10985 int element = MovingOrBlocked2Element(x, y);
10986 struct ElementInfo *ei = &element_info[element];
10987 struct ElementChangeInfo *change = &ei->change_page[page];
10988 boolean handle_action_before_change = FALSE;
10991 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10992 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10994 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10995 x, y, element, element_info[element].token_name);
10996 Debug("game:playing:HandleElementChange", "This should never happen!");
11000 // this can happen with classic bombs on walkable, changing elements
11001 if (!CAN_CHANGE_OR_HAS_ACTION(element))
11006 if (ChangeDelay[x][y] == 0) // initialize element change
11008 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
11010 if (change->can_change)
11012 // !!! not clear why graphic animation should be reset at all here !!!
11013 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
11014 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
11017 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
11019 When using an animation frame delay of 1 (this only happens with
11020 "sp_zonk.moving.left/right" in the classic graphics), the default
11021 (non-moving) animation shows wrong animation frames (while the
11022 moving animation, like "sp_zonk.moving.left/right", is correct,
11023 so this graphical bug never shows up with the classic graphics).
11024 For an animation with 4 frames, this causes wrong frames 0,0,1,2
11025 be drawn instead of the correct frames 0,1,2,3. This is caused by
11026 "GfxFrame[][]" being reset *twice* (in two successive frames) after
11027 an element change: First when the change delay ("ChangeDelay[][]")
11028 counter has reached zero after decrementing, then a second time in
11029 the next frame (after "GfxFrame[][]" was already incremented) when
11030 "ChangeDelay[][]" is reset to the initial delay value again.
11032 This causes frame 0 to be drawn twice, while the last frame won't
11033 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
11035 As some animations may already be cleverly designed around this bug
11036 (at least the "Snake Bite" snake tail animation does this), it cannot
11037 simply be fixed here without breaking such existing animations.
11038 Unfortunately, it cannot easily be detected if a graphics set was
11039 designed "before" or "after" the bug was fixed. As a workaround,
11040 a new graphics set option "game.graphics_engine_version" was added
11041 to be able to specify the game's major release version for which the
11042 graphics set was designed, which can then be used to decide if the
11043 bugfix should be used (version 4 and above) or not (version 3 or
11044 below, or if no version was specified at all, as with old sets).
11046 (The wrong/fixed animation frames can be tested with the test level set
11047 "test_gfxframe" and level "000", which contains a specially prepared
11048 custom element at level position (x/y) == (11/9) which uses the zonk
11049 animation mentioned above. Using "game.graphics_engine_version: 4"
11050 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
11051 This can also be seen from the debug output for this test element.)
11054 // when a custom element is about to change (for example by change delay),
11055 // do not reset graphic animation when the custom element is moving
11056 if (game.graphics_engine_version < 4 &&
11059 ResetGfxAnimation(x, y);
11060 ResetRandomAnimationValue(x, y);
11063 if (change->pre_change_function)
11064 change->pre_change_function(x, y);
11068 ChangeDelay[x][y]--;
11070 if (ChangeDelay[x][y] != 0) // continue element change
11072 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11074 // also needed if CE can not change, but has CE delay with CE action
11075 if (IS_ANIMATED(graphic))
11076 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11078 if (change->can_change)
11080 if (change->change_function)
11081 change->change_function(x, y);
11084 else // finish element change
11086 if (ChangePage[x][y] != -1) // remember page from delayed change
11088 page = ChangePage[x][y];
11089 ChangePage[x][y] = -1;
11091 change = &ei->change_page[page];
11094 if (IS_MOVING(x, y)) // never change a running system ;-)
11096 ChangeDelay[x][y] = 1; // try change after next move step
11097 ChangePage[x][y] = page; // remember page to use for change
11102 // special case: set new level random seed before changing element
11103 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11104 handle_action_before_change = TRUE;
11106 if (change->has_action && handle_action_before_change)
11107 ExecuteCustomElementAction(x, y, element, page);
11109 if (change->can_change)
11111 if (ChangeElement(x, y, element, page))
11113 if (change->post_change_function)
11114 change->post_change_function(x, y);
11118 if (change->has_action && !handle_action_before_change)
11119 ExecuteCustomElementAction(x, y, element, page);
11123 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11124 int trigger_element,
11126 int trigger_player,
11130 boolean change_done_any = FALSE;
11131 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11134 if (!(trigger_events[trigger_element][trigger_event]))
11137 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11139 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11141 int element = EL_CUSTOM_START + i;
11142 boolean change_done = FALSE;
11145 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11146 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11149 for (p = 0; p < element_info[element].num_change_pages; p++)
11151 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11153 if (change->can_change_or_has_action &&
11154 change->has_event[trigger_event] &&
11155 change->trigger_side & trigger_side &&
11156 change->trigger_player & trigger_player &&
11157 change->trigger_page & trigger_page_bits &&
11158 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11160 change->actual_trigger_element = trigger_element;
11161 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11162 change->actual_trigger_player_bits = trigger_player;
11163 change->actual_trigger_side = trigger_side;
11164 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11165 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11166 change->actual_trigger_x = trigger_x;
11167 change->actual_trigger_y = trigger_y;
11169 if ((change->can_change && !change_done) || change->has_action)
11173 SCAN_PLAYFIELD(x, y)
11175 if (Tile[x][y] == element)
11177 if (change->can_change && !change_done)
11179 // if element already changed in this frame, not only prevent
11180 // another element change (checked in ChangeElement()), but
11181 // also prevent additional element actions for this element
11183 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11184 !level.use_action_after_change_bug)
11187 ChangeDelay[x][y] = 1;
11188 ChangeEvent[x][y] = trigger_event;
11190 HandleElementChange(x, y, p);
11192 else if (change->has_action)
11194 // if element already changed in this frame, not only prevent
11195 // another element change (checked in ChangeElement()), but
11196 // also prevent additional element actions for this element
11198 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11199 !level.use_action_after_change_bug)
11202 ExecuteCustomElementAction(x, y, element, p);
11203 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11208 if (change->can_change)
11210 change_done = TRUE;
11211 change_done_any = TRUE;
11218 RECURSION_LOOP_DETECTION_END();
11220 return change_done_any;
11223 static boolean CheckElementChangeExt(int x, int y,
11225 int trigger_element,
11227 int trigger_player,
11230 boolean change_done = FALSE;
11233 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11234 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11237 if (Tile[x][y] == EL_BLOCKED)
11239 Blocked2Moving(x, y, &x, &y);
11240 element = Tile[x][y];
11243 // check if element has already changed or is about to change after moving
11244 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11245 Tile[x][y] != element) ||
11247 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11248 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11249 ChangePage[x][y] != -1)))
11252 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11254 for (p = 0; p < element_info[element].num_change_pages; p++)
11256 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11258 /* check trigger element for all events where the element that is checked
11259 for changing interacts with a directly adjacent element -- this is
11260 different to element changes that affect other elements to change on the
11261 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11262 boolean check_trigger_element =
11263 (trigger_event == CE_NEXT_TO_X ||
11264 trigger_event == CE_TOUCHING_X ||
11265 trigger_event == CE_HITTING_X ||
11266 trigger_event == CE_HIT_BY_X ||
11267 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11269 if (change->can_change_or_has_action &&
11270 change->has_event[trigger_event] &&
11271 change->trigger_side & trigger_side &&
11272 change->trigger_player & trigger_player &&
11273 (!check_trigger_element ||
11274 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11276 change->actual_trigger_element = trigger_element;
11277 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11278 change->actual_trigger_player_bits = trigger_player;
11279 change->actual_trigger_side = trigger_side;
11280 change->actual_trigger_ce_value = CustomValue[x][y];
11281 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11282 change->actual_trigger_x = x;
11283 change->actual_trigger_y = y;
11285 // special case: trigger element not at (x,y) position for some events
11286 if (check_trigger_element)
11298 { 0, 0 }, { 0, 0 }, { 0, 0 },
11302 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11303 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11305 change->actual_trigger_ce_value = CustomValue[xx][yy];
11306 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11307 change->actual_trigger_x = xx;
11308 change->actual_trigger_y = yy;
11311 if (change->can_change && !change_done)
11313 ChangeDelay[x][y] = 1;
11314 ChangeEvent[x][y] = trigger_event;
11316 HandleElementChange(x, y, p);
11318 change_done = TRUE;
11320 else if (change->has_action)
11322 ExecuteCustomElementAction(x, y, element, p);
11323 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11328 RECURSION_LOOP_DETECTION_END();
11330 return change_done;
11333 static void PlayPlayerSound(struct PlayerInfo *player)
11335 int jx = player->jx, jy = player->jy;
11336 int sound_element = player->artwork_element;
11337 int last_action = player->last_action_waiting;
11338 int action = player->action_waiting;
11340 if (player->is_waiting)
11342 if (action != last_action)
11343 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11345 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11349 if (action != last_action)
11350 StopSound(element_info[sound_element].sound[last_action]);
11352 if (last_action == ACTION_SLEEPING)
11353 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11357 static void PlayAllPlayersSound(void)
11361 for (i = 0; i < MAX_PLAYERS; i++)
11362 if (stored_player[i].active)
11363 PlayPlayerSound(&stored_player[i]);
11366 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11368 boolean last_waiting = player->is_waiting;
11369 int move_dir = player->MovDir;
11371 player->dir_waiting = move_dir;
11372 player->last_action_waiting = player->action_waiting;
11376 if (!last_waiting) // not waiting -> waiting
11378 player->is_waiting = TRUE;
11380 player->frame_counter_bored =
11382 game.player_boring_delay_fixed +
11383 GetSimpleRandom(game.player_boring_delay_random);
11384 player->frame_counter_sleeping =
11386 game.player_sleeping_delay_fixed +
11387 GetSimpleRandom(game.player_sleeping_delay_random);
11389 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11392 if (game.player_sleeping_delay_fixed +
11393 game.player_sleeping_delay_random > 0 &&
11394 player->anim_delay_counter == 0 &&
11395 player->post_delay_counter == 0 &&
11396 FrameCounter >= player->frame_counter_sleeping)
11397 player->is_sleeping = TRUE;
11398 else if (game.player_boring_delay_fixed +
11399 game.player_boring_delay_random > 0 &&
11400 FrameCounter >= player->frame_counter_bored)
11401 player->is_bored = TRUE;
11403 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11404 player->is_bored ? ACTION_BORING :
11407 if (player->is_sleeping && player->use_murphy)
11409 // special case for sleeping Murphy when leaning against non-free tile
11411 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11412 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11413 !IS_MOVING(player->jx - 1, player->jy)))
11414 move_dir = MV_LEFT;
11415 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11416 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11417 !IS_MOVING(player->jx + 1, player->jy)))
11418 move_dir = MV_RIGHT;
11420 player->is_sleeping = FALSE;
11422 player->dir_waiting = move_dir;
11425 if (player->is_sleeping)
11427 if (player->num_special_action_sleeping > 0)
11429 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11431 int last_special_action = player->special_action_sleeping;
11432 int num_special_action = player->num_special_action_sleeping;
11433 int special_action =
11434 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11435 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11436 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11437 last_special_action + 1 : ACTION_SLEEPING);
11438 int special_graphic =
11439 el_act_dir2img(player->artwork_element, special_action, move_dir);
11441 player->anim_delay_counter =
11442 graphic_info[special_graphic].anim_delay_fixed +
11443 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11444 player->post_delay_counter =
11445 graphic_info[special_graphic].post_delay_fixed +
11446 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11448 player->special_action_sleeping = special_action;
11451 if (player->anim_delay_counter > 0)
11453 player->action_waiting = player->special_action_sleeping;
11454 player->anim_delay_counter--;
11456 else if (player->post_delay_counter > 0)
11458 player->post_delay_counter--;
11462 else if (player->is_bored)
11464 if (player->num_special_action_bored > 0)
11466 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11468 int special_action =
11469 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11470 int special_graphic =
11471 el_act_dir2img(player->artwork_element, special_action, move_dir);
11473 player->anim_delay_counter =
11474 graphic_info[special_graphic].anim_delay_fixed +
11475 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11476 player->post_delay_counter =
11477 graphic_info[special_graphic].post_delay_fixed +
11478 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11480 player->special_action_bored = special_action;
11483 if (player->anim_delay_counter > 0)
11485 player->action_waiting = player->special_action_bored;
11486 player->anim_delay_counter--;
11488 else if (player->post_delay_counter > 0)
11490 player->post_delay_counter--;
11495 else if (last_waiting) // waiting -> not waiting
11497 player->is_waiting = FALSE;
11498 player->is_bored = FALSE;
11499 player->is_sleeping = FALSE;
11501 player->frame_counter_bored = -1;
11502 player->frame_counter_sleeping = -1;
11504 player->anim_delay_counter = 0;
11505 player->post_delay_counter = 0;
11507 player->dir_waiting = player->MovDir;
11508 player->action_waiting = ACTION_DEFAULT;
11510 player->special_action_bored = ACTION_DEFAULT;
11511 player->special_action_sleeping = ACTION_DEFAULT;
11515 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11517 if ((!player->is_moving && player->was_moving) ||
11518 (player->MovPos == 0 && player->was_moving) ||
11519 (player->is_snapping && !player->was_snapping) ||
11520 (player->is_dropping && !player->was_dropping))
11522 if (!CheckSaveEngineSnapshotToList())
11525 player->was_moving = FALSE;
11526 player->was_snapping = TRUE;
11527 player->was_dropping = TRUE;
11531 if (player->is_moving)
11532 player->was_moving = TRUE;
11534 if (!player->is_snapping)
11535 player->was_snapping = FALSE;
11537 if (!player->is_dropping)
11538 player->was_dropping = FALSE;
11541 static struct MouseActionInfo mouse_action_last = { 0 };
11542 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11543 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11546 CheckSaveEngineSnapshotToList();
11548 mouse_action_last = mouse_action;
11551 static void CheckSingleStepMode(struct PlayerInfo *player)
11553 if (tape.single_step && tape.recording && !tape.pausing)
11555 // as it is called "single step mode", just return to pause mode when the
11556 // player stopped moving after one tile (or never starts moving at all)
11557 // (reverse logic needed here in case single step mode used in team mode)
11558 if (player->is_moving ||
11559 player->is_pushing ||
11560 player->is_dropping_pressed ||
11561 player->effective_mouse_action.button)
11562 game.enter_single_step_mode = FALSE;
11565 CheckSaveEngineSnapshot(player);
11568 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11570 int left = player_action & JOY_LEFT;
11571 int right = player_action & JOY_RIGHT;
11572 int up = player_action & JOY_UP;
11573 int down = player_action & JOY_DOWN;
11574 int button1 = player_action & JOY_BUTTON_1;
11575 int button2 = player_action & JOY_BUTTON_2;
11576 int dx = (left ? -1 : right ? 1 : 0);
11577 int dy = (up ? -1 : down ? 1 : 0);
11579 if (!player->active || tape.pausing)
11585 SnapField(player, dx, dy);
11589 DropElement(player);
11591 MovePlayer(player, dx, dy);
11594 CheckSingleStepMode(player);
11596 SetPlayerWaiting(player, FALSE);
11598 return player_action;
11602 // no actions for this player (no input at player's configured device)
11604 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11605 SnapField(player, 0, 0);
11606 CheckGravityMovementWhenNotMoving(player);
11608 if (player->MovPos == 0)
11609 SetPlayerWaiting(player, TRUE);
11611 if (player->MovPos == 0) // needed for tape.playing
11612 player->is_moving = FALSE;
11614 player->is_dropping = FALSE;
11615 player->is_dropping_pressed = FALSE;
11616 player->drop_pressed_delay = 0;
11618 CheckSingleStepMode(player);
11624 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11627 if (!tape.use_mouse_actions)
11630 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11631 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11632 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11635 static void SetTapeActionFromMouseAction(byte *tape_action,
11636 struct MouseActionInfo *mouse_action)
11638 if (!tape.use_mouse_actions)
11641 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11642 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11643 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11646 static void CheckLevelSolved(void)
11648 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11650 if (game_bd.level_solved &&
11651 !game_bd.game_over) // game won
11655 game_bd.game_over = TRUE;
11657 game.all_players_gone = TRUE;
11660 if (game_bd.game_over) // game lost
11661 game.all_players_gone = TRUE;
11663 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11665 if (game_em.level_solved &&
11666 !game_em.game_over) // game won
11670 game_em.game_over = TRUE;
11672 game.all_players_gone = TRUE;
11675 if (game_em.game_over) // game lost
11676 game.all_players_gone = TRUE;
11678 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11680 if (game_sp.level_solved &&
11681 !game_sp.game_over) // game won
11685 game_sp.game_over = TRUE;
11687 game.all_players_gone = TRUE;
11690 if (game_sp.game_over) // game lost
11691 game.all_players_gone = TRUE;
11693 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11695 if (game_mm.level_solved &&
11696 !game_mm.game_over) // game won
11700 game_mm.game_over = TRUE;
11702 game.all_players_gone = TRUE;
11705 if (game_mm.game_over) // game lost
11706 game.all_players_gone = TRUE;
11710 static void PlayTimeoutSound(int seconds_left)
11712 // will be played directly by BD engine (for classic bonus time sounds)
11713 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && checkBonusTime_BD())
11716 // try to use individual "running out of time" sound for each second left
11717 int sound = SND_GAME_RUNNING_OUT_OF_TIME_0 - seconds_left;
11719 // if special sound per second not defined, use default sound
11720 if (getSoundInfoEntryFilename(sound) == NULL)
11721 sound = SND_GAME_RUNNING_OUT_OF_TIME;
11723 // if out of time, but player still alive, play special "timeout" sound, if defined
11724 if (seconds_left == 0 && !checkGameFailed())
11725 if (getSoundInfoEntryFilename(SND_GAME_TIMEOUT) != NULL)
11726 sound = SND_GAME_TIMEOUT;
11731 static void CheckLevelTime_StepCounter(void)
11741 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11742 PlayTimeoutSound(TimeLeft);
11744 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11746 DisplayGameControlValues();
11748 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11749 for (i = 0; i < MAX_PLAYERS; i++)
11750 KillPlayer(&stored_player[i]);
11752 else if (game.no_level_time_limit && !game.all_players_gone)
11754 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11756 DisplayGameControlValues();
11760 static void CheckLevelTime(void)
11762 int frames_per_second = FRAMES_PER_SECOND;
11765 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11767 // level time may be running slower in native BD engine
11768 frames_per_second = getFramesPerSecond_BD();
11770 // if native engine time changed, force main engine time change
11771 if (getTimeLeft_BD() < TimeLeft)
11772 TimeFrames = frames_per_second;
11774 // if last second running, wait for native engine time to exactly reach zero
11775 if (getTimeLeft_BD() == 1 && TimeLeft == 1)
11776 TimeFrames = frames_per_second - 1;
11779 if (TimeFrames >= frames_per_second)
11783 for (i = 0; i < MAX_PLAYERS; i++)
11785 struct PlayerInfo *player = &stored_player[i];
11787 if (SHIELD_ON(player))
11789 player->shield_normal_time_left--;
11791 if (player->shield_deadly_time_left > 0)
11792 player->shield_deadly_time_left--;
11796 if (!game.LevelSolved && !level.use_step_counter)
11804 if (TimeLeft <= 10 && game.time_limit)
11805 PlayTimeoutSound(TimeLeft);
11807 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11808 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11810 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11812 if (!TimeLeft && game.time_limit)
11814 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11816 if (game_bd.game->cave->player_state == GD_PL_LIVING)
11817 game_bd.game->cave->player_state = GD_PL_TIMEOUT;
11819 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11821 game_em.lev->killed_out_of_time = TRUE;
11825 for (i = 0; i < MAX_PLAYERS; i++)
11826 KillPlayer(&stored_player[i]);
11830 else if (game.no_level_time_limit && !game.all_players_gone)
11832 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11835 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11839 if (TapeTimeFrames >= FRAMES_PER_SECOND)
11841 TapeTimeFrames = 0;
11844 if (tape.recording || tape.playing)
11845 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11848 if (tape.recording || tape.playing)
11849 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11851 UpdateAndDisplayGameControlValues();
11854 void AdvanceFrameAndPlayerCounters(int player_nr)
11858 // handle game and tape time differently for native BD game engine
11860 // tape time is running in native BD engine even if player is not hatched yet
11861 if (!checkGameRunning())
11864 // advance frame counters (global frame counter and tape time frame counter)
11868 // level time is running in native BD engine after player is being hatched
11869 if (!checkGamePlaying())
11872 // advance time frame counter (used to control available time to solve level)
11875 // advance player counters (counters for move delay, move animation etc.)
11876 for (i = 0; i < MAX_PLAYERS; i++)
11878 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11879 int move_delay_value = stored_player[i].move_delay_value;
11880 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11882 if (!advance_player_counters) // not all players may be affected
11885 if (move_frames == 0) // less than one move per game frame
11887 int stepsize = TILEX / move_delay_value;
11888 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11889 int count = (stored_player[i].is_moving ?
11890 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11892 if (count % delay == 0)
11896 stored_player[i].Frame += move_frames;
11898 if (stored_player[i].MovPos != 0)
11899 stored_player[i].StepFrame += move_frames;
11901 if (stored_player[i].move_delay > 0)
11902 stored_player[i].move_delay--;
11904 // due to bugs in previous versions, counter must count up, not down
11905 if (stored_player[i].push_delay != -1)
11906 stored_player[i].push_delay++;
11908 if (stored_player[i].drop_delay > 0)
11909 stored_player[i].drop_delay--;
11911 if (stored_player[i].is_dropping_pressed)
11912 stored_player[i].drop_pressed_delay++;
11916 void AdvanceFrameCounter(void)
11921 void AdvanceGfxFrame(void)
11925 SCAN_PLAYFIELD(x, y)
11931 static void HandleMouseAction(struct MouseActionInfo *mouse_action,
11932 struct MouseActionInfo *mouse_action_last)
11934 if (mouse_action->button)
11936 int new_button = (mouse_action->button && mouse_action_last->button == 0);
11937 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button);
11938 int x = mouse_action->lx;
11939 int y = mouse_action->ly;
11940 int element = Tile[x][y];
11944 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
11945 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
11949 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
11950 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
11953 if (level.use_step_counter)
11955 boolean counted_click = FALSE;
11957 // element clicked that can change when clicked/pressed
11958 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
11959 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
11960 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
11961 counted_click = TRUE;
11963 // element clicked that can trigger change when clicked/pressed
11964 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
11965 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
11966 counted_click = TRUE;
11968 if (new_button && counted_click)
11969 CheckLevelTime_StepCounter();
11974 void StartGameActions(boolean init_network_game, boolean record_tape,
11977 unsigned int new_random_seed = InitRND(random_seed);
11980 TapeStartRecording(new_random_seed);
11982 if (setup.auto_pause_on_start && !tape.pausing)
11983 TapeTogglePause(TAPE_TOGGLE_MANUAL);
11985 if (init_network_game)
11987 SendToServer_LevelFile();
11988 SendToServer_StartPlaying();
11996 static void GameActionsExt(void)
11999 static unsigned int game_frame_delay = 0;
12001 unsigned int game_frame_delay_value;
12002 byte *recorded_player_action;
12003 byte summarized_player_action = 0;
12004 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
12007 // detect endless loops, caused by custom element programming
12008 if (recursion_loop_detected && recursion_loop_depth == 0)
12010 char *message = getStringCat3("Internal Error! Element ",
12011 EL_NAME(recursion_loop_element),
12012 " caused endless loop! Quit the game?");
12014 Warn("element '%s' caused endless loop in game engine",
12015 EL_NAME(recursion_loop_element));
12017 RequestQuitGameExt(program.headless, level_editor_test_game, message);
12019 recursion_loop_detected = FALSE; // if game should be continued
12026 if (game.restart_level)
12027 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
12029 CheckLevelSolved();
12031 if (game.LevelSolved && !game.LevelSolved_GameEnd)
12034 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
12037 if (game_status != GAME_MODE_PLAYING) // status might have changed
12040 game_frame_delay_value =
12041 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
12043 if (tape.playing && tape.warp_forward && !tape.pausing)
12044 game_frame_delay_value = 0;
12046 SetVideoFrameDelay(game_frame_delay_value);
12048 // (de)activate virtual buttons depending on current game status
12049 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
12051 if (game.all_players_gone) // if no players there to be controlled anymore
12052 SetOverlayActive(FALSE);
12053 else if (!tape.playing) // if game continues after tape stopped playing
12054 SetOverlayActive(TRUE);
12059 // ---------- main game synchronization point ----------
12061 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12063 Debug("game:playing:skip", "skip == %d", skip);
12066 // ---------- main game synchronization point ----------
12068 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12072 if (network_playing && !network_player_action_received)
12074 // try to get network player actions in time
12076 // last chance to get network player actions without main loop delay
12077 HandleNetworking();
12079 // game was quit by network peer
12080 if (game_status != GAME_MODE_PLAYING)
12083 // check if network player actions still missing and game still running
12084 if (!network_player_action_received && !checkGameEnded())
12085 return; // failed to get network player actions in time
12087 // do not yet reset "network_player_action_received" (for tape.pausing)
12093 // at this point we know that we really continue executing the game
12095 network_player_action_received = FALSE;
12097 // when playing tape, read previously recorded player input from tape data
12098 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
12100 local_player->effective_mouse_action = local_player->mouse_action;
12102 if (recorded_player_action != NULL)
12103 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
12104 recorded_player_action);
12106 // TapePlayAction() may return NULL when toggling to "pause before death"
12110 if (tape.set_centered_player)
12112 game.centered_player_nr_next = tape.centered_player_nr_next;
12113 game.set_centered_player = TRUE;
12116 for (i = 0; i < MAX_PLAYERS; i++)
12118 summarized_player_action |= stored_player[i].action;
12120 if (!network_playing && (game.team_mode || tape.playing))
12121 stored_player[i].effective_action = stored_player[i].action;
12124 if (network_playing && !checkGameEnded())
12125 SendToServer_MovePlayer(summarized_player_action);
12127 // summarize all actions at local players mapped input device position
12128 // (this allows using different input devices in single player mode)
12129 if (!network.enabled && !game.team_mode)
12130 stored_player[map_player_action[local_player->index_nr]].effective_action =
12131 summarized_player_action;
12133 // summarize all actions at centered player in local team mode
12134 if (tape.recording &&
12135 setup.team_mode && !network.enabled &&
12136 setup.input_on_focus &&
12137 game.centered_player_nr != -1)
12139 for (i = 0; i < MAX_PLAYERS; i++)
12140 stored_player[map_player_action[i]].effective_action =
12141 (i == game.centered_player_nr ? summarized_player_action : 0);
12144 if (recorded_player_action != NULL)
12145 for (i = 0; i < MAX_PLAYERS; i++)
12146 stored_player[i].effective_action = recorded_player_action[i];
12148 for (i = 0; i < MAX_PLAYERS; i++)
12150 tape_action[i] = stored_player[i].effective_action;
12152 /* (this may happen in the RND game engine if a player was not present on
12153 the playfield on level start, but appeared later from a custom element */
12154 if (setup.team_mode &&
12157 !tape.player_participates[i])
12158 tape.player_participates[i] = TRUE;
12161 SetTapeActionFromMouseAction(tape_action,
12162 &local_player->effective_mouse_action);
12164 // only record actions from input devices, but not programmed actions
12165 if (tape.recording)
12166 TapeRecordAction(tape_action);
12168 // remember if game was played (especially after tape stopped playing)
12169 if (!tape.playing && summarized_player_action && !checkGameFailed())
12170 game.GamePlayed = TRUE;
12172 #if USE_NEW_PLAYER_ASSIGNMENTS
12173 // !!! also map player actions in single player mode !!!
12174 // if (game.team_mode)
12177 byte mapped_action[MAX_PLAYERS];
12179 #if DEBUG_PLAYER_ACTIONS
12180 for (i = 0; i < MAX_PLAYERS; i++)
12181 DebugContinued("", "%d, ", stored_player[i].effective_action);
12184 for (i = 0; i < MAX_PLAYERS; i++)
12185 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
12187 for (i = 0; i < MAX_PLAYERS; i++)
12188 stored_player[i].effective_action = mapped_action[i];
12190 #if DEBUG_PLAYER_ACTIONS
12191 DebugContinued("", "=> ");
12192 for (i = 0; i < MAX_PLAYERS; i++)
12193 DebugContinued("", "%d, ", stored_player[i].effective_action);
12194 DebugContinued("game:playing:player", "\n");
12197 #if DEBUG_PLAYER_ACTIONS
12200 for (i = 0; i < MAX_PLAYERS; i++)
12201 DebugContinued("", "%d, ", stored_player[i].effective_action);
12202 DebugContinued("game:playing:player", "\n");
12207 for (i = 0; i < MAX_PLAYERS; i++)
12209 // allow engine snapshot in case of changed movement attempt
12210 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12211 (stored_player[i].effective_action & KEY_MOTION))
12212 game.snapshot.changed_action = TRUE;
12214 // allow engine snapshot in case of snapping/dropping attempt
12215 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12216 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12217 game.snapshot.changed_action = TRUE;
12219 game.snapshot.last_action[i] = stored_player[i].effective_action;
12222 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
12224 GameActions_BD_Main();
12226 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12228 GameActions_EM_Main();
12230 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12232 GameActions_SP_Main();
12234 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12236 GameActions_MM_Main();
12240 GameActions_RND_Main();
12243 BlitScreenToBitmap(backbuffer);
12245 CheckLevelSolved();
12248 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12250 if (global.show_frames_per_second)
12252 static unsigned int fps_counter = 0;
12253 static int fps_frames = 0;
12254 unsigned int fps_delay_ms = Counter() - fps_counter;
12258 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12260 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12263 fps_counter = Counter();
12265 // always draw FPS to screen after FPS value was updated
12266 redraw_mask |= REDRAW_FPS;
12269 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12270 if (GetDrawDeactivationMask() == REDRAW_NONE)
12271 redraw_mask |= REDRAW_FPS;
12275 static void GameActions_CheckSaveEngineSnapshot(void)
12277 if (!game.snapshot.save_snapshot)
12280 // clear flag for saving snapshot _before_ saving snapshot
12281 game.snapshot.save_snapshot = FALSE;
12283 SaveEngineSnapshotToList();
12286 void GameActions(void)
12290 GameActions_CheckSaveEngineSnapshot();
12293 void GameActions_BD_Main(void)
12295 byte effective_action[MAX_PLAYERS];
12298 for (i = 0; i < MAX_PLAYERS; i++)
12299 effective_action[i] = stored_player[i].effective_action;
12301 GameActions_BD(effective_action);
12304 void GameActions_EM_Main(void)
12306 byte effective_action[MAX_PLAYERS];
12309 for (i = 0; i < MAX_PLAYERS; i++)
12310 effective_action[i] = stored_player[i].effective_action;
12312 GameActions_EM(effective_action);
12315 void GameActions_SP_Main(void)
12317 byte effective_action[MAX_PLAYERS];
12320 for (i = 0; i < MAX_PLAYERS; i++)
12321 effective_action[i] = stored_player[i].effective_action;
12323 GameActions_SP(effective_action);
12325 for (i = 0; i < MAX_PLAYERS; i++)
12327 if (stored_player[i].force_dropping)
12328 stored_player[i].action |= KEY_BUTTON_DROP;
12330 stored_player[i].force_dropping = FALSE;
12334 void GameActions_MM_Main(void)
12338 GameActions_MM(local_player->effective_mouse_action);
12341 void GameActions_RND_Main(void)
12346 void GameActions_RND(void)
12348 static struct MouseActionInfo mouse_action_last = { 0 };
12349 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12350 int magic_wall_x = 0, magic_wall_y = 0;
12351 int i, x, y, element, graphic, last_gfx_frame;
12353 InitPlayfieldScanModeVars();
12355 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12357 SCAN_PLAYFIELD(x, y)
12359 ChangeCount[x][y] = 0;
12360 ChangeEvent[x][y] = -1;
12364 if (game.set_centered_player)
12366 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12368 // switching to "all players" only possible if all players fit to screen
12369 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12371 game.centered_player_nr_next = game.centered_player_nr;
12372 game.set_centered_player = FALSE;
12375 // do not switch focus to non-existing (or non-active) player
12376 if (game.centered_player_nr_next >= 0 &&
12377 !stored_player[game.centered_player_nr_next].active)
12379 game.centered_player_nr_next = game.centered_player_nr;
12380 game.set_centered_player = FALSE;
12384 if (game.set_centered_player &&
12385 ScreenMovPos == 0) // screen currently aligned at tile position
12389 if (game.centered_player_nr_next == -1)
12391 setScreenCenteredToAllPlayers(&sx, &sy);
12395 sx = stored_player[game.centered_player_nr_next].jx;
12396 sy = stored_player[game.centered_player_nr_next].jy;
12399 game.centered_player_nr = game.centered_player_nr_next;
12400 game.set_centered_player = FALSE;
12402 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12403 DrawGameDoorValues();
12406 // check single step mode (set flag and clear again if any player is active)
12407 game.enter_single_step_mode =
12408 (tape.single_step && tape.recording && !tape.pausing);
12410 for (i = 0; i < MAX_PLAYERS; i++)
12412 int actual_player_action = stored_player[i].effective_action;
12415 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12416 - rnd_equinox_tetrachloride 048
12417 - rnd_equinox_tetrachloride_ii 096
12418 - rnd_emanuel_schmieg 002
12419 - doctor_sloan_ww 001, 020
12421 if (stored_player[i].MovPos == 0)
12422 CheckGravityMovement(&stored_player[i]);
12425 // overwrite programmed action with tape action
12426 if (stored_player[i].programmed_action)
12427 actual_player_action = stored_player[i].programmed_action;
12429 PlayerActions(&stored_player[i], actual_player_action);
12431 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12434 // single step pause mode may already have been toggled by "ScrollPlayer()"
12435 if (game.enter_single_step_mode && !tape.pausing)
12436 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12438 ScrollScreen(NULL, SCROLL_GO_ON);
12440 /* for backwards compatibility, the following code emulates a fixed bug that
12441 occured when pushing elements (causing elements that just made their last
12442 pushing step to already (if possible) make their first falling step in the
12443 same game frame, which is bad); this code is also needed to use the famous
12444 "spring push bug" which is used in older levels and might be wanted to be
12445 used also in newer levels, but in this case the buggy pushing code is only
12446 affecting the "spring" element and no other elements */
12448 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12450 for (i = 0; i < MAX_PLAYERS; i++)
12452 struct PlayerInfo *player = &stored_player[i];
12453 int x = player->jx;
12454 int y = player->jy;
12456 if (player->active && player->is_pushing && player->is_moving &&
12458 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12459 Tile[x][y] == EL_SPRING))
12461 ContinueMoving(x, y);
12463 // continue moving after pushing (this is actually a bug)
12464 if (!IS_MOVING(x, y))
12465 Stop[x][y] = FALSE;
12470 SCAN_PLAYFIELD(x, y)
12472 Last[x][y] = Tile[x][y];
12474 ChangeCount[x][y] = 0;
12475 ChangeEvent[x][y] = -1;
12477 // this must be handled before main playfield loop
12478 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12481 if (MovDelay[x][y] <= 0)
12485 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12488 if (MovDelay[x][y] <= 0)
12490 int element = Store[x][y];
12491 int move_direction = MovDir[x][y];
12492 int player_index_bit = Store2[x][y];
12498 TEST_DrawLevelField(x, y);
12500 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12502 if (IS_ENVELOPE(element))
12503 local_player->show_envelope = element;
12508 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12510 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12512 Debug("game:playing:GameActions_RND", "This should never happen!");
12514 ChangePage[x][y] = -1;
12518 Stop[x][y] = FALSE;
12519 if (WasJustMoving[x][y] > 0)
12520 WasJustMoving[x][y]--;
12521 if (WasJustFalling[x][y] > 0)
12522 WasJustFalling[x][y]--;
12523 if (CheckCollision[x][y] > 0)
12524 CheckCollision[x][y]--;
12525 if (CheckImpact[x][y] > 0)
12526 CheckImpact[x][y]--;
12530 /* reset finished pushing action (not done in ContinueMoving() to allow
12531 continuous pushing animation for elements with zero push delay) */
12532 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12534 ResetGfxAnimation(x, y);
12535 TEST_DrawLevelField(x, y);
12539 if (IS_BLOCKED(x, y))
12543 Blocked2Moving(x, y, &oldx, &oldy);
12544 if (!IS_MOVING(oldx, oldy))
12546 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12547 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12548 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12549 Debug("game:playing:GameActions_RND", "This should never happen!");
12555 HandleMouseAction(&mouse_action, &mouse_action_last);
12557 SCAN_PLAYFIELD(x, y)
12559 element = Tile[x][y];
12560 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12561 last_gfx_frame = GfxFrame[x][y];
12563 if (element == EL_EMPTY)
12564 graphic = el2img(GfxElementEmpty[x][y]);
12566 ResetGfxFrame(x, y);
12568 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12569 DrawLevelGraphicAnimation(x, y, graphic);
12571 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12572 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12573 ResetRandomAnimationValue(x, y);
12575 SetRandomAnimationValue(x, y);
12577 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12579 if (IS_INACTIVE(element))
12581 if (IS_ANIMATED(graphic))
12582 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12587 // this may take place after moving, so 'element' may have changed
12588 if (IS_CHANGING(x, y) &&
12589 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12591 int page = element_info[element].event_page_nr[CE_DELAY];
12593 HandleElementChange(x, y, page);
12595 element = Tile[x][y];
12596 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12599 CheckNextToConditions(x, y);
12601 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12605 element = Tile[x][y];
12606 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12608 if (IS_ANIMATED(graphic) &&
12609 !IS_MOVING(x, y) &&
12611 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12613 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12614 TEST_DrawTwinkleOnField(x, y);
12616 else if (element == EL_ACID)
12619 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12621 else if ((element == EL_EXIT_OPEN ||
12622 element == EL_EM_EXIT_OPEN ||
12623 element == EL_SP_EXIT_OPEN ||
12624 element == EL_STEEL_EXIT_OPEN ||
12625 element == EL_EM_STEEL_EXIT_OPEN ||
12626 element == EL_SP_TERMINAL ||
12627 element == EL_SP_TERMINAL_ACTIVE ||
12628 element == EL_EXTRA_TIME ||
12629 element == EL_SHIELD_NORMAL ||
12630 element == EL_SHIELD_DEADLY) &&
12631 IS_ANIMATED(graphic))
12632 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12633 else if (IS_MOVING(x, y))
12634 ContinueMoving(x, y);
12635 else if (IS_ACTIVE_BOMB(element))
12636 CheckDynamite(x, y);
12637 else if (element == EL_AMOEBA_GROWING)
12638 AmoebaGrowing(x, y);
12639 else if (element == EL_AMOEBA_SHRINKING)
12640 AmoebaShrinking(x, y);
12642 #if !USE_NEW_AMOEBA_CODE
12643 else if (IS_AMOEBALIVE(element))
12644 AmoebaReproduce(x, y);
12647 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12649 else if (element == EL_EXIT_CLOSED)
12651 else if (element == EL_EM_EXIT_CLOSED)
12653 else if (element == EL_STEEL_EXIT_CLOSED)
12654 CheckExitSteel(x, y);
12655 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12656 CheckExitSteelEM(x, y);
12657 else if (element == EL_SP_EXIT_CLOSED)
12659 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12660 element == EL_EXPANDABLE_STEELWALL_GROWING)
12662 else if (element == EL_EXPANDABLE_WALL ||
12663 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12664 element == EL_EXPANDABLE_WALL_VERTICAL ||
12665 element == EL_EXPANDABLE_WALL_ANY ||
12666 element == EL_BD_EXPANDABLE_WALL ||
12667 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12668 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12669 element == EL_EXPANDABLE_STEELWALL_ANY)
12670 CheckWallGrowing(x, y);
12671 else if (element == EL_FLAMES)
12672 CheckForDragon(x, y);
12673 else if (element == EL_EXPLOSION)
12674 ; // drawing of correct explosion animation is handled separately
12675 else if (element == EL_ELEMENT_SNAPPING ||
12676 element == EL_DIAGONAL_SHRINKING ||
12677 element == EL_DIAGONAL_GROWING)
12679 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]);
12681 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12683 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12684 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12686 if (IS_BELT_ACTIVE(element))
12687 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12689 if (game.magic_wall_active)
12691 int jx = local_player->jx, jy = local_player->jy;
12693 // play the element sound at the position nearest to the player
12694 if ((element == EL_MAGIC_WALL_FULL ||
12695 element == EL_MAGIC_WALL_ACTIVE ||
12696 element == EL_MAGIC_WALL_EMPTYING ||
12697 element == EL_BD_MAGIC_WALL_FULL ||
12698 element == EL_BD_MAGIC_WALL_ACTIVE ||
12699 element == EL_BD_MAGIC_WALL_EMPTYING ||
12700 element == EL_DC_MAGIC_WALL_FULL ||
12701 element == EL_DC_MAGIC_WALL_ACTIVE ||
12702 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12703 ABS(x - jx) + ABS(y - jy) <
12704 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12712 #if USE_NEW_AMOEBA_CODE
12713 // new experimental amoeba growth stuff
12714 if (!(FrameCounter % 8))
12716 static unsigned int random = 1684108901;
12718 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12720 x = RND(lev_fieldx);
12721 y = RND(lev_fieldy);
12722 element = Tile[x][y];
12724 if (!IS_PLAYER(x, y) &&
12725 (element == EL_EMPTY ||
12726 CAN_GROW_INTO(element) ||
12727 element == EL_QUICKSAND_EMPTY ||
12728 element == EL_QUICKSAND_FAST_EMPTY ||
12729 element == EL_ACID_SPLASH_LEFT ||
12730 element == EL_ACID_SPLASH_RIGHT))
12732 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12733 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12734 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12735 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12736 Tile[x][y] = EL_AMOEBA_DROP;
12739 random = random * 129 + 1;
12744 game.explosions_delayed = FALSE;
12746 SCAN_PLAYFIELD(x, y)
12748 element = Tile[x][y];
12750 if (ExplodeField[x][y])
12751 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12752 else if (element == EL_EXPLOSION)
12753 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12755 ExplodeField[x][y] = EX_TYPE_NONE;
12758 game.explosions_delayed = TRUE;
12760 if (game.magic_wall_active)
12762 if (!(game.magic_wall_time_left % 4))
12764 int element = Tile[magic_wall_x][magic_wall_y];
12766 if (element == EL_BD_MAGIC_WALL_FULL ||
12767 element == EL_BD_MAGIC_WALL_ACTIVE ||
12768 element == EL_BD_MAGIC_WALL_EMPTYING)
12769 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12770 else if (element == EL_DC_MAGIC_WALL_FULL ||
12771 element == EL_DC_MAGIC_WALL_ACTIVE ||
12772 element == EL_DC_MAGIC_WALL_EMPTYING)
12773 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12775 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12778 if (game.magic_wall_time_left > 0)
12780 game.magic_wall_time_left--;
12782 if (!game.magic_wall_time_left)
12784 SCAN_PLAYFIELD(x, y)
12786 element = Tile[x][y];
12788 if (element == EL_MAGIC_WALL_ACTIVE ||
12789 element == EL_MAGIC_WALL_FULL)
12791 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12792 TEST_DrawLevelField(x, y);
12794 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12795 element == EL_BD_MAGIC_WALL_FULL)
12797 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12798 TEST_DrawLevelField(x, y);
12800 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12801 element == EL_DC_MAGIC_WALL_FULL)
12803 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12804 TEST_DrawLevelField(x, y);
12808 game.magic_wall_active = FALSE;
12813 if (game.light_time_left > 0)
12815 game.light_time_left--;
12817 if (game.light_time_left == 0)
12818 RedrawAllLightSwitchesAndInvisibleElements();
12821 if (game.timegate_time_left > 0)
12823 game.timegate_time_left--;
12825 if (game.timegate_time_left == 0)
12826 CloseAllOpenTimegates();
12829 if (game.lenses_time_left > 0)
12831 game.lenses_time_left--;
12833 if (game.lenses_time_left == 0)
12834 RedrawAllInvisibleElementsForLenses();
12837 if (game.magnify_time_left > 0)
12839 game.magnify_time_left--;
12841 if (game.magnify_time_left == 0)
12842 RedrawAllInvisibleElementsForMagnifier();
12845 for (i = 0; i < MAX_PLAYERS; i++)
12847 struct PlayerInfo *player = &stored_player[i];
12849 if (SHIELD_ON(player))
12851 if (player->shield_deadly_time_left)
12852 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12853 else if (player->shield_normal_time_left)
12854 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12858 #if USE_DELAYED_GFX_REDRAW
12859 SCAN_PLAYFIELD(x, y)
12861 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12863 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12864 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12866 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12867 DrawLevelField(x, y);
12869 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12870 DrawLevelFieldCrumbled(x, y);
12872 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12873 DrawLevelFieldCrumbledNeighbours(x, y);
12875 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12876 DrawTwinkleOnField(x, y);
12879 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12884 PlayAllPlayersSound();
12886 for (i = 0; i < MAX_PLAYERS; i++)
12888 struct PlayerInfo *player = &stored_player[i];
12890 if (player->show_envelope != 0 && (!player->active ||
12891 player->MovPos == 0))
12893 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12895 player->show_envelope = 0;
12899 // use random number generator in every frame to make it less predictable
12900 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12903 mouse_action_last = mouse_action;
12906 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12908 int min_x = x, min_y = y, max_x = x, max_y = y;
12909 int scr_fieldx = getScreenFieldSizeX();
12910 int scr_fieldy = getScreenFieldSizeY();
12913 for (i = 0; i < MAX_PLAYERS; i++)
12915 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12917 if (!stored_player[i].active || &stored_player[i] == player)
12920 min_x = MIN(min_x, jx);
12921 min_y = MIN(min_y, jy);
12922 max_x = MAX(max_x, jx);
12923 max_y = MAX(max_y, jy);
12926 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12929 static boolean AllPlayersInVisibleScreen(void)
12933 for (i = 0; i < MAX_PLAYERS; i++)
12935 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12937 if (!stored_player[i].active)
12940 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12947 void ScrollLevel(int dx, int dy)
12949 int scroll_offset = 2 * TILEX_VAR;
12952 BlitBitmap(drawto_field, drawto_field,
12953 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12954 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12955 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12956 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12957 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12958 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12962 x = (dx == 1 ? BX1 : BX2);
12963 for (y = BY1; y <= BY2; y++)
12964 DrawScreenField(x, y);
12969 y = (dy == 1 ? BY1 : BY2);
12970 for (x = BX1; x <= BX2; x++)
12971 DrawScreenField(x, y);
12974 redraw_mask |= REDRAW_FIELD;
12977 static boolean canFallDown(struct PlayerInfo *player)
12979 int jx = player->jx, jy = player->jy;
12981 return (IN_LEV_FIELD(jx, jy + 1) &&
12982 (IS_FREE(jx, jy + 1) ||
12983 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12984 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12985 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12988 static boolean canPassField(int x, int y, int move_dir)
12990 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12991 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12992 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12993 int nextx = x + dx;
12994 int nexty = y + dy;
12995 int element = Tile[x][y];
12997 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12998 !CAN_MOVE(element) &&
12999 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
13000 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
13001 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
13004 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
13006 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13007 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13008 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13012 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
13013 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
13014 (IS_DIGGABLE(Tile[newx][newy]) ||
13015 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
13016 canPassField(newx, newy, move_dir)));
13019 static void CheckGravityMovement(struct PlayerInfo *player)
13021 if (player->gravity && !player->programmed_action)
13023 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
13024 int move_dir_vertical = player->effective_action & MV_VERTICAL;
13025 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
13026 int jx = player->jx, jy = player->jy;
13027 boolean player_is_moving_to_valid_field =
13028 (!player_is_snapping &&
13029 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
13030 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
13031 boolean player_can_fall_down = canFallDown(player);
13033 if (player_can_fall_down &&
13034 !player_is_moving_to_valid_field)
13035 player->programmed_action = MV_DOWN;
13039 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
13041 return CheckGravityMovement(player);
13043 if (player->gravity && !player->programmed_action)
13045 int jx = player->jx, jy = player->jy;
13046 boolean field_under_player_is_free =
13047 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
13048 boolean player_is_standing_on_valid_field =
13049 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
13050 (IS_WALKABLE(Tile[jx][jy]) &&
13051 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
13053 if (field_under_player_is_free && !player_is_standing_on_valid_field)
13054 player->programmed_action = MV_DOWN;
13059 MovePlayerOneStep()
13060 -----------------------------------------------------------------------------
13061 dx, dy: direction (non-diagonal) to try to move the player to
13062 real_dx, real_dy: direction as read from input device (can be diagonal)
13065 boolean MovePlayerOneStep(struct PlayerInfo *player,
13066 int dx, int dy, int real_dx, int real_dy)
13068 int jx = player->jx, jy = player->jy;
13069 int new_jx = jx + dx, new_jy = jy + dy;
13071 boolean player_can_move = !player->cannot_move;
13073 if (!player->active || (!dx && !dy))
13074 return MP_NO_ACTION;
13076 player->MovDir = (dx < 0 ? MV_LEFT :
13077 dx > 0 ? MV_RIGHT :
13079 dy > 0 ? MV_DOWN : MV_NONE);
13081 if (!IN_LEV_FIELD(new_jx, new_jy))
13082 return MP_NO_ACTION;
13084 if (!player_can_move)
13086 if (player->MovPos == 0)
13088 player->is_moving = FALSE;
13089 player->is_digging = FALSE;
13090 player->is_collecting = FALSE;
13091 player->is_snapping = FALSE;
13092 player->is_pushing = FALSE;
13096 if (!network.enabled && game.centered_player_nr == -1 &&
13097 !AllPlayersInSight(player, new_jx, new_jy))
13098 return MP_NO_ACTION;
13100 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG);
13101 if (can_move != MP_MOVING)
13104 // check if DigField() has caused relocation of the player
13105 if (player->jx != jx || player->jy != jy)
13106 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
13108 StorePlayer[jx][jy] = 0;
13109 player->last_jx = jx;
13110 player->last_jy = jy;
13111 player->jx = new_jx;
13112 player->jy = new_jy;
13113 StorePlayer[new_jx][new_jy] = player->element_nr;
13115 if (player->move_delay_value_next != -1)
13117 player->move_delay_value = player->move_delay_value_next;
13118 player->move_delay_value_next = -1;
13122 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
13124 player->step_counter++;
13126 PlayerVisit[jx][jy] = FrameCounter;
13128 player->is_moving = TRUE;
13131 // should better be called in MovePlayer(), but this breaks some tapes
13132 ScrollPlayer(player, SCROLL_INIT);
13138 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
13140 int jx = player->jx, jy = player->jy;
13141 int old_jx = jx, old_jy = jy;
13142 int moved = MP_NO_ACTION;
13144 if (!player->active)
13149 if (player->MovPos == 0)
13151 player->is_moving = FALSE;
13152 player->is_digging = FALSE;
13153 player->is_collecting = FALSE;
13154 player->is_snapping = FALSE;
13155 player->is_pushing = FALSE;
13161 if (player->move_delay > 0)
13164 player->move_delay = -1; // set to "uninitialized" value
13166 // store if player is automatically moved to next field
13167 player->is_auto_moving = (player->programmed_action != MV_NONE);
13169 // remove the last programmed player action
13170 player->programmed_action = 0;
13172 if (player->MovPos)
13174 // should only happen if pre-1.2 tape recordings are played
13175 // this is only for backward compatibility
13177 int original_move_delay_value = player->move_delay_value;
13180 Debug("game:playing:MovePlayer",
13181 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13185 // scroll remaining steps with finest movement resolution
13186 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13188 while (player->MovPos)
13190 ScrollPlayer(player, SCROLL_GO_ON);
13191 ScrollScreen(NULL, SCROLL_GO_ON);
13193 AdvanceFrameAndPlayerCounters(player->index_nr);
13196 BackToFront_WithFrameDelay(0);
13199 player->move_delay_value = original_move_delay_value;
13202 player->is_active = FALSE;
13204 if (player->last_move_dir & MV_HORIZONTAL)
13206 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13207 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13211 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13212 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13215 if (!moved && !player->is_active)
13217 player->is_moving = FALSE;
13218 player->is_digging = FALSE;
13219 player->is_collecting = FALSE;
13220 player->is_snapping = FALSE;
13221 player->is_pushing = FALSE;
13227 if (moved & MP_MOVING && !ScreenMovPos &&
13228 (player->index_nr == game.centered_player_nr ||
13229 game.centered_player_nr == -1))
13231 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13233 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13235 // actual player has left the screen -- scroll in that direction
13236 if (jx != old_jx) // player has moved horizontally
13237 scroll_x += (jx - old_jx);
13238 else // player has moved vertically
13239 scroll_y += (jy - old_jy);
13243 int offset_raw = game.scroll_delay_value;
13245 if (jx != old_jx) // player has moved horizontally
13247 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13248 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13249 int new_scroll_x = jx - MIDPOSX + offset_x;
13251 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13252 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13253 scroll_x = new_scroll_x;
13255 // don't scroll over playfield boundaries
13256 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13258 // don't scroll more than one field at a time
13259 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13261 // don't scroll against the player's moving direction
13262 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13263 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13264 scroll_x = old_scroll_x;
13266 else // player has moved vertically
13268 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13269 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13270 int new_scroll_y = jy - MIDPOSY + offset_y;
13272 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13273 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13274 scroll_y = new_scroll_y;
13276 // don't scroll over playfield boundaries
13277 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13279 // don't scroll more than one field at a time
13280 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13282 // don't scroll against the player's moving direction
13283 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13284 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13285 scroll_y = old_scroll_y;
13289 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13291 if (!network.enabled && game.centered_player_nr == -1 &&
13292 !AllPlayersInVisibleScreen())
13294 scroll_x = old_scroll_x;
13295 scroll_y = old_scroll_y;
13299 ScrollScreen(player, SCROLL_INIT);
13300 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13305 player->StepFrame = 0;
13307 if (moved & MP_MOVING)
13309 if (old_jx != jx && old_jy == jy)
13310 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13311 else if (old_jx == jx && old_jy != jy)
13312 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13314 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13316 player->last_move_dir = player->MovDir;
13317 player->is_moving = TRUE;
13318 player->is_snapping = FALSE;
13319 player->is_switching = FALSE;
13320 player->is_dropping = FALSE;
13321 player->is_dropping_pressed = FALSE;
13322 player->drop_pressed_delay = 0;
13325 // should better be called here than above, but this breaks some tapes
13326 ScrollPlayer(player, SCROLL_INIT);
13331 CheckGravityMovementWhenNotMoving(player);
13333 player->is_moving = FALSE;
13335 /* at this point, the player is allowed to move, but cannot move right now
13336 (e.g. because of something blocking the way) -- ensure that the player
13337 is also allowed to move in the next frame (in old versions before 3.1.1,
13338 the player was forced to wait again for eight frames before next try) */
13340 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13341 player->move_delay = 0; // allow direct movement in the next frame
13344 if (player->move_delay == -1) // not yet initialized by DigField()
13345 player->move_delay = player->move_delay_value;
13347 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13349 TestIfPlayerTouchesBadThing(jx, jy);
13350 TestIfPlayerTouchesCustomElement(jx, jy);
13353 if (!player->active)
13354 RemovePlayer(player);
13359 void ScrollPlayer(struct PlayerInfo *player, int mode)
13361 int jx = player->jx, jy = player->jy;
13362 int last_jx = player->last_jx, last_jy = player->last_jy;
13363 int move_stepsize = TILEX / player->move_delay_value;
13365 if (!player->active)
13368 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13371 if (mode == SCROLL_INIT)
13373 player->actual_frame_counter.count = FrameCounter;
13374 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13376 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13377 Tile[last_jx][last_jy] == EL_EMPTY)
13379 int last_field_block_delay = 0; // start with no blocking at all
13380 int block_delay_adjustment = player->block_delay_adjustment;
13382 // if player blocks last field, add delay for exactly one move
13383 if (player->block_last_field)
13385 last_field_block_delay += player->move_delay_value;
13387 // when blocking enabled, prevent moving up despite gravity
13388 if (player->gravity && player->MovDir == MV_UP)
13389 block_delay_adjustment = -1;
13392 // add block delay adjustment (also possible when not blocking)
13393 last_field_block_delay += block_delay_adjustment;
13395 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13396 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13399 if (player->MovPos != 0) // player has not yet reached destination
13402 else if (!FrameReached(&player->actual_frame_counter))
13405 if (player->MovPos != 0)
13407 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13408 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13410 // before DrawPlayer() to draw correct player graphic for this case
13411 if (player->MovPos == 0)
13412 CheckGravityMovement(player);
13415 if (player->MovPos == 0) // player reached destination field
13417 if (player->move_delay_reset_counter > 0)
13419 player->move_delay_reset_counter--;
13421 if (player->move_delay_reset_counter == 0)
13423 // continue with normal speed after quickly moving through gate
13424 HALVE_PLAYER_SPEED(player);
13426 // be able to make the next move without delay
13427 player->move_delay = 0;
13431 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13432 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13433 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13434 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13435 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13436 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13437 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13438 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13440 ExitPlayer(player);
13442 if (game.players_still_needed == 0 &&
13443 (game.friends_still_needed == 0 ||
13444 IS_SP_ELEMENT(Tile[jx][jy])))
13448 player->last_jx = jx;
13449 player->last_jy = jy;
13451 // this breaks one level: "machine", level 000
13453 int move_direction = player->MovDir;
13454 int enter_side = MV_DIR_OPPOSITE(move_direction);
13455 int leave_side = move_direction;
13456 int old_jx = last_jx;
13457 int old_jy = last_jy;
13458 int old_element = Tile[old_jx][old_jy];
13459 int new_element = Tile[jx][jy];
13461 if (IS_CUSTOM_ELEMENT(old_element))
13462 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13464 player->index_bit, leave_side);
13466 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13467 CE_PLAYER_LEAVES_X,
13468 player->index_bit, leave_side);
13470 // needed because pushed element has not yet reached its destination,
13471 // so it would trigger a change event at its previous field location
13472 if (!player->is_pushing)
13474 if (IS_CUSTOM_ELEMENT(new_element))
13475 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13476 player->index_bit, enter_side);
13478 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13479 CE_PLAYER_ENTERS_X,
13480 player->index_bit, enter_side);
13483 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13484 CE_MOVE_OF_X, move_direction);
13487 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13489 TestIfPlayerTouchesBadThing(jx, jy);
13490 TestIfPlayerTouchesCustomElement(jx, jy);
13492 // needed because pushed element has not yet reached its destination,
13493 // so it would trigger a change event at its previous field location
13494 if (!player->is_pushing)
13495 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13497 if (level.finish_dig_collect &&
13498 (player->is_digging || player->is_collecting))
13500 int last_element = player->last_removed_element;
13501 int move_direction = player->MovDir;
13502 int enter_side = MV_DIR_OPPOSITE(move_direction);
13503 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13504 CE_PLAYER_COLLECTS_X);
13506 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13507 player->index_bit, enter_side);
13509 player->last_removed_element = EL_UNDEFINED;
13512 if (!player->active)
13513 RemovePlayer(player);
13516 if (level.use_step_counter)
13517 CheckLevelTime_StepCounter();
13519 if (tape.single_step && tape.recording && !tape.pausing &&
13520 !player->programmed_action)
13521 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13523 if (!player->programmed_action)
13524 CheckSaveEngineSnapshot(player);
13528 void ScrollScreen(struct PlayerInfo *player, int mode)
13530 static DelayCounter screen_frame_counter = { 0 };
13532 if (mode == SCROLL_INIT)
13534 // set scrolling step size according to actual player's moving speed
13535 ScrollStepSize = TILEX / player->move_delay_value;
13537 screen_frame_counter.count = FrameCounter;
13538 screen_frame_counter.value = 1;
13540 ScreenMovDir = player->MovDir;
13541 ScreenMovPos = player->MovPos;
13542 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13545 else if (!FrameReached(&screen_frame_counter))
13550 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13551 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13552 redraw_mask |= REDRAW_FIELD;
13555 ScreenMovDir = MV_NONE;
13558 void CheckNextToConditions(int x, int y)
13560 int element = Tile[x][y];
13562 if (IS_PLAYER(x, y))
13563 TestIfPlayerNextToCustomElement(x, y);
13565 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13566 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13567 TestIfElementNextToCustomElement(x, y);
13570 void TestIfPlayerNextToCustomElement(int x, int y)
13572 struct XY *xy = xy_topdown;
13573 static int trigger_sides[4][2] =
13575 // center side border side
13576 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13577 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13578 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13579 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13583 if (!IS_PLAYER(x, y))
13586 struct PlayerInfo *player = PLAYERINFO(x, y);
13588 if (player->is_moving)
13591 for (i = 0; i < NUM_DIRECTIONS; i++)
13593 int xx = x + xy[i].x;
13594 int yy = y + xy[i].y;
13595 int border_side = trigger_sides[i][1];
13596 int border_element;
13598 if (!IN_LEV_FIELD(xx, yy))
13601 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13602 continue; // center and border element not connected
13604 border_element = Tile[xx][yy];
13606 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13607 player->index_bit, border_side);
13608 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13609 CE_PLAYER_NEXT_TO_X,
13610 player->index_bit, border_side);
13612 /* use player element that is initially defined in the level playfield,
13613 not the player element that corresponds to the runtime player number
13614 (example: a level that contains EL_PLAYER_3 as the only player would
13615 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13617 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13618 CE_NEXT_TO_X, border_side);
13622 void TestIfPlayerTouchesCustomElement(int x, int y)
13624 struct XY *xy = xy_topdown;
13625 static int trigger_sides[4][2] =
13627 // center side border side
13628 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13629 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13630 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13631 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13633 static int touch_dir[4] =
13635 MV_LEFT | MV_RIGHT,
13640 int center_element = Tile[x][y]; // should always be non-moving!
13643 for (i = 0; i < NUM_DIRECTIONS; i++)
13645 int xx = x + xy[i].x;
13646 int yy = y + xy[i].y;
13647 int center_side = trigger_sides[i][0];
13648 int border_side = trigger_sides[i][1];
13649 int border_element;
13651 if (!IN_LEV_FIELD(xx, yy))
13654 if (IS_PLAYER(x, y)) // player found at center element
13656 struct PlayerInfo *player = PLAYERINFO(x, y);
13658 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13659 border_element = Tile[xx][yy]; // may be moving!
13660 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13661 border_element = Tile[xx][yy];
13662 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13663 border_element = MovingOrBlocked2Element(xx, yy);
13665 continue; // center and border element do not touch
13667 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13668 player->index_bit, border_side);
13669 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13670 CE_PLAYER_TOUCHES_X,
13671 player->index_bit, border_side);
13674 /* use player element that is initially defined in the level playfield,
13675 not the player element that corresponds to the runtime player number
13676 (example: a level that contains EL_PLAYER_3 as the only player would
13677 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13678 int player_element = PLAYERINFO(x, y)->initial_element;
13680 // as element "X" is the player here, check opposite (center) side
13681 CheckElementChangeBySide(xx, yy, border_element, player_element,
13682 CE_TOUCHING_X, center_side);
13685 else if (IS_PLAYER(xx, yy)) // player found at border element
13687 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13689 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13691 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13692 continue; // center and border element do not touch
13695 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13696 player->index_bit, center_side);
13697 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13698 CE_PLAYER_TOUCHES_X,
13699 player->index_bit, center_side);
13702 /* use player element that is initially defined in the level playfield,
13703 not the player element that corresponds to the runtime player number
13704 (example: a level that contains EL_PLAYER_3 as the only player would
13705 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13706 int player_element = PLAYERINFO(xx, yy)->initial_element;
13708 // as element "X" is the player here, check opposite (border) side
13709 CheckElementChangeBySide(x, y, center_element, player_element,
13710 CE_TOUCHING_X, border_side);
13718 void TestIfElementNextToCustomElement(int x, int y)
13720 struct XY *xy = xy_topdown;
13721 static int trigger_sides[4][2] =
13723 // center side border side
13724 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13725 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13726 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13727 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13729 int center_element = Tile[x][y]; // should always be non-moving!
13732 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13735 for (i = 0; i < NUM_DIRECTIONS; i++)
13737 int xx = x + xy[i].x;
13738 int yy = y + xy[i].y;
13739 int border_side = trigger_sides[i][1];
13740 int border_element;
13742 if (!IN_LEV_FIELD(xx, yy))
13745 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13746 continue; // center and border element not connected
13748 border_element = Tile[xx][yy];
13750 // check for change of center element (but change it only once)
13751 if (CheckElementChangeBySide(x, y, center_element, border_element,
13752 CE_NEXT_TO_X, border_side))
13757 void TestIfElementTouchesCustomElement(int x, int y)
13759 struct XY *xy = xy_topdown;
13760 static int trigger_sides[4][2] =
13762 // center side border side
13763 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13764 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13765 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13766 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13768 static int touch_dir[4] =
13770 MV_LEFT | MV_RIGHT,
13775 boolean change_center_element = FALSE;
13776 int center_element = Tile[x][y]; // should always be non-moving!
13777 int border_element_old[NUM_DIRECTIONS];
13780 for (i = 0; i < NUM_DIRECTIONS; i++)
13782 int xx = x + xy[i].x;
13783 int yy = y + xy[i].y;
13784 int border_element;
13786 border_element_old[i] = -1;
13788 if (!IN_LEV_FIELD(xx, yy))
13791 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13792 border_element = Tile[xx][yy]; // may be moving!
13793 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13794 border_element = Tile[xx][yy];
13795 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13796 border_element = MovingOrBlocked2Element(xx, yy);
13798 continue; // center and border element do not touch
13800 border_element_old[i] = border_element;
13803 for (i = 0; i < NUM_DIRECTIONS; i++)
13805 int xx = x + xy[i].x;
13806 int yy = y + xy[i].y;
13807 int center_side = trigger_sides[i][0];
13808 int border_element = border_element_old[i];
13810 if (border_element == -1)
13813 // check for change of border element
13814 CheckElementChangeBySide(xx, yy, border_element, center_element,
13815 CE_TOUCHING_X, center_side);
13817 // (center element cannot be player, so we don't have to check this here)
13820 for (i = 0; i < NUM_DIRECTIONS; i++)
13822 int xx = x + xy[i].x;
13823 int yy = y + xy[i].y;
13824 int border_side = trigger_sides[i][1];
13825 int border_element = border_element_old[i];
13827 if (border_element == -1)
13830 // check for change of center element (but change it only once)
13831 if (!change_center_element)
13832 change_center_element =
13833 CheckElementChangeBySide(x, y, center_element, border_element,
13834 CE_TOUCHING_X, border_side);
13836 if (IS_PLAYER(xx, yy))
13838 /* use player element that is initially defined in the level playfield,
13839 not the player element that corresponds to the runtime player number
13840 (example: a level that contains EL_PLAYER_3 as the only player would
13841 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13842 int player_element = PLAYERINFO(xx, yy)->initial_element;
13844 // as element "X" is the player here, check opposite (border) side
13845 CheckElementChangeBySide(x, y, center_element, player_element,
13846 CE_TOUCHING_X, border_side);
13851 void TestIfElementHitsCustomElement(int x, int y, int direction)
13853 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13854 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13855 int hitx = x + dx, hity = y + dy;
13856 int hitting_element = Tile[x][y];
13857 int touched_element;
13859 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13862 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13863 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13865 if (IN_LEV_FIELD(hitx, hity))
13867 int opposite_direction = MV_DIR_OPPOSITE(direction);
13868 int hitting_side = direction;
13869 int touched_side = opposite_direction;
13870 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13871 MovDir[hitx][hity] != direction ||
13872 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13878 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13879 CE_HITTING_X, touched_side);
13881 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13882 CE_HIT_BY_X, hitting_side);
13884 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13885 CE_HIT_BY_SOMETHING, opposite_direction);
13887 if (IS_PLAYER(hitx, hity))
13889 /* use player element that is initially defined in the level playfield,
13890 not the player element that corresponds to the runtime player number
13891 (example: a level that contains EL_PLAYER_3 as the only player would
13892 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13893 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13895 CheckElementChangeBySide(x, y, hitting_element, player_element,
13896 CE_HITTING_X, touched_side);
13901 // "hitting something" is also true when hitting the playfield border
13902 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13903 CE_HITTING_SOMETHING, direction);
13906 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13908 int i, kill_x = -1, kill_y = -1;
13910 int bad_element = -1;
13911 struct XY *test_xy = xy_topdown;
13912 static int test_dir[4] =
13920 for (i = 0; i < NUM_DIRECTIONS; i++)
13922 int test_x, test_y, test_move_dir, test_element;
13924 test_x = good_x + test_xy[i].x;
13925 test_y = good_y + test_xy[i].y;
13927 if (!IN_LEV_FIELD(test_x, test_y))
13931 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13933 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13935 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13936 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13938 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13939 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13943 bad_element = test_element;
13949 if (kill_x != -1 || kill_y != -1)
13951 if (IS_PLAYER(good_x, good_y))
13953 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13955 if (player->shield_deadly_time_left > 0 &&
13956 !IS_INDESTRUCTIBLE(bad_element))
13957 Bang(kill_x, kill_y);
13958 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13959 KillPlayer(player);
13962 Bang(good_x, good_y);
13966 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13968 int i, kill_x = -1, kill_y = -1;
13969 int bad_element = Tile[bad_x][bad_y];
13970 struct XY *test_xy = xy_topdown;
13971 static int touch_dir[4] =
13973 MV_LEFT | MV_RIGHT,
13978 static int test_dir[4] =
13986 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13989 for (i = 0; i < NUM_DIRECTIONS; i++)
13991 int test_x, test_y, test_move_dir, test_element;
13993 test_x = bad_x + test_xy[i].x;
13994 test_y = bad_y + test_xy[i].y;
13996 if (!IN_LEV_FIELD(test_x, test_y))
14000 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14002 test_element = Tile[test_x][test_y];
14004 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
14005 2nd case: DONT_TOUCH style bad thing does not move away from good thing
14007 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
14008 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
14010 // good thing is player or penguin that does not move away
14011 if (IS_PLAYER(test_x, test_y))
14013 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14015 if (bad_element == EL_ROBOT && player->is_moving)
14016 continue; // robot does not kill player if he is moving
14018 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
14020 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
14021 continue; // center and border element do not touch
14029 else if (test_element == EL_PENGUIN)
14039 if (kill_x != -1 || kill_y != -1)
14041 if (IS_PLAYER(kill_x, kill_y))
14043 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14045 if (player->shield_deadly_time_left > 0 &&
14046 !IS_INDESTRUCTIBLE(bad_element))
14047 Bang(bad_x, bad_y);
14048 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14049 KillPlayer(player);
14052 Bang(kill_x, kill_y);
14056 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
14058 int bad_element = Tile[bad_x][bad_y];
14059 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
14060 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
14061 int test_x = bad_x + dx, test_y = bad_y + dy;
14062 int test_move_dir, test_element;
14063 int kill_x = -1, kill_y = -1;
14065 if (!IN_LEV_FIELD(test_x, test_y))
14069 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14071 test_element = Tile[test_x][test_y];
14073 if (test_move_dir != bad_move_dir)
14075 // good thing can be player or penguin that does not move away
14076 if (IS_PLAYER(test_x, test_y))
14078 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14080 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
14081 player as being hit when he is moving towards the bad thing, because
14082 the "get hit by" condition would be lost after the player stops) */
14083 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
14084 return; // player moves away from bad thing
14089 else if (test_element == EL_PENGUIN)
14096 if (kill_x != -1 || kill_y != -1)
14098 if (IS_PLAYER(kill_x, kill_y))
14100 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14102 if (player->shield_deadly_time_left > 0 &&
14103 !IS_INDESTRUCTIBLE(bad_element))
14104 Bang(bad_x, bad_y);
14105 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14106 KillPlayer(player);
14109 Bang(kill_x, kill_y);
14113 void TestIfPlayerTouchesBadThing(int x, int y)
14115 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14118 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
14120 TestIfGoodThingHitsBadThing(x, y, move_dir);
14123 void TestIfBadThingTouchesPlayer(int x, int y)
14125 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14128 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
14130 TestIfBadThingHitsGoodThing(x, y, move_dir);
14133 void TestIfFriendTouchesBadThing(int x, int y)
14135 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14138 void TestIfBadThingTouchesFriend(int x, int y)
14140 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14143 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
14145 int i, kill_x = bad_x, kill_y = bad_y;
14146 struct XY *xy = xy_topdown;
14148 for (i = 0; i < NUM_DIRECTIONS; i++)
14152 x = bad_x + xy[i].x;
14153 y = bad_y + xy[i].y;
14154 if (!IN_LEV_FIELD(x, y))
14157 element = Tile[x][y];
14158 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14159 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14167 if (kill_x != bad_x || kill_y != bad_y)
14168 Bang(bad_x, bad_y);
14171 void KillPlayer(struct PlayerInfo *player)
14173 int jx = player->jx, jy = player->jy;
14175 if (!player->active)
14179 Debug("game:playing:KillPlayer",
14180 "0: killed == %d, active == %d, reanimated == %d",
14181 player->killed, player->active, player->reanimated);
14184 /* the following code was introduced to prevent an infinite loop when calling
14186 -> CheckTriggeredElementChangeExt()
14187 -> ExecuteCustomElementAction()
14189 -> (infinitely repeating the above sequence of function calls)
14190 which occurs when killing the player while having a CE with the setting
14191 "kill player X when explosion of <player X>"; the solution using a new
14192 field "player->killed" was chosen for backwards compatibility, although
14193 clever use of the fields "player->active" etc. would probably also work */
14195 if (player->killed)
14199 player->killed = TRUE;
14201 // remove accessible field at the player's position
14202 RemoveField(jx, jy);
14204 // deactivate shield (else Bang()/Explode() would not work right)
14205 player->shield_normal_time_left = 0;
14206 player->shield_deadly_time_left = 0;
14209 Debug("game:playing:KillPlayer",
14210 "1: killed == %d, active == %d, reanimated == %d",
14211 player->killed, player->active, player->reanimated);
14217 Debug("game:playing:KillPlayer",
14218 "2: killed == %d, active == %d, reanimated == %d",
14219 player->killed, player->active, player->reanimated);
14222 if (player->reanimated) // killed player may have been reanimated
14223 player->killed = player->reanimated = FALSE;
14225 BuryPlayer(player);
14228 static void KillPlayerUnlessEnemyProtected(int x, int y)
14230 if (!PLAYER_ENEMY_PROTECTED(x, y))
14231 KillPlayer(PLAYERINFO(x, y));
14234 static void KillPlayerUnlessExplosionProtected(int x, int y)
14236 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14237 KillPlayer(PLAYERINFO(x, y));
14240 void BuryPlayer(struct PlayerInfo *player)
14242 int jx = player->jx, jy = player->jy;
14244 if (!player->active)
14247 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14249 RemovePlayer(player);
14251 player->buried = TRUE;
14253 if (game.all_players_gone)
14254 game.GameOver = TRUE;
14257 void RemovePlayer(struct PlayerInfo *player)
14259 int jx = player->jx, jy = player->jy;
14260 int i, found = FALSE;
14262 player->present = FALSE;
14263 player->active = FALSE;
14265 // required for some CE actions (even if the player is not active anymore)
14266 player->MovPos = 0;
14268 if (!ExplodeField[jx][jy])
14269 StorePlayer[jx][jy] = 0;
14271 if (player->is_moving)
14272 TEST_DrawLevelField(player->last_jx, player->last_jy);
14274 for (i = 0; i < MAX_PLAYERS; i++)
14275 if (stored_player[i].active)
14280 game.all_players_gone = TRUE;
14281 game.GameOver = TRUE;
14284 game.exit_x = game.robot_wheel_x = jx;
14285 game.exit_y = game.robot_wheel_y = jy;
14288 void ExitPlayer(struct PlayerInfo *player)
14290 DrawPlayer(player); // needed here only to cleanup last field
14291 RemovePlayer(player);
14293 if (game.players_still_needed > 0)
14294 game.players_still_needed--;
14297 static void SetFieldForSnapping(int x, int y, int element, int direction,
14298 int player_index_bit)
14300 struct ElementInfo *ei = &element_info[element];
14301 int direction_bit = MV_DIR_TO_BIT(direction);
14302 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14303 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14304 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14306 Tile[x][y] = EL_ELEMENT_SNAPPING;
14307 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14308 MovDir[x][y] = direction;
14309 Store[x][y] = element;
14310 Store2[x][y] = player_index_bit;
14312 ResetGfxAnimation(x, y);
14314 GfxElement[x][y] = element;
14315 GfxAction[x][y] = action;
14316 GfxDir[x][y] = direction;
14317 GfxFrame[x][y] = -1;
14320 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14321 int player_index_bit)
14323 TestIfElementTouchesCustomElement(x, y); // for empty space
14325 if (level.finish_dig_collect)
14327 int dig_side = MV_DIR_OPPOSITE(direction);
14328 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14329 CE_PLAYER_COLLECTS_X);
14331 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14332 player_index_bit, dig_side);
14333 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14334 player_index_bit, dig_side);
14339 =============================================================================
14340 checkDiagonalPushing()
14341 -----------------------------------------------------------------------------
14342 check if diagonal input device direction results in pushing of object
14343 (by checking if the alternative direction is walkable, diggable, ...)
14344 =============================================================================
14347 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14348 int x, int y, int real_dx, int real_dy)
14350 int jx, jy, dx, dy, xx, yy;
14352 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14355 // diagonal direction: check alternative direction
14360 xx = jx + (dx == 0 ? real_dx : 0);
14361 yy = jy + (dy == 0 ? real_dy : 0);
14363 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14367 =============================================================================
14369 -----------------------------------------------------------------------------
14370 x, y: field next to player (non-diagonal) to try to dig to
14371 real_dx, real_dy: direction as read from input device (can be diagonal)
14372 =============================================================================
14375 static int DigField(struct PlayerInfo *player,
14376 int oldx, int oldy, int x, int y,
14377 int real_dx, int real_dy, int mode)
14379 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14380 boolean player_was_pushing = player->is_pushing;
14381 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14382 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14383 int jx = oldx, jy = oldy;
14384 int dx = x - jx, dy = y - jy;
14385 int nextx = x + dx, nexty = y + dy;
14386 int move_direction = (dx == -1 ? MV_LEFT :
14387 dx == +1 ? MV_RIGHT :
14389 dy == +1 ? MV_DOWN : MV_NONE);
14390 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14391 int dig_side = MV_DIR_OPPOSITE(move_direction);
14392 int old_element = Tile[jx][jy];
14393 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14396 if (is_player) // function can also be called by EL_PENGUIN
14398 if (player->MovPos == 0)
14400 player->is_digging = FALSE;
14401 player->is_collecting = FALSE;
14404 if (player->MovPos == 0) // last pushing move finished
14405 player->is_pushing = FALSE;
14407 if (mode == DF_NO_PUSH) // player just stopped pushing
14409 player->is_switching = FALSE;
14410 player->push_delay = -1;
14412 return MP_NO_ACTION;
14415 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14416 old_element = Back[jx][jy];
14418 // in case of element dropped at player position, check background
14419 else if (Back[jx][jy] != EL_EMPTY &&
14420 game.engine_version >= VERSION_IDENT(2,2,0,0))
14421 old_element = Back[jx][jy];
14423 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14424 return MP_NO_ACTION; // field has no opening in this direction
14426 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction))
14427 return MP_NO_ACTION; // field has no opening in this direction
14429 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14433 Tile[jx][jy] = player->artwork_element;
14434 InitMovingField(jx, jy, MV_DOWN);
14435 Store[jx][jy] = EL_ACID;
14436 ContinueMoving(jx, jy);
14437 BuryPlayer(player);
14439 return MP_DONT_RUN_INTO;
14442 if (player_can_move && DONT_RUN_INTO(element))
14444 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14446 return MP_DONT_RUN_INTO;
14449 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14450 return MP_NO_ACTION;
14452 collect_count = element_info[element].collect_count_initial;
14454 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14455 return MP_NO_ACTION;
14457 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14458 player_can_move = player_can_move_or_snap;
14460 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14461 game.engine_version >= VERSION_IDENT(2,2,0,0))
14463 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14464 player->index_bit, dig_side);
14465 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14466 player->index_bit, dig_side);
14468 if (element == EL_DC_LANDMINE)
14471 if (Tile[x][y] != element) // field changed by snapping
14474 return MP_NO_ACTION;
14477 if (player->gravity && is_player && !player->is_auto_moving &&
14478 canFallDown(player) && move_direction != MV_DOWN &&
14479 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14480 return MP_NO_ACTION; // player cannot walk here due to gravity
14482 if (player_can_move &&
14483 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14485 int sound_element = SND_ELEMENT(element);
14486 int sound_action = ACTION_WALKING;
14488 if (IS_RND_GATE(element))
14490 if (!player->key[RND_GATE_NR(element)])
14491 return MP_NO_ACTION;
14493 else if (IS_RND_GATE_GRAY(element))
14495 if (!player->key[RND_GATE_GRAY_NR(element)])
14496 return MP_NO_ACTION;
14498 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14500 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14501 return MP_NO_ACTION;
14503 else if (element == EL_EXIT_OPEN ||
14504 element == EL_EM_EXIT_OPEN ||
14505 element == EL_EM_EXIT_OPENING ||
14506 element == EL_STEEL_EXIT_OPEN ||
14507 element == EL_EM_STEEL_EXIT_OPEN ||
14508 element == EL_EM_STEEL_EXIT_OPENING ||
14509 element == EL_SP_EXIT_OPEN ||
14510 element == EL_SP_EXIT_OPENING)
14512 sound_action = ACTION_PASSING; // player is passing exit
14514 else if (element == EL_EMPTY)
14516 sound_action = ACTION_MOVING; // nothing to walk on
14519 // play sound from background or player, whatever is available
14520 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14521 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14523 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14525 else if (player_can_move &&
14526 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14528 if (!ACCESS_FROM(element, opposite_direction))
14529 return MP_NO_ACTION; // field not accessible from this direction
14531 if (CAN_MOVE(element)) // only fixed elements can be passed!
14532 return MP_NO_ACTION;
14534 if (IS_EM_GATE(element))
14536 if (!player->key[EM_GATE_NR(element)])
14537 return MP_NO_ACTION;
14539 else if (IS_EM_GATE_GRAY(element))
14541 if (!player->key[EM_GATE_GRAY_NR(element)])
14542 return MP_NO_ACTION;
14544 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14546 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14547 return MP_NO_ACTION;
14549 else if (IS_EMC_GATE(element))
14551 if (!player->key[EMC_GATE_NR(element)])
14552 return MP_NO_ACTION;
14554 else if (IS_EMC_GATE_GRAY(element))
14556 if (!player->key[EMC_GATE_GRAY_NR(element)])
14557 return MP_NO_ACTION;
14559 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14561 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14562 return MP_NO_ACTION;
14564 else if (element == EL_DC_GATE_WHITE ||
14565 element == EL_DC_GATE_WHITE_GRAY ||
14566 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14568 if (player->num_white_keys == 0)
14569 return MP_NO_ACTION;
14571 player->num_white_keys--;
14573 else if (IS_SP_PORT(element))
14575 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14576 element == EL_SP_GRAVITY_PORT_RIGHT ||
14577 element == EL_SP_GRAVITY_PORT_UP ||
14578 element == EL_SP_GRAVITY_PORT_DOWN)
14579 player->gravity = !player->gravity;
14580 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14581 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14582 element == EL_SP_GRAVITY_ON_PORT_UP ||
14583 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14584 player->gravity = TRUE;
14585 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14586 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14587 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14588 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14589 player->gravity = FALSE;
14592 // automatically move to the next field with double speed
14593 player->programmed_action = move_direction;
14595 if (player->move_delay_reset_counter == 0)
14597 player->move_delay_reset_counter = 2; // two double speed steps
14599 DOUBLE_PLAYER_SPEED(player);
14602 PlayLevelSoundAction(x, y, ACTION_PASSING);
14604 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14608 if (mode != DF_SNAP)
14610 GfxElement[x][y] = GFX_ELEMENT(element);
14611 player->is_digging = TRUE;
14614 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14616 // use old behaviour for old levels (digging)
14617 if (!level.finish_dig_collect)
14619 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14620 player->index_bit, dig_side);
14622 // if digging triggered player relocation, finish digging tile
14623 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14624 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14627 if (mode == DF_SNAP)
14629 if (level.block_snap_field)
14630 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14632 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14634 // use old behaviour for old levels (snapping)
14635 if (!level.finish_dig_collect)
14636 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14637 player->index_bit, dig_side);
14640 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14644 if (is_player && mode != DF_SNAP)
14646 GfxElement[x][y] = element;
14647 player->is_collecting = TRUE;
14650 if (element == EL_SPEED_PILL)
14652 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14654 else if (element == EL_EXTRA_TIME && level.time > 0)
14656 TimeLeft += level.extra_time;
14658 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14660 DisplayGameControlValues();
14662 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14664 int shield_time = (element == EL_SHIELD_DEADLY ?
14665 level.shield_deadly_time :
14666 level.shield_normal_time);
14668 player->shield_normal_time_left += shield_time;
14669 if (element == EL_SHIELD_DEADLY)
14670 player->shield_deadly_time_left += shield_time;
14672 else if (element == EL_DYNAMITE ||
14673 element == EL_EM_DYNAMITE ||
14674 element == EL_SP_DISK_RED)
14676 if (player->inventory_size < MAX_INVENTORY_SIZE)
14677 player->inventory_element[player->inventory_size++] = element;
14679 DrawGameDoorValues();
14681 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14683 player->dynabomb_count++;
14684 player->dynabombs_left++;
14686 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14688 player->dynabomb_size++;
14690 else if (element == EL_DYNABOMB_INCREASE_POWER)
14692 player->dynabomb_xl = TRUE;
14694 else if (IS_KEY(element))
14696 player->key[KEY_NR(element)] = TRUE;
14698 DrawGameDoorValues();
14700 else if (element == EL_DC_KEY_WHITE)
14702 player->num_white_keys++;
14704 // display white keys?
14705 // DrawGameDoorValues();
14707 else if (IS_ENVELOPE(element))
14709 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14711 if (!wait_for_snapping)
14712 player->show_envelope = element;
14714 else if (element == EL_EMC_LENSES)
14716 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14718 RedrawAllInvisibleElementsForLenses();
14720 else if (element == EL_EMC_MAGNIFIER)
14722 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14724 RedrawAllInvisibleElementsForMagnifier();
14726 else if (IS_DROPPABLE(element) ||
14727 IS_THROWABLE(element)) // can be collected and dropped
14731 if (collect_count == 0)
14732 player->inventory_infinite_element = element;
14734 for (i = 0; i < collect_count; i++)
14735 if (player->inventory_size < MAX_INVENTORY_SIZE)
14736 player->inventory_element[player->inventory_size++] = element;
14738 DrawGameDoorValues();
14740 else if (collect_count > 0)
14742 game.gems_still_needed -= collect_count;
14743 if (game.gems_still_needed < 0)
14744 game.gems_still_needed = 0;
14746 game.snapshot.collected_item = TRUE;
14748 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14750 DisplayGameControlValues();
14753 RaiseScoreElement(element);
14754 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14756 // use old behaviour for old levels (collecting)
14757 if (!level.finish_dig_collect && is_player)
14759 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14760 player->index_bit, dig_side);
14762 // if collecting triggered player relocation, finish collecting tile
14763 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14764 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14767 if (mode == DF_SNAP)
14769 if (level.block_snap_field)
14770 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14772 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14774 // use old behaviour for old levels (snapping)
14775 if (!level.finish_dig_collect)
14776 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14777 player->index_bit, dig_side);
14780 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14782 if (mode == DF_SNAP && element != EL_BD_ROCK)
14783 return MP_NO_ACTION;
14785 if (CAN_FALL(element) && dy)
14786 return MP_NO_ACTION;
14788 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14789 !(element == EL_SPRING && level.use_spring_bug))
14790 return MP_NO_ACTION;
14792 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14793 ((move_direction & MV_VERTICAL &&
14794 ((element_info[element].move_pattern & MV_LEFT &&
14795 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14796 (element_info[element].move_pattern & MV_RIGHT &&
14797 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14798 (move_direction & MV_HORIZONTAL &&
14799 ((element_info[element].move_pattern & MV_UP &&
14800 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14801 (element_info[element].move_pattern & MV_DOWN &&
14802 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14803 return MP_NO_ACTION;
14805 // do not push elements already moving away faster than player
14806 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14807 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14808 return MP_NO_ACTION;
14810 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14812 if (player->push_delay_value == -1 || !player_was_pushing)
14813 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14815 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14817 if (player->push_delay_value == -1)
14818 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14820 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14822 if (!player->is_pushing)
14823 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14826 player->is_pushing = TRUE;
14827 player->is_active = TRUE;
14829 if (!(IN_LEV_FIELD(nextx, nexty) &&
14830 (IS_FREE(nextx, nexty) ||
14831 (IS_SB_ELEMENT(element) &&
14832 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14833 (IS_CUSTOM_ELEMENT(element) &&
14834 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14835 return MP_NO_ACTION;
14837 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14838 return MP_NO_ACTION;
14840 if (player->push_delay == -1) // new pushing; restart delay
14841 player->push_delay = 0;
14843 if (player->push_delay < player->push_delay_value &&
14844 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14845 element != EL_SPRING && element != EL_BALLOON)
14847 // make sure that there is no move delay before next try to push
14848 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14849 player->move_delay = 0;
14851 return MP_NO_ACTION;
14854 if (IS_CUSTOM_ELEMENT(element) &&
14855 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14857 if (!DigFieldByCE(nextx, nexty, element))
14858 return MP_NO_ACTION;
14861 if (IS_SB_ELEMENT(element))
14863 boolean sokoban_task_solved = FALSE;
14865 if (element == EL_SOKOBAN_FIELD_FULL)
14867 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14869 IncrementSokobanFieldsNeeded();
14870 IncrementSokobanObjectsNeeded();
14873 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14875 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14877 DecrementSokobanFieldsNeeded();
14878 DecrementSokobanObjectsNeeded();
14880 // sokoban object was pushed from empty field to sokoban field
14881 if (Back[x][y] == EL_EMPTY)
14882 sokoban_task_solved = TRUE;
14885 Tile[x][y] = EL_SOKOBAN_OBJECT;
14887 if (Back[x][y] == Back[nextx][nexty])
14888 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14889 else if (Back[x][y] != 0)
14890 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14893 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14896 if (sokoban_task_solved &&
14897 game.sokoban_fields_still_needed == 0 &&
14898 game.sokoban_objects_still_needed == 0 &&
14899 level.auto_exit_sokoban)
14901 game.players_still_needed = 0;
14905 PlaySound(SND_GAME_SOKOBAN_SOLVING);
14909 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14911 InitMovingField(x, y, move_direction);
14912 GfxAction[x][y] = ACTION_PUSHING;
14914 if (mode == DF_SNAP)
14915 ContinueMoving(x, y);
14917 MovPos[x][y] = (dx != 0 ? dx : dy);
14919 Pushed[x][y] = TRUE;
14920 Pushed[nextx][nexty] = TRUE;
14922 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14923 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14925 player->push_delay_value = -1; // get new value later
14927 // check for element change _after_ element has been pushed
14928 if (game.use_change_when_pushing_bug)
14930 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14931 player->index_bit, dig_side);
14932 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14933 player->index_bit, dig_side);
14936 else if (IS_SWITCHABLE(element))
14938 if (PLAYER_SWITCHING(player, x, y))
14940 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14941 player->index_bit, dig_side);
14946 player->is_switching = TRUE;
14947 player->switch_x = x;
14948 player->switch_y = y;
14950 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14952 if (element == EL_ROBOT_WHEEL)
14954 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14956 game.robot_wheel_x = x;
14957 game.robot_wheel_y = y;
14958 game.robot_wheel_active = TRUE;
14960 TEST_DrawLevelField(x, y);
14962 else if (element == EL_SP_TERMINAL)
14966 SCAN_PLAYFIELD(xx, yy)
14968 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14972 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14974 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14976 ResetGfxAnimation(xx, yy);
14977 TEST_DrawLevelField(xx, yy);
14981 else if (IS_BELT_SWITCH(element))
14983 ToggleBeltSwitch(x, y);
14985 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14986 element == EL_SWITCHGATE_SWITCH_DOWN ||
14987 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14988 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14990 ToggleSwitchgateSwitch();
14992 else if (element == EL_LIGHT_SWITCH ||
14993 element == EL_LIGHT_SWITCH_ACTIVE)
14995 ToggleLightSwitch(x, y);
14997 else if (element == EL_TIMEGATE_SWITCH ||
14998 element == EL_DC_TIMEGATE_SWITCH)
15000 ActivateTimegateSwitch(x, y);
15002 else if (element == EL_BALLOON_SWITCH_LEFT ||
15003 element == EL_BALLOON_SWITCH_RIGHT ||
15004 element == EL_BALLOON_SWITCH_UP ||
15005 element == EL_BALLOON_SWITCH_DOWN ||
15006 element == EL_BALLOON_SWITCH_NONE ||
15007 element == EL_BALLOON_SWITCH_ANY)
15009 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
15010 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
15011 element == EL_BALLOON_SWITCH_UP ? MV_UP :
15012 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
15013 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
15016 else if (element == EL_LAMP)
15018 Tile[x][y] = EL_LAMP_ACTIVE;
15019 game.lights_still_needed--;
15021 ResetGfxAnimation(x, y);
15022 TEST_DrawLevelField(x, y);
15024 else if (element == EL_TIME_ORB_FULL)
15026 Tile[x][y] = EL_TIME_ORB_EMPTY;
15028 if (level.time > 0 || level.use_time_orb_bug)
15030 TimeLeft += level.time_orb_time;
15031 game.no_level_time_limit = FALSE;
15033 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
15035 DisplayGameControlValues();
15038 ResetGfxAnimation(x, y);
15039 TEST_DrawLevelField(x, y);
15041 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
15042 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15046 game.ball_active = !game.ball_active;
15048 SCAN_PLAYFIELD(xx, yy)
15050 int e = Tile[xx][yy];
15052 if (game.ball_active)
15054 if (e == EL_EMC_MAGIC_BALL)
15055 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
15056 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
15057 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
15061 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
15062 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
15063 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15064 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
15069 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15070 player->index_bit, dig_side);
15072 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15073 player->index_bit, dig_side);
15075 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15076 player->index_bit, dig_side);
15082 if (!PLAYER_SWITCHING(player, x, y))
15084 player->is_switching = TRUE;
15085 player->switch_x = x;
15086 player->switch_y = y;
15088 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
15089 player->index_bit, dig_side);
15090 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15091 player->index_bit, dig_side);
15093 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
15094 player->index_bit, dig_side);
15095 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15096 player->index_bit, dig_side);
15099 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
15100 player->index_bit, dig_side);
15101 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15102 player->index_bit, dig_side);
15104 return MP_NO_ACTION;
15107 player->push_delay = -1;
15109 if (is_player) // function can also be called by EL_PENGUIN
15111 if (Tile[x][y] != element) // really digged/collected something
15113 player->is_collecting = !player->is_digging;
15114 player->is_active = TRUE;
15116 player->last_removed_element = element;
15123 static boolean DigFieldByCE(int x, int y, int digging_element)
15125 int element = Tile[x][y];
15127 if (!IS_FREE(x, y))
15129 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
15130 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
15133 // no element can dig solid indestructible elements
15134 if (IS_INDESTRUCTIBLE(element) &&
15135 !IS_DIGGABLE(element) &&
15136 !IS_COLLECTIBLE(element))
15139 if (AmoebaNr[x][y] &&
15140 (element == EL_AMOEBA_FULL ||
15141 element == EL_BD_AMOEBA ||
15142 element == EL_AMOEBA_GROWING))
15144 AmoebaCnt[AmoebaNr[x][y]]--;
15145 AmoebaCnt2[AmoebaNr[x][y]]--;
15148 if (IS_MOVING(x, y))
15149 RemoveMovingField(x, y);
15153 TEST_DrawLevelField(x, y);
15156 // if digged element was about to explode, prevent the explosion
15157 ExplodeField[x][y] = EX_TYPE_NONE;
15159 PlayLevelSoundAction(x, y, action);
15162 Store[x][y] = EL_EMPTY;
15164 // this makes it possible to leave the removed element again
15165 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15166 Store[x][y] = element;
15171 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15173 int jx = player->jx, jy = player->jy;
15174 int x = jx + dx, y = jy + dy;
15175 int snap_direction = (dx == -1 ? MV_LEFT :
15176 dx == +1 ? MV_RIGHT :
15178 dy == +1 ? MV_DOWN : MV_NONE);
15179 boolean can_continue_snapping = (level.continuous_snapping &&
15180 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15182 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15185 if (!player->active || !IN_LEV_FIELD(x, y))
15193 if (player->MovPos == 0)
15194 player->is_pushing = FALSE;
15196 player->is_snapping = FALSE;
15198 if (player->MovPos == 0)
15200 player->is_moving = FALSE;
15201 player->is_digging = FALSE;
15202 player->is_collecting = FALSE;
15208 // prevent snapping with already pressed snap key when not allowed
15209 if (player->is_snapping && !can_continue_snapping)
15212 player->MovDir = snap_direction;
15214 if (player->MovPos == 0)
15216 player->is_moving = FALSE;
15217 player->is_digging = FALSE;
15218 player->is_collecting = FALSE;
15221 player->is_dropping = FALSE;
15222 player->is_dropping_pressed = FALSE;
15223 player->drop_pressed_delay = 0;
15225 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15228 player->is_snapping = TRUE;
15229 player->is_active = TRUE;
15231 if (player->MovPos == 0)
15233 player->is_moving = FALSE;
15234 player->is_digging = FALSE;
15235 player->is_collecting = FALSE;
15238 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15239 TEST_DrawLevelField(player->last_jx, player->last_jy);
15241 TEST_DrawLevelField(x, y);
15246 static boolean DropElement(struct PlayerInfo *player)
15248 int old_element, new_element;
15249 int dropx = player->jx, dropy = player->jy;
15250 int drop_direction = player->MovDir;
15251 int drop_side = drop_direction;
15252 int drop_element = get_next_dropped_element(player);
15254 /* do not drop an element on top of another element; when holding drop key
15255 pressed without moving, dropped element must move away before the next
15256 element can be dropped (this is especially important if the next element
15257 is dynamite, which can be placed on background for historical reasons) */
15258 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15261 if (IS_THROWABLE(drop_element))
15263 dropx += GET_DX_FROM_DIR(drop_direction);
15264 dropy += GET_DY_FROM_DIR(drop_direction);
15266 if (!IN_LEV_FIELD(dropx, dropy))
15270 old_element = Tile[dropx][dropy]; // old element at dropping position
15271 new_element = drop_element; // default: no change when dropping
15273 // check if player is active, not moving and ready to drop
15274 if (!player->active || player->MovPos || player->drop_delay > 0)
15277 // check if player has anything that can be dropped
15278 if (new_element == EL_UNDEFINED)
15281 // only set if player has anything that can be dropped
15282 player->is_dropping_pressed = TRUE;
15284 // check if drop key was pressed long enough for EM style dynamite
15285 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15288 // check if anything can be dropped at the current position
15289 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15292 // collected custom elements can only be dropped on empty fields
15293 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15296 if (old_element != EL_EMPTY)
15297 Back[dropx][dropy] = old_element; // store old element on this field
15299 ResetGfxAnimation(dropx, dropy);
15300 ResetRandomAnimationValue(dropx, dropy);
15302 if (player->inventory_size > 0 ||
15303 player->inventory_infinite_element != EL_UNDEFINED)
15305 if (player->inventory_size > 0)
15307 player->inventory_size--;
15309 DrawGameDoorValues();
15311 if (new_element == EL_DYNAMITE)
15312 new_element = EL_DYNAMITE_ACTIVE;
15313 else if (new_element == EL_EM_DYNAMITE)
15314 new_element = EL_EM_DYNAMITE_ACTIVE;
15315 else if (new_element == EL_SP_DISK_RED)
15316 new_element = EL_SP_DISK_RED_ACTIVE;
15319 Tile[dropx][dropy] = new_element;
15321 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15322 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15323 el2img(Tile[dropx][dropy]), 0);
15325 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15327 // needed if previous element just changed to "empty" in the last frame
15328 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15330 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15331 player->index_bit, drop_side);
15332 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15334 player->index_bit, drop_side);
15336 TestIfElementTouchesCustomElement(dropx, dropy);
15338 else // player is dropping a dyna bomb
15340 player->dynabombs_left--;
15342 Tile[dropx][dropy] = new_element;
15344 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15345 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15346 el2img(Tile[dropx][dropy]), 0);
15348 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15351 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15352 InitField_WithBug1(dropx, dropy, FALSE);
15354 new_element = Tile[dropx][dropy]; // element might have changed
15356 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15357 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15359 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15360 MovDir[dropx][dropy] = drop_direction;
15362 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15364 // do not cause impact style collision by dropping elements that can fall
15365 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15368 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15369 player->is_dropping = TRUE;
15371 player->drop_pressed_delay = 0;
15372 player->is_dropping_pressed = FALSE;
15374 player->drop_x = dropx;
15375 player->drop_y = dropy;
15380 // ----------------------------------------------------------------------------
15381 // game sound playing functions
15382 // ----------------------------------------------------------------------------
15384 static int *loop_sound_frame = NULL;
15385 static int *loop_sound_volume = NULL;
15387 void InitPlayLevelSound(void)
15389 int num_sounds = getSoundListSize();
15391 checked_free(loop_sound_frame);
15392 checked_free(loop_sound_volume);
15394 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15395 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15398 static void PlayLevelSoundExt(int x, int y, int nr, boolean is_loop_sound)
15400 int sx = SCREENX(x), sy = SCREENY(y);
15401 int volume, stereo_position;
15402 int max_distance = 8;
15403 int type = (is_loop_sound ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15405 if ((!setup.sound_simple && !is_loop_sound) ||
15406 (!setup.sound_loops && is_loop_sound))
15409 if (!IN_LEV_FIELD(x, y) ||
15410 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15411 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15414 volume = SOUND_MAX_VOLUME;
15416 if (!IN_SCR_FIELD(sx, sy))
15418 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15419 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15421 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15424 stereo_position = (SOUND_MAX_LEFT +
15425 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15426 (SCR_FIELDX + 2 * max_distance));
15430 /* This assures that quieter loop sounds do not overwrite louder ones,
15431 while restarting sound volume comparison with each new game frame. */
15433 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15436 loop_sound_volume[nr] = volume;
15437 loop_sound_frame[nr] = FrameCounter;
15440 PlaySoundExt(nr, volume, stereo_position, type);
15443 static void PlayLevelSound(int x, int y, int nr)
15445 PlayLevelSoundExt(x, y, nr, IS_LOOP_SOUND(nr));
15448 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15450 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15451 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15452 y < LEVELY(BY1) ? LEVELY(BY1) :
15453 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15457 static void PlayLevelSoundAction(int x, int y, int action)
15459 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15462 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15464 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15466 if (sound_effect != SND_UNDEFINED)
15467 PlayLevelSound(x, y, sound_effect);
15470 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15473 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15475 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15476 PlayLevelSound(x, y, sound_effect);
15479 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15481 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15483 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15484 PlayLevelSound(x, y, sound_effect);
15487 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15489 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15491 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15492 StopSound(sound_effect);
15495 static int getLevelMusicNr(void)
15497 int level_pos = level_nr - leveldir_current->first_level;
15499 if (levelset.music[level_nr] != MUS_UNDEFINED)
15500 return levelset.music[level_nr]; // from config file
15502 return MAP_NOCONF_MUSIC(level_pos); // from music dir
15505 static void FadeLevelSounds(void)
15510 static void FadeLevelMusic(void)
15512 int music_nr = getLevelMusicNr();
15513 char *curr_music = getCurrentlyPlayingMusicFilename();
15514 char *next_music = getMusicInfoEntryFilename(music_nr);
15516 if (!strEqual(curr_music, next_music))
15520 void FadeLevelSoundsAndMusic(void)
15526 static void PlayLevelMusic(void)
15528 int music_nr = getLevelMusicNr();
15529 char *curr_music = getCurrentlyPlayingMusicFilename();
15530 char *next_music = getMusicInfoEntryFilename(music_nr);
15532 if (!strEqual(curr_music, next_music))
15533 PlayMusicLoop(music_nr);
15536 static int getSoundAction_BD(int sample)
15540 case GD_S_STONE_PUSHING:
15541 case GD_S_MEGA_STONE_PUSHING:
15542 case GD_S_FLYING_STONE_PUSHING:
15543 case GD_S_WAITING_STONE_PUSHING:
15544 case GD_S_CHASING_STONE_PUSHING:
15545 case GD_S_NUT_PUSHING:
15546 case GD_S_NITRO_PACK_PUSHING:
15547 case GD_S_BLADDER_PUSHING:
15548 case GD_S_BOX_PUSHING:
15549 return ACTION_PUSHING;
15551 case GD_S_STONE_FALLING:
15552 case GD_S_MEGA_STONE_FALLING:
15553 case GD_S_FLYING_STONE_FALLING:
15554 case GD_S_NUT_FALLING:
15555 case GD_S_DIRT_BALL_FALLING:
15556 case GD_S_DIRT_LOOSE_FALLING:
15557 case GD_S_NITRO_PACK_FALLING:
15558 case GD_S_FALLING_WALL_FALLING:
15559 return ACTION_FALLING;
15561 case GD_S_STONE_IMPACT:
15562 case GD_S_MEGA_STONE_IMPACT:
15563 case GD_S_FLYING_STONE_IMPACT:
15564 case GD_S_NUT_IMPACT:
15565 case GD_S_DIRT_BALL_IMPACT:
15566 case GD_S_DIRT_LOOSE_IMPACT:
15567 case GD_S_NITRO_PACK_IMPACT:
15568 case GD_S_FALLING_WALL_IMPACT:
15569 return ACTION_IMPACT;
15571 case GD_S_NUT_CRACKING:
15572 return ACTION_BREAKING;
15574 case GD_S_EXPANDING_WALL:
15575 case GD_S_WALL_REAPPEARING:
15578 case GD_S_ACID_SPREADING:
15579 return ACTION_GROWING;
15581 case GD_S_DIAMOND_COLLECTING:
15582 case GD_S_FLYING_DIAMOND_COLLECTING:
15583 case GD_S_SKELETON_COLLECTING:
15584 case GD_S_PNEUMATIC_COLLECTING:
15585 case GD_S_BOMB_COLLECTING:
15586 case GD_S_CLOCK_COLLECTING:
15587 case GD_S_SWEET_COLLECTING:
15588 case GD_S_KEY_COLLECTING:
15589 case GD_S_DIAMOND_KEY_COLLECTING:
15590 return ACTION_COLLECTING;
15592 case GD_S_BOMB_PLACING:
15593 case GD_S_REPLICATOR:
15594 return ACTION_DROPPING;
15596 case GD_S_BLADDER_MOVING:
15597 return ACTION_MOVING;
15599 case GD_S_BLADDER_SPENDER:
15600 case GD_S_BLADDER_CONVERTING:
15601 case GD_S_GRAVITY_CHANGING:
15602 return ACTION_CHANGING;
15604 case GD_S_BITER_EATING:
15605 return ACTION_EATING;
15607 case GD_S_DOOR_OPENING:
15608 case GD_S_CRACKING:
15609 return ACTION_OPENING;
15611 case GD_S_DIRT_WALKING:
15612 return ACTION_DIGGING;
15614 case GD_S_EMPTY_WALKING:
15615 return ACTION_WALKING;
15617 case GD_S_SWITCH_BITER:
15618 case GD_S_SWITCH_CREATURES:
15619 case GD_S_SWITCH_GRAVITY:
15620 case GD_S_SWITCH_EXPANDING:
15621 case GD_S_SWITCH_CONVEYOR:
15622 case GD_S_SWITCH_REPLICATOR:
15623 case GD_S_STIRRING:
15624 return ACTION_ACTIVATING;
15626 case GD_S_TELEPORTER:
15627 return ACTION_PASSING;
15629 case GD_S_EXPLODING:
15630 case GD_S_BOMB_EXPLODING:
15631 case GD_S_GHOST_EXPLODING:
15632 case GD_S_VOODOO_EXPLODING:
15633 case GD_S_NITRO_PACK_EXPLODING:
15634 return ACTION_EXPLODING;
15636 case GD_S_COVERING:
15638 case GD_S_MAGIC_WALL:
15639 case GD_S_PNEUMATIC_HAMMER:
15641 return ACTION_ACTIVE;
15643 case GD_S_DIAMOND_FALLING_RANDOM:
15644 case GD_S_DIAMOND_FALLING_1:
15645 case GD_S_DIAMOND_FALLING_2:
15646 case GD_S_DIAMOND_FALLING_3:
15647 case GD_S_DIAMOND_FALLING_4:
15648 case GD_S_DIAMOND_FALLING_5:
15649 case GD_S_DIAMOND_FALLING_6:
15650 case GD_S_DIAMOND_FALLING_7:
15651 case GD_S_DIAMOND_FALLING_8:
15652 case GD_S_DIAMOND_IMPACT_RANDOM:
15653 case GD_S_DIAMOND_IMPACT_1:
15654 case GD_S_DIAMOND_IMPACT_2:
15655 case GD_S_DIAMOND_IMPACT_3:
15656 case GD_S_DIAMOND_IMPACT_4:
15657 case GD_S_DIAMOND_IMPACT_5:
15658 case GD_S_DIAMOND_IMPACT_6:
15659 case GD_S_DIAMOND_IMPACT_7:
15660 case GD_S_DIAMOND_IMPACT_8:
15661 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15662 case GD_S_FLYING_DIAMOND_FALLING_1:
15663 case GD_S_FLYING_DIAMOND_FALLING_2:
15664 case GD_S_FLYING_DIAMOND_FALLING_3:
15665 case GD_S_FLYING_DIAMOND_FALLING_4:
15666 case GD_S_FLYING_DIAMOND_FALLING_5:
15667 case GD_S_FLYING_DIAMOND_FALLING_6:
15668 case GD_S_FLYING_DIAMOND_FALLING_7:
15669 case GD_S_FLYING_DIAMOND_FALLING_8:
15670 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15671 case GD_S_FLYING_DIAMOND_IMPACT_1:
15672 case GD_S_FLYING_DIAMOND_IMPACT_2:
15673 case GD_S_FLYING_DIAMOND_IMPACT_3:
15674 case GD_S_FLYING_DIAMOND_IMPACT_4:
15675 case GD_S_FLYING_DIAMOND_IMPACT_5:
15676 case GD_S_FLYING_DIAMOND_IMPACT_6:
15677 case GD_S_FLYING_DIAMOND_IMPACT_7:
15678 case GD_S_FLYING_DIAMOND_IMPACT_8:
15679 case GD_S_TIMEOUT_0:
15680 case GD_S_TIMEOUT_1:
15681 case GD_S_TIMEOUT_2:
15682 case GD_S_TIMEOUT_3:
15683 case GD_S_TIMEOUT_4:
15684 case GD_S_TIMEOUT_5:
15685 case GD_S_TIMEOUT_6:
15686 case GD_S_TIMEOUT_7:
15687 case GD_S_TIMEOUT_8:
15688 case GD_S_TIMEOUT_9:
15689 case GD_S_TIMEOUT_10:
15690 case GD_S_BONUS_LIFE:
15691 // trigger special post-processing (and force sound to be non-looping)
15692 return ACTION_OTHER;
15694 case GD_S_AMOEBA_MAGIC:
15695 case GD_S_FINISHED:
15696 // trigger special post-processing (and force sound to be looping)
15697 return ACTION_DEFAULT;
15700 return ACTION_DEFAULT;
15704 static int getSoundEffect_BD(int element_bd, int sample)
15706 int sound_action = getSoundAction_BD(sample);
15707 int sound_effect = element_info[SND_ELEMENT(element_bd)].sound[sound_action];
15711 if (sound_action != ACTION_OTHER &&
15712 sound_action != ACTION_DEFAULT)
15713 return sound_effect;
15715 // special post-processing for some sounds
15718 case GD_S_DIAMOND_FALLING_RANDOM:
15719 case GD_S_DIAMOND_FALLING_1:
15720 case GD_S_DIAMOND_FALLING_2:
15721 case GD_S_DIAMOND_FALLING_3:
15722 case GD_S_DIAMOND_FALLING_4:
15723 case GD_S_DIAMOND_FALLING_5:
15724 case GD_S_DIAMOND_FALLING_6:
15725 case GD_S_DIAMOND_FALLING_7:
15726 case GD_S_DIAMOND_FALLING_8:
15727 nr = (sample == GD_S_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15728 sample - GD_S_DIAMOND_FALLING_1);
15729 sound_effect = SND_BD_DIAMOND_FALLING_RANDOM_1 + nr;
15731 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15732 sound_effect = SND_BD_DIAMOND_FALLING;
15735 case GD_S_DIAMOND_IMPACT_RANDOM:
15736 case GD_S_DIAMOND_IMPACT_1:
15737 case GD_S_DIAMOND_IMPACT_2:
15738 case GD_S_DIAMOND_IMPACT_3:
15739 case GD_S_DIAMOND_IMPACT_4:
15740 case GD_S_DIAMOND_IMPACT_5:
15741 case GD_S_DIAMOND_IMPACT_6:
15742 case GD_S_DIAMOND_IMPACT_7:
15743 case GD_S_DIAMOND_IMPACT_8:
15744 nr = (sample == GD_S_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15745 sample - GD_S_DIAMOND_IMPACT_1);
15746 sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr;
15748 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15749 sound_effect = SND_BD_DIAMOND_IMPACT;
15752 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15753 case GD_S_FLYING_DIAMOND_FALLING_1:
15754 case GD_S_FLYING_DIAMOND_FALLING_2:
15755 case GD_S_FLYING_DIAMOND_FALLING_3:
15756 case GD_S_FLYING_DIAMOND_FALLING_4:
15757 case GD_S_FLYING_DIAMOND_FALLING_5:
15758 case GD_S_FLYING_DIAMOND_FALLING_6:
15759 case GD_S_FLYING_DIAMOND_FALLING_7:
15760 case GD_S_FLYING_DIAMOND_FALLING_8:
15761 nr = (sample == GD_S_FLYING_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15762 sample - GD_S_FLYING_DIAMOND_FALLING_1);
15763 sound_effect = SND_BD_FLYING_DIAMOND_FALLING_RANDOM_1 + nr;
15765 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15766 sound_effect = SND_BD_FLYING_DIAMOND_FALLING;
15769 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15770 case GD_S_FLYING_DIAMOND_IMPACT_1:
15771 case GD_S_FLYING_DIAMOND_IMPACT_2:
15772 case GD_S_FLYING_DIAMOND_IMPACT_3:
15773 case GD_S_FLYING_DIAMOND_IMPACT_4:
15774 case GD_S_FLYING_DIAMOND_IMPACT_5:
15775 case GD_S_FLYING_DIAMOND_IMPACT_6:
15776 case GD_S_FLYING_DIAMOND_IMPACT_7:
15777 case GD_S_FLYING_DIAMOND_IMPACT_8:
15778 nr = (sample == GD_S_FLYING_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15779 sample - GD_S_FLYING_DIAMOND_IMPACT_1);
15780 sound_effect = SND_BD_FLYING_DIAMOND_IMPACT_RANDOM_1 + nr;
15782 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15783 sound_effect = SND_BD_FLYING_DIAMOND_IMPACT;
15786 case GD_S_TIMEOUT_0:
15787 case GD_S_TIMEOUT_1:
15788 case GD_S_TIMEOUT_2:
15789 case GD_S_TIMEOUT_3:
15790 case GD_S_TIMEOUT_4:
15791 case GD_S_TIMEOUT_5:
15792 case GD_S_TIMEOUT_6:
15793 case GD_S_TIMEOUT_7:
15794 case GD_S_TIMEOUT_8:
15795 case GD_S_TIMEOUT_9:
15796 case GD_S_TIMEOUT_10:
15797 nr = sample - GD_S_TIMEOUT_0;
15798 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME_0 + nr;
15800 if (getSoundInfoEntryFilename(sound_effect) == NULL && sample != GD_S_TIMEOUT_0)
15801 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME;
15804 case GD_S_BONUS_LIFE:
15805 sound_effect = SND_GAME_HEALTH_BONUS;
15808 case GD_S_AMOEBA_MAGIC:
15809 sound_effect = SND_BD_AMOEBA_OTHER;
15812 case GD_S_FINISHED:
15813 sound_effect = SND_GAME_LEVELTIME_BONUS;
15817 sound_effect = SND_UNDEFINED;
15821 return sound_effect;
15824 void PlayLevelSound_BD(int xx, int yy, int element_bd, int sample)
15826 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15827 int sound_effect = getSoundEffect_BD(element, sample);
15828 int sound_action = getSoundAction_BD(sample);
15829 boolean is_loop_sound = IS_LOOP_SOUND(sound_effect);
15831 int x = xx - offset;
15832 int y = yy - offset;
15834 // some sound actions are always looping in native BD game engine
15835 if (sound_action == ACTION_DEFAULT)
15836 is_loop_sound = TRUE;
15838 // some sound actions are always non-looping in native BD game engine
15839 if (sound_action == ACTION_FALLING ||
15840 sound_action == ACTION_MOVING ||
15841 sound_action == ACTION_OTHER)
15842 is_loop_sound = FALSE;
15844 if (sound_effect != SND_UNDEFINED)
15845 PlayLevelSoundExt(x, y, sound_effect, is_loop_sound);
15848 void StopSound_BD(int element_bd, int sample)
15850 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15851 int sound_effect = getSoundEffect_BD(element, sample);
15853 if (sound_effect != SND_UNDEFINED)
15854 StopSound(sound_effect);
15857 boolean isSoundPlaying_BD(int element_bd, int sample)
15859 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15860 int sound_effect = getSoundEffect_BD(element, sample);
15862 if (sound_effect != SND_UNDEFINED)
15863 return isSoundPlaying(sound_effect);
15868 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15870 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15872 int x = xx - offset;
15873 int y = yy - offset;
15878 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15882 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15886 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15890 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15894 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15898 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15902 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15905 case SOUND_android_clone:
15906 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15909 case SOUND_android_move:
15910 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15914 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15918 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15922 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15925 case SOUND_eater_eat:
15926 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15930 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15933 case SOUND_collect:
15934 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15937 case SOUND_diamond:
15938 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15942 // !!! CHECK THIS !!!
15944 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15946 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15950 case SOUND_wonderfall:
15951 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15955 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15959 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15963 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15967 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15971 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15975 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15979 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15983 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15986 case SOUND_exit_open:
15987 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15990 case SOUND_exit_leave:
15991 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15994 case SOUND_dynamite:
15995 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15999 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16003 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
16007 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16011 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
16015 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
16019 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
16023 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
16028 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
16030 int element = map_element_SP_to_RND(element_sp);
16031 int action = map_action_SP_to_RND(action_sp);
16032 int offset = (setup.sp_show_border_elements ? 0 : 1);
16033 int x = xx - offset;
16034 int y = yy - offset;
16036 PlayLevelSoundElementAction(x, y, element, action);
16039 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
16041 int element = map_element_MM_to_RND(element_mm);
16042 int action = map_action_MM_to_RND(action_mm);
16044 int x = xx - offset;
16045 int y = yy - offset;
16047 if (!IS_MM_ELEMENT(element))
16048 element = EL_MM_DEFAULT;
16050 PlayLevelSoundElementAction(x, y, element, action);
16053 void PlaySound_MM(int sound_mm)
16055 int sound = map_sound_MM_to_RND(sound_mm);
16057 if (sound == SND_UNDEFINED)
16063 void PlaySoundLoop_MM(int sound_mm)
16065 int sound = map_sound_MM_to_RND(sound_mm);
16067 if (sound == SND_UNDEFINED)
16070 PlaySoundLoop(sound);
16073 void StopSound_MM(int sound_mm)
16075 int sound = map_sound_MM_to_RND(sound_mm);
16077 if (sound == SND_UNDEFINED)
16083 void RaiseScore(int value)
16085 game.score += value;
16087 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
16089 DisplayGameControlValues();
16092 void RaiseScoreElement(int element)
16097 case EL_BD_DIAMOND:
16098 case EL_EMERALD_YELLOW:
16099 case EL_EMERALD_RED:
16100 case EL_EMERALD_PURPLE:
16101 case EL_SP_INFOTRON:
16102 RaiseScore(level.score[SC_EMERALD]);
16105 RaiseScore(level.score[SC_DIAMOND]);
16108 RaiseScore(level.score[SC_CRYSTAL]);
16111 RaiseScore(level.score[SC_PEARL]);
16114 case EL_BD_BUTTERFLY:
16115 case EL_SP_ELECTRON:
16116 RaiseScore(level.score[SC_BUG]);
16119 case EL_BD_FIREFLY:
16120 case EL_SP_SNIKSNAK:
16121 RaiseScore(level.score[SC_SPACESHIP]);
16124 case EL_DARK_YAMYAM:
16125 RaiseScore(level.score[SC_YAMYAM]);
16128 RaiseScore(level.score[SC_ROBOT]);
16131 RaiseScore(level.score[SC_PACMAN]);
16134 RaiseScore(level.score[SC_NUT]);
16137 case EL_EM_DYNAMITE:
16138 case EL_SP_DISK_RED:
16139 case EL_DYNABOMB_INCREASE_NUMBER:
16140 case EL_DYNABOMB_INCREASE_SIZE:
16141 case EL_DYNABOMB_INCREASE_POWER:
16142 RaiseScore(level.score[SC_DYNAMITE]);
16144 case EL_SHIELD_NORMAL:
16145 case EL_SHIELD_DEADLY:
16146 RaiseScore(level.score[SC_SHIELD]);
16148 case EL_EXTRA_TIME:
16149 RaiseScore(level.extra_time_score);
16163 case EL_DC_KEY_WHITE:
16164 RaiseScore(level.score[SC_KEY]);
16167 RaiseScore(element_info[element].collect_score);
16172 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
16174 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
16178 // prevent short reactivation of overlay buttons while closing door
16179 SetOverlayActive(FALSE);
16180 UnmapGameButtons();
16182 // door may still be open due to skipped or envelope style request
16183 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
16186 if (network.enabled)
16188 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
16192 // when using BD game engine, cover screen before fading out
16193 if (!quick_quit && level.game_engine_type == GAME_ENGINE_TYPE_BD)
16194 game_bd.cover_screen = TRUE;
16197 FadeSkipNextFadeIn();
16199 SetGameStatus(GAME_MODE_MAIN);
16204 else // continue playing the game
16206 if (tape.playing && tape.deactivate_display)
16207 TapeDeactivateDisplayOff(TRUE);
16209 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
16211 if (tape.playing && tape.deactivate_display)
16212 TapeDeactivateDisplayOn();
16216 void RequestQuitGame(boolean escape_key_pressed)
16218 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
16219 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
16220 level_editor_test_game);
16221 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
16222 quick_quit || score_info_tape_play);
16224 RequestQuitGameExt(skip_request, quick_quit,
16225 "Do you really want to quit the game?");
16228 static char *getRestartGameMessage(void)
16230 boolean play_again = hasStartedNetworkGame();
16231 static char message[MAX_OUTPUT_LINESIZE];
16232 char *game_over_text = "Game over!";
16233 char *play_again_text = " Play it again?";
16235 if (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
16236 game_mm.game_over_message != NULL)
16237 game_over_text = game_mm.game_over_message;
16239 snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text,
16240 (play_again ? play_again_text : ""));
16245 static void RequestRestartGame(void)
16247 char *message = getRestartGameMessage();
16248 boolean has_started_game = hasStartedNetworkGame();
16249 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
16250 int door_state = DOOR_CLOSE_1;
16252 if (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game)
16254 CloseDoor(door_state);
16256 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16260 // if game was invoked from level editor, also close tape recorder door
16261 if (level_editor_test_game)
16262 door_state = DOOR_CLOSE_ALL;
16264 CloseDoor(door_state);
16266 SetGameStatus(GAME_MODE_MAIN);
16272 boolean CheckRestartGame(void)
16274 static int game_over_delay = 0;
16275 int game_over_delay_value = 50;
16276 boolean game_over = checkGameFailed();
16280 game_over_delay = game_over_delay_value;
16285 if (game_over_delay > 0)
16287 if (game_over_delay == game_over_delay_value / 2)
16288 PlaySound(SND_GAME_LOSING);
16295 // do not ask to play again if request dialog is already active
16296 if (game.request_active)
16299 // do not ask to play again if request dialog already handled
16300 if (game.RestartGameRequested)
16303 // do not ask to play again if game was never actually played
16304 if (!game.GamePlayed)
16307 // do not ask to play again if this was disabled in setup menu
16308 if (!setup.ask_on_game_over)
16311 game.RestartGameRequested = TRUE;
16313 RequestRestartGame();
16318 boolean checkGameRunning(void)
16320 if (game_status != GAME_MODE_PLAYING)
16323 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGameRunning_BD())
16329 boolean checkGamePlaying(void)
16331 if (game_status != GAME_MODE_PLAYING)
16334 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGamePlaying_BD())
16340 boolean checkGameSolved(void)
16342 // set for all game engines if level was solved
16343 return game.LevelSolved_GameEnd;
16346 boolean checkGameFailed(void)
16348 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
16349 return (game_bd.game_over && !game_bd.level_solved);
16350 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16351 return (game_em.game_over && !game_em.level_solved);
16352 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16353 return (game_sp.game_over && !game_sp.level_solved);
16354 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16355 return (game_mm.game_over && !game_mm.level_solved);
16356 else // GAME_ENGINE_TYPE_RND
16357 return (game.GameOver && !game.LevelSolved);
16360 boolean checkGameEnded(void)
16362 return (checkGameSolved() || checkGameFailed());
16366 // ----------------------------------------------------------------------------
16367 // random generator functions
16368 // ----------------------------------------------------------------------------
16370 unsigned int InitEngineRandom_RND(int seed)
16372 game.num_random_calls = 0;
16374 return InitEngineRandom(seed);
16377 unsigned int RND(int max)
16381 game.num_random_calls++;
16383 return GetEngineRandom(max);
16390 // ----------------------------------------------------------------------------
16391 // game engine snapshot handling functions
16392 // ----------------------------------------------------------------------------
16394 struct EngineSnapshotInfo
16396 // runtime values for custom element collect score
16397 int collect_score[NUM_CUSTOM_ELEMENTS];
16399 // runtime values for group element choice position
16400 int choice_pos[NUM_GROUP_ELEMENTS];
16402 // runtime values for belt position animations
16403 int belt_graphic[4][NUM_BELT_PARTS];
16404 int belt_anim_mode[4][NUM_BELT_PARTS];
16407 static struct EngineSnapshotInfo engine_snapshot_rnd;
16408 static char *snapshot_level_identifier = NULL;
16409 static int snapshot_level_nr = -1;
16411 static void SaveEngineSnapshotValues_RND(void)
16413 static int belt_base_active_element[4] =
16415 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
16416 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
16417 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
16418 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
16422 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16424 int element = EL_CUSTOM_START + i;
16426 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
16429 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16431 int element = EL_GROUP_START + i;
16433 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
16436 for (i = 0; i < 4; i++)
16438 for (j = 0; j < NUM_BELT_PARTS; j++)
16440 int element = belt_base_active_element[i] + j;
16441 int graphic = el2img(element);
16442 int anim_mode = graphic_info[graphic].anim_mode;
16444 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
16445 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
16450 static void LoadEngineSnapshotValues_RND(void)
16452 unsigned int num_random_calls = game.num_random_calls;
16455 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16457 int element = EL_CUSTOM_START + i;
16459 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
16462 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16464 int element = EL_GROUP_START + i;
16466 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
16469 for (i = 0; i < 4; i++)
16471 for (j = 0; j < NUM_BELT_PARTS; j++)
16473 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
16474 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
16476 graphic_info[graphic].anim_mode = anim_mode;
16480 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16482 InitRND(tape.random_seed);
16483 for (i = 0; i < num_random_calls; i++)
16487 if (game.num_random_calls != num_random_calls)
16489 Error("number of random calls out of sync");
16490 Error("number of random calls should be %d", num_random_calls);
16491 Error("number of random calls is %d", game.num_random_calls);
16493 Fail("this should not happen -- please debug");
16497 void FreeEngineSnapshotSingle(void)
16499 FreeSnapshotSingle();
16501 setString(&snapshot_level_identifier, NULL);
16502 snapshot_level_nr = -1;
16505 void FreeEngineSnapshotList(void)
16507 FreeSnapshotList();
16510 static ListNode *SaveEngineSnapshotBuffers(void)
16512 ListNode *buffers = NULL;
16514 // copy some special values to a structure better suited for the snapshot
16516 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16517 SaveEngineSnapshotValues_RND();
16518 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16519 SaveEngineSnapshotValues_EM();
16520 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16521 SaveEngineSnapshotValues_SP(&buffers);
16522 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16523 SaveEngineSnapshotValues_MM();
16525 // save values stored in special snapshot structure
16527 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16528 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
16529 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16530 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
16531 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16532 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
16533 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16534 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
16536 // save further RND engine values
16538 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
16539 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
16540 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
16542 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
16543 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
16544 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
16545 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16546 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTimeFrames));
16547 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16549 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16550 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16551 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16553 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16555 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16556 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16558 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16559 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16560 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16561 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16562 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16563 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16564 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16565 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16566 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16567 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16568 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16569 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16570 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16571 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16572 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16573 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16574 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16575 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16577 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16578 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16580 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16581 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16582 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16584 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16585 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16587 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16588 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16589 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16590 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16591 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16592 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16594 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16595 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16598 ListNode *node = engine_snapshot_list_rnd;
16601 while (node != NULL)
16603 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16608 Debug("game:playing:SaveEngineSnapshotBuffers",
16609 "size of engine snapshot: %d bytes", num_bytes);
16615 void SaveEngineSnapshotSingle(void)
16617 ListNode *buffers = SaveEngineSnapshotBuffers();
16619 // finally save all snapshot buffers to single snapshot
16620 SaveSnapshotSingle(buffers);
16622 // save level identification information
16623 setString(&snapshot_level_identifier, leveldir_current->identifier);
16624 snapshot_level_nr = level_nr;
16627 boolean CheckSaveEngineSnapshotToList(void)
16629 boolean save_snapshot =
16630 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16631 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16632 game.snapshot.changed_action) ||
16633 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16634 game.snapshot.collected_item));
16636 game.snapshot.changed_action = FALSE;
16637 game.snapshot.collected_item = FALSE;
16638 game.snapshot.save_snapshot = save_snapshot;
16640 return save_snapshot;
16643 void SaveEngineSnapshotToList(void)
16645 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16649 ListNode *buffers = SaveEngineSnapshotBuffers();
16651 // finally save all snapshot buffers to snapshot list
16652 SaveSnapshotToList(buffers);
16655 void SaveEngineSnapshotToListInitial(void)
16657 FreeEngineSnapshotList();
16659 SaveEngineSnapshotToList();
16662 static void LoadEngineSnapshotValues(void)
16664 // restore special values from snapshot structure
16666 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16667 LoadEngineSnapshotValues_RND();
16668 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16669 LoadEngineSnapshotValues_EM();
16670 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16671 LoadEngineSnapshotValues_SP();
16672 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16673 LoadEngineSnapshotValues_MM();
16676 void LoadEngineSnapshotSingle(void)
16678 LoadSnapshotSingle();
16680 LoadEngineSnapshotValues();
16683 static void LoadEngineSnapshot_Undo(int steps)
16685 LoadSnapshotFromList_Older(steps);
16687 LoadEngineSnapshotValues();
16690 static void LoadEngineSnapshot_Redo(int steps)
16692 LoadSnapshotFromList_Newer(steps);
16694 LoadEngineSnapshotValues();
16697 boolean CheckEngineSnapshotSingle(void)
16699 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16700 snapshot_level_nr == level_nr);
16703 boolean CheckEngineSnapshotList(void)
16705 return CheckSnapshotList();
16709 // ---------- new game button stuff -------------------------------------------
16716 boolean *setup_value;
16717 boolean allowed_on_tape;
16718 boolean is_touch_button;
16720 } gamebutton_info[NUM_GAME_BUTTONS] =
16723 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16724 GAME_CTRL_ID_STOP, NULL,
16725 TRUE, FALSE, "stop game"
16728 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16729 GAME_CTRL_ID_PAUSE, NULL,
16730 TRUE, FALSE, "pause game"
16733 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16734 GAME_CTRL_ID_PLAY, NULL,
16735 TRUE, FALSE, "play game"
16738 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16739 GAME_CTRL_ID_UNDO, NULL,
16740 TRUE, FALSE, "undo step"
16743 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16744 GAME_CTRL_ID_REDO, NULL,
16745 TRUE, FALSE, "redo step"
16748 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16749 GAME_CTRL_ID_SAVE, NULL,
16750 TRUE, FALSE, "save game"
16753 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16754 GAME_CTRL_ID_PAUSE2, NULL,
16755 TRUE, FALSE, "pause game"
16758 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16759 GAME_CTRL_ID_LOAD, NULL,
16760 TRUE, FALSE, "load game"
16763 IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart,
16764 GAME_CTRL_ID_RESTART, NULL,
16765 TRUE, FALSE, "restart game"
16768 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16769 GAME_CTRL_ID_PANEL_STOP, NULL,
16770 FALSE, FALSE, "stop game"
16773 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16774 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16775 FALSE, FALSE, "pause game"
16778 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16779 GAME_CTRL_ID_PANEL_PLAY, NULL,
16780 FALSE, FALSE, "play game"
16783 IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart,
16784 GAME_CTRL_ID_PANEL_RESTART, NULL,
16785 FALSE, FALSE, "restart game"
16788 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16789 GAME_CTRL_ID_TOUCH_STOP, NULL,
16790 FALSE, TRUE, "stop game"
16793 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16794 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16795 FALSE, TRUE, "pause game"
16798 IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart,
16799 GAME_CTRL_ID_TOUCH_RESTART, NULL,
16800 FALSE, TRUE, "restart game"
16803 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16804 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16805 TRUE, FALSE, "background music on/off"
16808 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16809 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16810 TRUE, FALSE, "sound loops on/off"
16813 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16814 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16815 TRUE, FALSE, "normal sounds on/off"
16818 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16819 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16820 FALSE, FALSE, "background music on/off"
16823 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16824 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16825 FALSE, FALSE, "sound loops on/off"
16828 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16829 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16830 FALSE, FALSE, "normal sounds on/off"
16834 void CreateGameButtons(void)
16838 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16840 int graphic = gamebutton_info[i].graphic;
16841 struct GraphicInfo *gfx = &graphic_info[graphic];
16842 struct XY *pos = gamebutton_info[i].pos;
16843 struct GadgetInfo *gi;
16846 unsigned int event_mask;
16847 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16848 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16849 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16850 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16851 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16852 int gd_x = gfx->src_x;
16853 int gd_y = gfx->src_y;
16854 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16855 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16856 int gd_xa = gfx->src_x + gfx->active_xoffset;
16857 int gd_ya = gfx->src_y + gfx->active_yoffset;
16858 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16859 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16860 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16861 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16864 // do not use touch buttons if overlay touch buttons are disabled
16865 if (is_touch_button && !setup.touch.overlay_buttons)
16868 if (gfx->bitmap == NULL)
16870 game_gadget[id] = NULL;
16875 if (id == GAME_CTRL_ID_STOP ||
16876 id == GAME_CTRL_ID_PANEL_STOP ||
16877 id == GAME_CTRL_ID_TOUCH_STOP ||
16878 id == GAME_CTRL_ID_PLAY ||
16879 id == GAME_CTRL_ID_PANEL_PLAY ||
16880 id == GAME_CTRL_ID_SAVE ||
16881 id == GAME_CTRL_ID_LOAD ||
16882 id == GAME_CTRL_ID_RESTART ||
16883 id == GAME_CTRL_ID_PANEL_RESTART ||
16884 id == GAME_CTRL_ID_TOUCH_RESTART)
16886 button_type = GD_TYPE_NORMAL_BUTTON;
16888 event_mask = GD_EVENT_RELEASED;
16890 else if (id == GAME_CTRL_ID_UNDO ||
16891 id == GAME_CTRL_ID_REDO)
16893 button_type = GD_TYPE_NORMAL_BUTTON;
16895 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16899 button_type = GD_TYPE_CHECK_BUTTON;
16900 checked = (gamebutton_info[i].setup_value != NULL ?
16901 *gamebutton_info[i].setup_value : FALSE);
16902 event_mask = GD_EVENT_PRESSED;
16905 gi = CreateGadget(GDI_CUSTOM_ID, id,
16906 GDI_IMAGE_ID, graphic,
16907 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16910 GDI_WIDTH, gfx->width,
16911 GDI_HEIGHT, gfx->height,
16912 GDI_TYPE, button_type,
16913 GDI_STATE, GD_BUTTON_UNPRESSED,
16914 GDI_CHECKED, checked,
16915 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16916 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16917 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16918 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16919 GDI_DIRECT_DRAW, FALSE,
16920 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16921 GDI_EVENT_MASK, event_mask,
16922 GDI_CALLBACK_ACTION, HandleGameButtons,
16926 Fail("cannot create gadget");
16928 game_gadget[id] = gi;
16932 void FreeGameButtons(void)
16936 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16937 FreeGadget(game_gadget[i]);
16940 static void UnmapGameButtonsAtSamePosition(int id)
16944 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16946 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16947 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16948 UnmapGadget(game_gadget[i]);
16951 static void UnmapGameButtonsAtSamePosition_All(void)
16953 if (setup.show_load_save_buttons)
16955 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16956 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16957 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16959 else if (setup.show_undo_redo_buttons)
16961 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16962 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16963 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16967 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16968 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16969 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16971 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16972 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16973 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16977 void MapLoadSaveButtons(void)
16979 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16980 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16982 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16983 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16986 void MapUndoRedoButtons(void)
16988 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16989 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16991 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16992 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16995 void ModifyPauseButtons(void)
16999 GAME_CTRL_ID_PAUSE,
17000 GAME_CTRL_ID_PAUSE2,
17001 GAME_CTRL_ID_PANEL_PAUSE,
17002 GAME_CTRL_ID_TOUCH_PAUSE,
17007 // do not redraw pause button on closed door (may happen when restarting game)
17008 if (!(GetDoorState() & DOOR_OPEN_1))
17011 for (i = 0; ids[i] > -1; i++)
17012 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
17015 static void MapGameButtonsExt(boolean on_tape)
17019 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17021 if ((i == GAME_CTRL_ID_UNDO ||
17022 i == GAME_CTRL_ID_REDO) &&
17023 game_status != GAME_MODE_PLAYING)
17026 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17027 MapGadget(game_gadget[i]);
17030 UnmapGameButtonsAtSamePosition_All();
17032 RedrawGameButtons();
17035 static void UnmapGameButtonsExt(boolean on_tape)
17039 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17040 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17041 UnmapGadget(game_gadget[i]);
17044 static void RedrawGameButtonsExt(boolean on_tape)
17048 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17049 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17050 RedrawGadget(game_gadget[i]);
17053 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
17058 gi->checked = state;
17061 static void RedrawSoundButtonGadget(int id)
17063 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
17064 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
17065 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
17066 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
17067 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
17068 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
17071 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
17072 RedrawGadget(game_gadget[id2]);
17075 void MapGameButtons(void)
17077 MapGameButtonsExt(FALSE);
17080 void UnmapGameButtons(void)
17082 UnmapGameButtonsExt(FALSE);
17085 void RedrawGameButtons(void)
17087 RedrawGameButtonsExt(FALSE);
17090 void MapGameButtonsOnTape(void)
17092 MapGameButtonsExt(TRUE);
17095 void UnmapGameButtonsOnTape(void)
17097 UnmapGameButtonsExt(TRUE);
17100 void RedrawGameButtonsOnTape(void)
17102 RedrawGameButtonsExt(TRUE);
17105 static void GameUndoRedoExt(void)
17107 ClearPlayerAction();
17109 tape.pausing = TRUE;
17112 UpdateAndDisplayGameControlValues();
17114 DrawCompleteVideoDisplay();
17115 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
17116 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
17117 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
17119 ModifyPauseButtons();
17124 static void GameUndo(int steps)
17126 if (!CheckEngineSnapshotList())
17129 int tape_property_bits = tape.property_bits;
17131 LoadEngineSnapshot_Undo(steps);
17133 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17138 static void GameRedo(int steps)
17140 if (!CheckEngineSnapshotList())
17143 int tape_property_bits = tape.property_bits;
17145 LoadEngineSnapshot_Redo(steps);
17147 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17152 static void HandleGameButtonsExt(int id, int button)
17154 static boolean game_undo_executed = FALSE;
17155 int steps = BUTTON_STEPSIZE(button);
17156 boolean handle_game_buttons =
17157 (game_status == GAME_MODE_PLAYING ||
17158 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
17160 if (!handle_game_buttons)
17165 case GAME_CTRL_ID_STOP:
17166 case GAME_CTRL_ID_PANEL_STOP:
17167 case GAME_CTRL_ID_TOUCH_STOP:
17172 case GAME_CTRL_ID_PAUSE:
17173 case GAME_CTRL_ID_PAUSE2:
17174 case GAME_CTRL_ID_PANEL_PAUSE:
17175 case GAME_CTRL_ID_TOUCH_PAUSE:
17176 if (network.enabled && game_status == GAME_MODE_PLAYING)
17179 SendToServer_ContinuePlaying();
17181 SendToServer_PausePlaying();
17184 TapeTogglePause(TAPE_TOGGLE_MANUAL);
17186 game_undo_executed = FALSE;
17190 case GAME_CTRL_ID_PLAY:
17191 case GAME_CTRL_ID_PANEL_PLAY:
17192 if (game_status == GAME_MODE_MAIN)
17194 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
17196 else if (tape.pausing)
17198 if (network.enabled)
17199 SendToServer_ContinuePlaying();
17201 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
17205 case GAME_CTRL_ID_UNDO:
17206 // Important: When using "save snapshot when collecting an item" mode,
17207 // load last (current) snapshot for first "undo" after pressing "pause"
17208 // (else the last-but-one snapshot would be loaded, because the snapshot
17209 // pointer already points to the last snapshot when pressing "pause",
17210 // which is fine for "every step/move" mode, but not for "every collect")
17211 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
17212 !game_undo_executed)
17215 game_undo_executed = TRUE;
17220 case GAME_CTRL_ID_REDO:
17224 case GAME_CTRL_ID_SAVE:
17228 case GAME_CTRL_ID_LOAD:
17232 case GAME_CTRL_ID_RESTART:
17233 case GAME_CTRL_ID_PANEL_RESTART:
17234 case GAME_CTRL_ID_TOUCH_RESTART:
17239 case SOUND_CTRL_ID_MUSIC:
17240 case SOUND_CTRL_ID_PANEL_MUSIC:
17241 if (setup.sound_music)
17243 setup.sound_music = FALSE;
17247 else if (audio.music_available)
17249 setup.sound = setup.sound_music = TRUE;
17251 SetAudioMode(setup.sound);
17253 if (game_status == GAME_MODE_PLAYING)
17257 RedrawSoundButtonGadget(id);
17261 case SOUND_CTRL_ID_LOOPS:
17262 case SOUND_CTRL_ID_PANEL_LOOPS:
17263 if (setup.sound_loops)
17264 setup.sound_loops = FALSE;
17265 else if (audio.loops_available)
17267 setup.sound = setup.sound_loops = TRUE;
17269 SetAudioMode(setup.sound);
17272 RedrawSoundButtonGadget(id);
17276 case SOUND_CTRL_ID_SIMPLE:
17277 case SOUND_CTRL_ID_PANEL_SIMPLE:
17278 if (setup.sound_simple)
17279 setup.sound_simple = FALSE;
17280 else if (audio.sound_available)
17282 setup.sound = setup.sound_simple = TRUE;
17284 SetAudioMode(setup.sound);
17287 RedrawSoundButtonGadget(id);
17296 static void HandleGameButtons(struct GadgetInfo *gi)
17298 HandleGameButtonsExt(gi->custom_id, gi->event.button);
17301 void HandleSoundButtonKeys(Key key)
17303 if (key == setup.shortcut.sound_simple)
17304 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
17305 else if (key == setup.shortcut.sound_loops)
17306 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
17307 else if (key == setup.shortcut.sound_music)
17308 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);