1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_INIT_PLAYER 1
27 #define DEBUG_PLAYER_ACTIONS 0
30 #define USE_NEW_AMOEBA_CODE FALSE
33 #define USE_QUICKSAND_BD_ROCK_BUGFIX 0
34 #define USE_QUICKSAND_IMPACT_BUGFIX 0
35 #define USE_DELAYED_GFX_REDRAW 0
36 #define USE_NEW_PLAYER_ASSIGNMENTS 1
38 #if USE_DELAYED_GFX_REDRAW
39 #define TEST_DrawLevelField(x, y) \
40 GfxRedraw[x][y] |= GFX_REDRAW_TILE
41 #define TEST_DrawLevelFieldCrumbled(x, y) \
42 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED
43 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
44 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS
45 #define TEST_DrawTwinkleOnField(x, y) \
46 GfxRedraw[x][y] |= GFX_REDRAW_TILE_TWINKLED
48 #define TEST_DrawLevelField(x, y) \
50 #define TEST_DrawLevelFieldCrumbled(x, y) \
51 DrawLevelFieldCrumbled(x, y)
52 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
53 DrawLevelFieldCrumbledNeighbours(x, y)
54 #define TEST_DrawTwinkleOnField(x, y) \
55 DrawTwinkleOnField(x, y)
65 #define MP_NO_ACTION 0
68 #define MP_DONT_RUN_INTO (MP_MOVING | MP_ACTION)
72 #define SCROLL_GO_ON 1
74 // for Bang()/Explode()
75 #define EX_PHASE_START 0
76 #define EX_TYPE_NONE 0
77 #define EX_TYPE_NORMAL (1 << 0)
78 #define EX_TYPE_CENTER (1 << 1)
79 #define EX_TYPE_BORDER (1 << 2)
80 #define EX_TYPE_CROSS (1 << 3)
81 #define EX_TYPE_DYNA (1 << 4)
82 #define EX_TYPE_SINGLE_TILE (EX_TYPE_CENTER | EX_TYPE_BORDER)
84 #define PANEL_OFF() (game.panel.active == FALSE)
85 #define PANEL_DEACTIVATED(p) ((p)->x < 0 || (p)->y < 0 || PANEL_OFF())
86 #define PANEL_XPOS(p) (DX + ALIGNED_TEXT_XPOS(p))
87 #define PANEL_YPOS(p) (DY + ALIGNED_TEXT_YPOS(p))
89 // game panel display and control definitions
90 #define GAME_PANEL_LEVEL_NUMBER 0
91 #define GAME_PANEL_GEMS 1
92 #define GAME_PANEL_GEMS_TOTAL 2
93 #define GAME_PANEL_GEMS_COLLECTED 3
94 #define GAME_PANEL_GEMS_SCORE 4
95 #define GAME_PANEL_INVENTORY_COUNT 5
96 #define GAME_PANEL_INVENTORY_FIRST_1 6
97 #define GAME_PANEL_INVENTORY_FIRST_2 7
98 #define GAME_PANEL_INVENTORY_FIRST_3 8
99 #define GAME_PANEL_INVENTORY_FIRST_4 9
100 #define GAME_PANEL_INVENTORY_FIRST_5 10
101 #define GAME_PANEL_INVENTORY_FIRST_6 11
102 #define GAME_PANEL_INVENTORY_FIRST_7 12
103 #define GAME_PANEL_INVENTORY_FIRST_8 13
104 #define GAME_PANEL_INVENTORY_LAST_1 14
105 #define GAME_PANEL_INVENTORY_LAST_2 15
106 #define GAME_PANEL_INVENTORY_LAST_3 16
107 #define GAME_PANEL_INVENTORY_LAST_4 17
108 #define GAME_PANEL_INVENTORY_LAST_5 18
109 #define GAME_PANEL_INVENTORY_LAST_6 19
110 #define GAME_PANEL_INVENTORY_LAST_7 20
111 #define GAME_PANEL_INVENTORY_LAST_8 21
112 #define GAME_PANEL_KEY_1 22
113 #define GAME_PANEL_KEY_2 23
114 #define GAME_PANEL_KEY_3 24
115 #define GAME_PANEL_KEY_4 25
116 #define GAME_PANEL_KEY_5 26
117 #define GAME_PANEL_KEY_6 27
118 #define GAME_PANEL_KEY_7 28
119 #define GAME_PANEL_KEY_8 29
120 #define GAME_PANEL_KEY_WHITE 30
121 #define GAME_PANEL_KEY_WHITE_COUNT 31
122 #define GAME_PANEL_SCORE 32
123 #define GAME_PANEL_HIGHSCORE 33
124 #define GAME_PANEL_TIME 34
125 #define GAME_PANEL_TIME_HH 35
126 #define GAME_PANEL_TIME_MM 36
127 #define GAME_PANEL_TIME_SS 37
128 #define GAME_PANEL_TIME_ANIM 38
129 #define GAME_PANEL_HEALTH 39
130 #define GAME_PANEL_HEALTH_ANIM 40
131 #define GAME_PANEL_FRAME 41
132 #define GAME_PANEL_SHIELD_NORMAL 42
133 #define GAME_PANEL_SHIELD_NORMAL_TIME 43
134 #define GAME_PANEL_SHIELD_DEADLY 44
135 #define GAME_PANEL_SHIELD_DEADLY_TIME 45
136 #define GAME_PANEL_EXIT 46
137 #define GAME_PANEL_EMC_MAGIC_BALL 47
138 #define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 48
139 #define GAME_PANEL_LIGHT_SWITCH 49
140 #define GAME_PANEL_LIGHT_SWITCH_TIME 50
141 #define GAME_PANEL_TIMEGATE_SWITCH 51
142 #define GAME_PANEL_TIMEGATE_SWITCH_TIME 52
143 #define GAME_PANEL_SWITCHGATE_SWITCH 53
144 #define GAME_PANEL_EMC_LENSES 54
145 #define GAME_PANEL_EMC_LENSES_TIME 55
146 #define GAME_PANEL_EMC_MAGNIFIER 56
147 #define GAME_PANEL_EMC_MAGNIFIER_TIME 57
148 #define GAME_PANEL_BALLOON_SWITCH 58
149 #define GAME_PANEL_DYNABOMB_NUMBER 59
150 #define GAME_PANEL_DYNABOMB_SIZE 60
151 #define GAME_PANEL_DYNABOMB_POWER 61
152 #define GAME_PANEL_PENGUINS 62
153 #define GAME_PANEL_SOKOBAN_OBJECTS 63
154 #define GAME_PANEL_SOKOBAN_FIELDS 64
155 #define GAME_PANEL_ROBOT_WHEEL 65
156 #define GAME_PANEL_CONVEYOR_BELT_1 66
157 #define GAME_PANEL_CONVEYOR_BELT_2 67
158 #define GAME_PANEL_CONVEYOR_BELT_3 68
159 #define GAME_PANEL_CONVEYOR_BELT_4 69
160 #define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 70
161 #define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 71
162 #define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 72
163 #define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 73
164 #define GAME_PANEL_MAGIC_WALL 74
165 #define GAME_PANEL_MAGIC_WALL_TIME 75
166 #define GAME_PANEL_GRAVITY_STATE 76
167 #define GAME_PANEL_GRAPHIC_1 77
168 #define GAME_PANEL_GRAPHIC_2 78
169 #define GAME_PANEL_GRAPHIC_3 79
170 #define GAME_PANEL_GRAPHIC_4 80
171 #define GAME_PANEL_GRAPHIC_5 81
172 #define GAME_PANEL_GRAPHIC_6 82
173 #define GAME_PANEL_GRAPHIC_7 83
174 #define GAME_PANEL_GRAPHIC_8 84
175 #define GAME_PANEL_ELEMENT_1 85
176 #define GAME_PANEL_ELEMENT_2 86
177 #define GAME_PANEL_ELEMENT_3 87
178 #define GAME_PANEL_ELEMENT_4 88
179 #define GAME_PANEL_ELEMENT_5 89
180 #define GAME_PANEL_ELEMENT_6 90
181 #define GAME_PANEL_ELEMENT_7 91
182 #define GAME_PANEL_ELEMENT_8 92
183 #define GAME_PANEL_ELEMENT_COUNT_1 93
184 #define GAME_PANEL_ELEMENT_COUNT_2 94
185 #define GAME_PANEL_ELEMENT_COUNT_3 95
186 #define GAME_PANEL_ELEMENT_COUNT_4 96
187 #define GAME_PANEL_ELEMENT_COUNT_5 97
188 #define GAME_PANEL_ELEMENT_COUNT_6 98
189 #define GAME_PANEL_ELEMENT_COUNT_7 99
190 #define GAME_PANEL_ELEMENT_COUNT_8 100
191 #define GAME_PANEL_CE_SCORE_1 101
192 #define GAME_PANEL_CE_SCORE_2 102
193 #define GAME_PANEL_CE_SCORE_3 103
194 #define GAME_PANEL_CE_SCORE_4 104
195 #define GAME_PANEL_CE_SCORE_5 105
196 #define GAME_PANEL_CE_SCORE_6 106
197 #define GAME_PANEL_CE_SCORE_7 107
198 #define GAME_PANEL_CE_SCORE_8 108
199 #define GAME_PANEL_CE_SCORE_1_ELEMENT 109
200 #define GAME_PANEL_CE_SCORE_2_ELEMENT 110
201 #define GAME_PANEL_CE_SCORE_3_ELEMENT 111
202 #define GAME_PANEL_CE_SCORE_4_ELEMENT 112
203 #define GAME_PANEL_CE_SCORE_5_ELEMENT 113
204 #define GAME_PANEL_CE_SCORE_6_ELEMENT 114
205 #define GAME_PANEL_CE_SCORE_7_ELEMENT 115
206 #define GAME_PANEL_CE_SCORE_8_ELEMENT 116
207 #define GAME_PANEL_PLAYER_NAME 117
208 #define GAME_PANEL_LEVEL_NAME 118
209 #define GAME_PANEL_LEVEL_AUTHOR 119
211 #define NUM_GAME_PANEL_CONTROLS 120
213 struct GamePanelOrderInfo
219 static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
221 struct GamePanelControlInfo
225 struct TextPosInfo *pos;
228 int graphic, graphic_active;
230 int value, last_value;
231 int frame, last_frame;
236 static struct GamePanelControlInfo game_panel_controls[] =
239 GAME_PANEL_LEVEL_NUMBER,
240 &game.panel.level_number,
249 GAME_PANEL_GEMS_TOTAL,
250 &game.panel.gems_total,
254 GAME_PANEL_GEMS_COLLECTED,
255 &game.panel.gems_collected,
259 GAME_PANEL_GEMS_SCORE,
260 &game.panel.gems_score,
264 GAME_PANEL_INVENTORY_COUNT,
265 &game.panel.inventory_count,
269 GAME_PANEL_INVENTORY_FIRST_1,
270 &game.panel.inventory_first[0],
274 GAME_PANEL_INVENTORY_FIRST_2,
275 &game.panel.inventory_first[1],
279 GAME_PANEL_INVENTORY_FIRST_3,
280 &game.panel.inventory_first[2],
284 GAME_PANEL_INVENTORY_FIRST_4,
285 &game.panel.inventory_first[3],
289 GAME_PANEL_INVENTORY_FIRST_5,
290 &game.panel.inventory_first[4],
294 GAME_PANEL_INVENTORY_FIRST_6,
295 &game.panel.inventory_first[5],
299 GAME_PANEL_INVENTORY_FIRST_7,
300 &game.panel.inventory_first[6],
304 GAME_PANEL_INVENTORY_FIRST_8,
305 &game.panel.inventory_first[7],
309 GAME_PANEL_INVENTORY_LAST_1,
310 &game.panel.inventory_last[0],
314 GAME_PANEL_INVENTORY_LAST_2,
315 &game.panel.inventory_last[1],
319 GAME_PANEL_INVENTORY_LAST_3,
320 &game.panel.inventory_last[2],
324 GAME_PANEL_INVENTORY_LAST_4,
325 &game.panel.inventory_last[3],
329 GAME_PANEL_INVENTORY_LAST_5,
330 &game.panel.inventory_last[4],
334 GAME_PANEL_INVENTORY_LAST_6,
335 &game.panel.inventory_last[5],
339 GAME_PANEL_INVENTORY_LAST_7,
340 &game.panel.inventory_last[6],
344 GAME_PANEL_INVENTORY_LAST_8,
345 &game.panel.inventory_last[7],
389 GAME_PANEL_KEY_WHITE,
390 &game.panel.key_white,
394 GAME_PANEL_KEY_WHITE_COUNT,
395 &game.panel.key_white_count,
404 GAME_PANEL_HIGHSCORE,
405 &game.panel.highscore,
429 GAME_PANEL_TIME_ANIM,
430 &game.panel.time_anim,
433 IMG_GFX_GAME_PANEL_TIME_ANIM,
434 IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
442 GAME_PANEL_HEALTH_ANIM,
443 &game.panel.health_anim,
446 IMG_GFX_GAME_PANEL_HEALTH_ANIM,
447 IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
455 GAME_PANEL_SHIELD_NORMAL,
456 &game.panel.shield_normal,
460 GAME_PANEL_SHIELD_NORMAL_TIME,
461 &game.panel.shield_normal_time,
465 GAME_PANEL_SHIELD_DEADLY,
466 &game.panel.shield_deadly,
470 GAME_PANEL_SHIELD_DEADLY_TIME,
471 &game.panel.shield_deadly_time,
480 GAME_PANEL_EMC_MAGIC_BALL,
481 &game.panel.emc_magic_ball,
485 GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
486 &game.panel.emc_magic_ball_switch,
490 GAME_PANEL_LIGHT_SWITCH,
491 &game.panel.light_switch,
495 GAME_PANEL_LIGHT_SWITCH_TIME,
496 &game.panel.light_switch_time,
500 GAME_PANEL_TIMEGATE_SWITCH,
501 &game.panel.timegate_switch,
505 GAME_PANEL_TIMEGATE_SWITCH_TIME,
506 &game.panel.timegate_switch_time,
510 GAME_PANEL_SWITCHGATE_SWITCH,
511 &game.panel.switchgate_switch,
515 GAME_PANEL_EMC_LENSES,
516 &game.panel.emc_lenses,
520 GAME_PANEL_EMC_LENSES_TIME,
521 &game.panel.emc_lenses_time,
525 GAME_PANEL_EMC_MAGNIFIER,
526 &game.panel.emc_magnifier,
530 GAME_PANEL_EMC_MAGNIFIER_TIME,
531 &game.panel.emc_magnifier_time,
535 GAME_PANEL_BALLOON_SWITCH,
536 &game.panel.balloon_switch,
540 GAME_PANEL_DYNABOMB_NUMBER,
541 &game.panel.dynabomb_number,
545 GAME_PANEL_DYNABOMB_SIZE,
546 &game.panel.dynabomb_size,
550 GAME_PANEL_DYNABOMB_POWER,
551 &game.panel.dynabomb_power,
556 &game.panel.penguins,
560 GAME_PANEL_SOKOBAN_OBJECTS,
561 &game.panel.sokoban_objects,
565 GAME_PANEL_SOKOBAN_FIELDS,
566 &game.panel.sokoban_fields,
570 GAME_PANEL_ROBOT_WHEEL,
571 &game.panel.robot_wheel,
575 GAME_PANEL_CONVEYOR_BELT_1,
576 &game.panel.conveyor_belt[0],
580 GAME_PANEL_CONVEYOR_BELT_2,
581 &game.panel.conveyor_belt[1],
585 GAME_PANEL_CONVEYOR_BELT_3,
586 &game.panel.conveyor_belt[2],
590 GAME_PANEL_CONVEYOR_BELT_4,
591 &game.panel.conveyor_belt[3],
595 GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
596 &game.panel.conveyor_belt_switch[0],
600 GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
601 &game.panel.conveyor_belt_switch[1],
605 GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
606 &game.panel.conveyor_belt_switch[2],
610 GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
611 &game.panel.conveyor_belt_switch[3],
615 GAME_PANEL_MAGIC_WALL,
616 &game.panel.magic_wall,
620 GAME_PANEL_MAGIC_WALL_TIME,
621 &game.panel.magic_wall_time,
625 GAME_PANEL_GRAVITY_STATE,
626 &game.panel.gravity_state,
630 GAME_PANEL_GRAPHIC_1,
631 &game.panel.graphic[0],
635 GAME_PANEL_GRAPHIC_2,
636 &game.panel.graphic[1],
640 GAME_PANEL_GRAPHIC_3,
641 &game.panel.graphic[2],
645 GAME_PANEL_GRAPHIC_4,
646 &game.panel.graphic[3],
650 GAME_PANEL_GRAPHIC_5,
651 &game.panel.graphic[4],
655 GAME_PANEL_GRAPHIC_6,
656 &game.panel.graphic[5],
660 GAME_PANEL_GRAPHIC_7,
661 &game.panel.graphic[6],
665 GAME_PANEL_GRAPHIC_8,
666 &game.panel.graphic[7],
670 GAME_PANEL_ELEMENT_1,
671 &game.panel.element[0],
675 GAME_PANEL_ELEMENT_2,
676 &game.panel.element[1],
680 GAME_PANEL_ELEMENT_3,
681 &game.panel.element[2],
685 GAME_PANEL_ELEMENT_4,
686 &game.panel.element[3],
690 GAME_PANEL_ELEMENT_5,
691 &game.panel.element[4],
695 GAME_PANEL_ELEMENT_6,
696 &game.panel.element[5],
700 GAME_PANEL_ELEMENT_7,
701 &game.panel.element[6],
705 GAME_PANEL_ELEMENT_8,
706 &game.panel.element[7],
710 GAME_PANEL_ELEMENT_COUNT_1,
711 &game.panel.element_count[0],
715 GAME_PANEL_ELEMENT_COUNT_2,
716 &game.panel.element_count[1],
720 GAME_PANEL_ELEMENT_COUNT_3,
721 &game.panel.element_count[2],
725 GAME_PANEL_ELEMENT_COUNT_4,
726 &game.panel.element_count[3],
730 GAME_PANEL_ELEMENT_COUNT_5,
731 &game.panel.element_count[4],
735 GAME_PANEL_ELEMENT_COUNT_6,
736 &game.panel.element_count[5],
740 GAME_PANEL_ELEMENT_COUNT_7,
741 &game.panel.element_count[6],
745 GAME_PANEL_ELEMENT_COUNT_8,
746 &game.panel.element_count[7],
750 GAME_PANEL_CE_SCORE_1,
751 &game.panel.ce_score[0],
755 GAME_PANEL_CE_SCORE_2,
756 &game.panel.ce_score[1],
760 GAME_PANEL_CE_SCORE_3,
761 &game.panel.ce_score[2],
765 GAME_PANEL_CE_SCORE_4,
766 &game.panel.ce_score[3],
770 GAME_PANEL_CE_SCORE_5,
771 &game.panel.ce_score[4],
775 GAME_PANEL_CE_SCORE_6,
776 &game.panel.ce_score[5],
780 GAME_PANEL_CE_SCORE_7,
781 &game.panel.ce_score[6],
785 GAME_PANEL_CE_SCORE_8,
786 &game.panel.ce_score[7],
790 GAME_PANEL_CE_SCORE_1_ELEMENT,
791 &game.panel.ce_score_element[0],
795 GAME_PANEL_CE_SCORE_2_ELEMENT,
796 &game.panel.ce_score_element[1],
800 GAME_PANEL_CE_SCORE_3_ELEMENT,
801 &game.panel.ce_score_element[2],
805 GAME_PANEL_CE_SCORE_4_ELEMENT,
806 &game.panel.ce_score_element[3],
810 GAME_PANEL_CE_SCORE_5_ELEMENT,
811 &game.panel.ce_score_element[4],
815 GAME_PANEL_CE_SCORE_6_ELEMENT,
816 &game.panel.ce_score_element[5],
820 GAME_PANEL_CE_SCORE_7_ELEMENT,
821 &game.panel.ce_score_element[6],
825 GAME_PANEL_CE_SCORE_8_ELEMENT,
826 &game.panel.ce_score_element[7],
830 GAME_PANEL_PLAYER_NAME,
831 &game.panel.player_name,
835 GAME_PANEL_LEVEL_NAME,
836 &game.panel.level_name,
840 GAME_PANEL_LEVEL_AUTHOR,
841 &game.panel.level_author,
852 // values for delayed check of falling and moving elements and for collision
853 #define CHECK_DELAY_MOVING 3
854 #define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
855 #define CHECK_DELAY_COLLISION 2
856 #define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
858 // values for initial player move delay (initial delay counter value)
859 #define INITIAL_MOVE_DELAY_OFF -1
860 #define INITIAL_MOVE_DELAY_ON 0
862 // values for player movement speed (which is in fact a delay value)
863 #define MOVE_DELAY_MIN_SPEED 32
864 #define MOVE_DELAY_NORMAL_SPEED 8
865 #define MOVE_DELAY_HIGH_SPEED 4
866 #define MOVE_DELAY_MAX_SPEED 1
868 #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
869 #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
871 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
872 #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
874 // values for scroll positions
875 #define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
876 (x) > SBX_Right + MIDPOSX ? SBX_Right :\
878 #define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
879 (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
882 // values for other actions
883 #define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
884 #define MOVE_STEPSIZE_MIN (1)
885 #define MOVE_STEPSIZE_MAX (TILEX)
887 #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
888 #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
890 #define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
892 #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
893 RND(element_info[e].push_delay_random))
894 #define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
895 RND(element_info[e].drop_delay_random))
896 #define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
897 RND(element_info[e].move_delay_random))
898 #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
899 (element_info[e].move_delay_random))
900 #define GET_NEW_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
901 RND(element_info[e].step_delay_random))
902 #define GET_MAX_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
903 (element_info[e].step_delay_random))
904 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
905 RND(element_info[e].ce_value_random_initial))
906 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
907 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
908 RND((c)->delay_random * (c)->delay_frames))
909 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
910 RND((c)->delay_random))
913 #define GET_VALID_RUNTIME_ELEMENT(e) \
914 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
916 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
917 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
918 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
919 (be) + (e) - EL_SELF)
921 #define GET_PLAYER_FROM_BITS(p) \
922 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
924 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
925 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
926 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
927 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
928 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
929 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
930 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
931 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
932 RESOLVED_REFERENCE_ELEMENT(be, e) : \
935 #define CAN_GROW_INTO(e) \
936 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
938 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
939 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
942 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
943 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
944 (CAN_MOVE_INTO_ACID(e) && \
945 Tile[x][y] == EL_ACID) || \
948 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
949 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
950 (CAN_MOVE_INTO_ACID(e) && \
951 Tile[x][y] == EL_ACID) || \
954 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
955 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
957 (CAN_MOVE_INTO_ACID(e) && \
958 Tile[x][y] == EL_ACID) || \
959 (DONT_COLLIDE_WITH(e) && \
961 !PLAYER_ENEMY_PROTECTED(x, y))))
963 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
964 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
966 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
967 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
969 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
970 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
972 #define ANDROID_CAN_CLONE_FIELD(x, y) \
973 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
974 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
976 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
977 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
979 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
980 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
982 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
983 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
985 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
986 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
988 #define PIG_CAN_ENTER_FIELD(e, x, y) \
989 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
991 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
992 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
993 Tile[x][y] == EL_EM_EXIT_OPEN || \
994 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
995 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
996 IS_FOOD_PENGUIN(Tile[x][y])))
997 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
998 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
1000 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
1001 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
1003 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
1004 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
1006 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
1007 (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
1008 Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
1010 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
1012 #define CE_ENTER_FIELD_COND(e, x, y) \
1013 (!IS_PLAYER(x, y) && \
1014 IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
1016 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
1017 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
1019 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
1020 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1022 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1023 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1024 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1025 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1027 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1029 // game button identifiers
1030 #define GAME_CTRL_ID_STOP 0
1031 #define GAME_CTRL_ID_PAUSE 1
1032 #define GAME_CTRL_ID_PLAY 2
1033 #define GAME_CTRL_ID_UNDO 3
1034 #define GAME_CTRL_ID_REDO 4
1035 #define GAME_CTRL_ID_SAVE 5
1036 #define GAME_CTRL_ID_PAUSE2 6
1037 #define GAME_CTRL_ID_LOAD 7
1038 #define GAME_CTRL_ID_RESTART 8
1039 #define GAME_CTRL_ID_PANEL_STOP 9
1040 #define GAME_CTRL_ID_PANEL_PAUSE 10
1041 #define GAME_CTRL_ID_PANEL_PLAY 11
1042 #define GAME_CTRL_ID_PANEL_RESTART 12
1043 #define GAME_CTRL_ID_TOUCH_STOP 13
1044 #define GAME_CTRL_ID_TOUCH_PAUSE 14
1045 #define GAME_CTRL_ID_TOUCH_RESTART 15
1046 #define SOUND_CTRL_ID_MUSIC 16
1047 #define SOUND_CTRL_ID_LOOPS 17
1048 #define SOUND_CTRL_ID_SIMPLE 18
1049 #define SOUND_CTRL_ID_PANEL_MUSIC 19
1050 #define SOUND_CTRL_ID_PANEL_LOOPS 20
1051 #define SOUND_CTRL_ID_PANEL_SIMPLE 21
1053 #define NUM_GAME_BUTTONS 22
1056 // forward declaration for internal use
1058 static void CreateField(int, int, int);
1060 static void ResetGfxAnimation(int, int);
1062 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1063 static void AdvanceFrameAndPlayerCounters(int);
1065 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1066 static boolean MovePlayer(struct PlayerInfo *, int, int);
1067 static void ScrollPlayer(struct PlayerInfo *, int);
1068 static void ScrollScreen(struct PlayerInfo *, int);
1070 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1071 static boolean DigFieldByCE(int, int, int);
1072 static boolean SnapField(struct PlayerInfo *, int, int);
1073 static boolean DropElement(struct PlayerInfo *);
1075 static void InitBeltMovement(void);
1076 static void CloseAllOpenTimegates(void);
1077 static void CheckGravityMovement(struct PlayerInfo *);
1078 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1079 static void KillPlayerUnlessEnemyProtected(int, int);
1080 static void KillPlayerUnlessExplosionProtected(int, int);
1082 static void CheckNextToConditions(int, int);
1083 static void TestIfPlayerNextToCustomElement(int, int);
1084 static void TestIfPlayerTouchesCustomElement(int, int);
1085 static void TestIfElementNextToCustomElement(int, int);
1086 static void TestIfElementTouchesCustomElement(int, int);
1087 static void TestIfElementHitsCustomElement(int, int, int);
1089 static void HandleElementChange(int, int, int);
1090 static void ExecuteCustomElementAction(int, int, int, int);
1091 static boolean ChangeElement(int, int, int, int);
1093 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int, int, int);
1094 #define CheckTriggeredElementChange(x, y, e, ev) \
1095 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, -1)
1096 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1097 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1098 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1099 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1100 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1101 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1102 #define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
1103 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1105 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1106 #define CheckElementChange(x, y, e, te, ev) \
1107 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1108 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1109 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1110 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1111 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1112 #define CheckElementChangeByMouse(x, y, e, ev, s) \
1113 CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
1115 static void PlayLevelSound(int, int, int);
1116 static void PlayLevelSoundNearest(int, int, int);
1117 static void PlayLevelSoundAction(int, int, int);
1118 static void PlayLevelSoundElementAction(int, int, int, int);
1119 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1120 static void PlayLevelSoundActionIfLoop(int, int, int);
1121 static void StopLevelSoundActionIfLoop(int, int, int);
1122 static void PlayLevelMusic(void);
1123 static void FadeLevelSoundsAndMusic(void);
1125 static void HandleGameButtons(struct GadgetInfo *);
1127 int AmoebaNeighbourNr(int, int);
1128 void AmoebaToDiamond(int, int);
1129 void ContinueMoving(int, int);
1130 void Bang(int, int);
1131 void InitMovDir(int, int);
1132 void InitAmoebaNr(int, int);
1133 void NewHighScore(int, boolean);
1135 void TestIfGoodThingHitsBadThing(int, int, int);
1136 void TestIfBadThingHitsGoodThing(int, int, int);
1137 void TestIfPlayerTouchesBadThing(int, int);
1138 void TestIfPlayerRunsIntoBadThing(int, int, int);
1139 void TestIfBadThingTouchesPlayer(int, int);
1140 void TestIfBadThingRunsIntoPlayer(int, int, int);
1141 void TestIfFriendTouchesBadThing(int, int);
1142 void TestIfBadThingTouchesFriend(int, int);
1143 void TestIfBadThingTouchesOtherBadThing(int, int);
1144 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1146 void KillPlayer(struct PlayerInfo *);
1147 void BuryPlayer(struct PlayerInfo *);
1148 void RemovePlayer(struct PlayerInfo *);
1149 void ExitPlayer(struct PlayerInfo *);
1151 static int getInvisibleActiveFromInvisibleElement(int);
1152 static int getInvisibleFromInvisibleActiveElement(int);
1154 static void TestFieldAfterSnapping(int, int, int, int, int);
1156 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1158 // for detection of endless loops, caused by custom element programming
1159 // (using maximal playfield width x 10 is just a rough approximation)
1160 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1162 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1164 if (recursion_loop_detected) \
1167 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1169 recursion_loop_detected = TRUE; \
1170 recursion_loop_element = (e); \
1173 recursion_loop_depth++; \
1176 #define RECURSION_LOOP_DETECTION_END() \
1178 recursion_loop_depth--; \
1181 static int recursion_loop_depth;
1182 static boolean recursion_loop_detected;
1183 static boolean recursion_loop_element;
1185 static int map_player_action[MAX_PLAYERS];
1188 // ----------------------------------------------------------------------------
1189 // definition of elements that automatically change to other elements after
1190 // a specified time, eventually calling a function when changing
1191 // ----------------------------------------------------------------------------
1193 // forward declaration for changer functions
1194 static void InitBuggyBase(int, int);
1195 static void WarnBuggyBase(int, int);
1197 static void InitTrap(int, int);
1198 static void ActivateTrap(int, int);
1199 static void ChangeActiveTrap(int, int);
1201 static void InitRobotWheel(int, int);
1202 static void RunRobotWheel(int, int);
1203 static void StopRobotWheel(int, int);
1205 static void InitTimegateWheel(int, int);
1206 static void RunTimegateWheel(int, int);
1208 static void InitMagicBallDelay(int, int);
1209 static void ActivateMagicBall(int, int);
1211 struct ChangingElementInfo
1216 void (*pre_change_function)(int x, int y);
1217 void (*change_function)(int x, int y);
1218 void (*post_change_function)(int x, int y);
1221 static struct ChangingElementInfo change_delay_list[] =
1256 EL_STEEL_EXIT_OPENING,
1264 EL_STEEL_EXIT_CLOSING,
1265 EL_STEEL_EXIT_CLOSED,
1288 EL_EM_STEEL_EXIT_OPENING,
1289 EL_EM_STEEL_EXIT_OPEN,
1296 EL_EM_STEEL_EXIT_CLOSING,
1320 EL_SWITCHGATE_OPENING,
1328 EL_SWITCHGATE_CLOSING,
1329 EL_SWITCHGATE_CLOSED,
1336 EL_TIMEGATE_OPENING,
1344 EL_TIMEGATE_CLOSING,
1353 EL_ACID_SPLASH_LEFT,
1361 EL_ACID_SPLASH_RIGHT,
1370 EL_SP_BUGGY_BASE_ACTIVATING,
1377 EL_SP_BUGGY_BASE_ACTIVATING,
1378 EL_SP_BUGGY_BASE_ACTIVE,
1385 EL_SP_BUGGY_BASE_ACTIVE,
1409 EL_ROBOT_WHEEL_ACTIVE,
1417 EL_TIMEGATE_SWITCH_ACTIVE,
1425 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1426 EL_DC_TIMEGATE_SWITCH,
1433 EL_EMC_MAGIC_BALL_ACTIVE,
1434 EL_EMC_MAGIC_BALL_ACTIVE,
1441 EL_EMC_SPRING_BUMPER_ACTIVE,
1442 EL_EMC_SPRING_BUMPER,
1449 EL_DIAGONAL_SHRINKING,
1457 EL_DIAGONAL_GROWING,
1478 int push_delay_fixed, push_delay_random;
1482 { EL_SPRING, 0, 0 },
1483 { EL_BALLOON, 0, 0 },
1485 { EL_SOKOBAN_OBJECT, 2, 0 },
1486 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1487 { EL_SATELLITE, 2, 0 },
1488 { EL_SP_DISK_YELLOW, 2, 0 },
1490 { EL_UNDEFINED, 0, 0 },
1498 move_stepsize_list[] =
1500 { EL_AMOEBA_DROP, 2 },
1501 { EL_AMOEBA_DROPPING, 2 },
1502 { EL_QUICKSAND_FILLING, 1 },
1503 { EL_QUICKSAND_EMPTYING, 1 },
1504 { EL_QUICKSAND_FAST_FILLING, 2 },
1505 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1506 { EL_MAGIC_WALL_FILLING, 2 },
1507 { EL_MAGIC_WALL_EMPTYING, 2 },
1508 { EL_BD_MAGIC_WALL_FILLING, 2 },
1509 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1510 { EL_DC_MAGIC_WALL_FILLING, 2 },
1511 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1513 { EL_UNDEFINED, 0 },
1521 collect_count_list[] =
1524 { EL_BD_DIAMOND, 1 },
1525 { EL_EMERALD_YELLOW, 1 },
1526 { EL_EMERALD_RED, 1 },
1527 { EL_EMERALD_PURPLE, 1 },
1529 { EL_SP_INFOTRON, 1 },
1533 { EL_UNDEFINED, 0 },
1541 access_direction_list[] =
1543 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1544 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1545 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1546 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1547 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1548 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1549 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1550 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1551 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1552 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1553 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1555 { EL_SP_PORT_LEFT, MV_RIGHT },
1556 { EL_SP_PORT_RIGHT, MV_LEFT },
1557 { EL_SP_PORT_UP, MV_DOWN },
1558 { EL_SP_PORT_DOWN, MV_UP },
1559 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1560 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1561 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1562 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1563 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1564 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1565 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1566 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1567 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1568 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1569 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1570 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1571 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1572 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1573 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1575 { EL_UNDEFINED, MV_NONE }
1578 static struct XY xy_topdown[] =
1586 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1588 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1589 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1590 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1591 IS_JUST_CHANGING(x, y))
1593 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1595 // static variables for playfield scan mode (scanning forward or backward)
1596 static int playfield_scan_start_x = 0;
1597 static int playfield_scan_start_y = 0;
1598 static int playfield_scan_delta_x = 1;
1599 static int playfield_scan_delta_y = 1;
1601 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1602 (y) >= 0 && (y) <= lev_fieldy - 1; \
1603 (y) += playfield_scan_delta_y) \
1604 for ((x) = playfield_scan_start_x; \
1605 (x) >= 0 && (x) <= lev_fieldx - 1; \
1606 (x) += playfield_scan_delta_x)
1609 void DEBUG_SetMaximumDynamite(void)
1613 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1614 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1615 local_player->inventory_element[local_player->inventory_size++] =
1620 static void InitPlayfieldScanModeVars(void)
1622 if (game.use_reverse_scan_direction)
1624 playfield_scan_start_x = lev_fieldx - 1;
1625 playfield_scan_start_y = lev_fieldy - 1;
1627 playfield_scan_delta_x = -1;
1628 playfield_scan_delta_y = -1;
1632 playfield_scan_start_x = 0;
1633 playfield_scan_start_y = 0;
1635 playfield_scan_delta_x = 1;
1636 playfield_scan_delta_y = 1;
1640 static void InitPlayfieldScanMode(int mode)
1642 game.use_reverse_scan_direction =
1643 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1645 InitPlayfieldScanModeVars();
1648 static int get_move_delay_from_stepsize(int move_stepsize)
1651 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1653 // make sure that stepsize value is always a power of 2
1654 move_stepsize = (1 << log_2(move_stepsize));
1656 return TILEX / move_stepsize;
1659 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1662 int player_nr = player->index_nr;
1663 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1664 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1666 // do no immediately change move delay -- the player might just be moving
1667 player->move_delay_value_next = move_delay;
1669 // information if player can move must be set separately
1670 player->cannot_move = cannot_move;
1674 player->move_delay = game.initial_move_delay[player_nr];
1675 player->move_delay_value = game.initial_move_delay_value[player_nr];
1677 player->move_delay_value_next = -1;
1679 player->move_delay_reset_counter = 0;
1683 void GetPlayerConfig(void)
1685 GameFrameDelay = setup.game_frame_delay;
1687 if (!audio.sound_available)
1688 setup.sound_simple = FALSE;
1690 if (!audio.loops_available)
1691 setup.sound_loops = FALSE;
1693 if (!audio.music_available)
1694 setup.sound_music = FALSE;
1696 if (!video.fullscreen_available)
1697 setup.fullscreen = FALSE;
1699 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1701 SetAudioMode(setup.sound);
1704 int GetElementFromGroupElement(int element)
1706 if (IS_GROUP_ELEMENT(element))
1708 struct ElementGroupInfo *group = element_info[element].group;
1709 int last_anim_random_frame = gfx.anim_random_frame;
1712 if (group->choice_mode == ANIM_RANDOM)
1713 gfx.anim_random_frame = RND(group->num_elements_resolved);
1715 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1716 group->choice_mode, 0,
1719 if (group->choice_mode == ANIM_RANDOM)
1720 gfx.anim_random_frame = last_anim_random_frame;
1722 group->choice_pos++;
1724 element = group->element_resolved[element_pos];
1730 static void IncrementSokobanFieldsNeeded(void)
1732 if (level.sb_fields_needed)
1733 game.sokoban_fields_still_needed++;
1736 static void IncrementSokobanObjectsNeeded(void)
1738 if (level.sb_objects_needed)
1739 game.sokoban_objects_still_needed++;
1742 static void DecrementSokobanFieldsNeeded(void)
1744 if (game.sokoban_fields_still_needed > 0)
1745 game.sokoban_fields_still_needed--;
1748 static void DecrementSokobanObjectsNeeded(void)
1750 if (game.sokoban_objects_still_needed > 0)
1751 game.sokoban_objects_still_needed--;
1754 static void InitPlayerField(int x, int y, int element, boolean init_game)
1756 if (element == EL_SP_MURPHY)
1760 if (stored_player[0].present)
1762 Tile[x][y] = EL_SP_MURPHY_CLONE;
1768 stored_player[0].initial_element = element;
1769 stored_player[0].use_murphy = TRUE;
1771 if (!level.use_artwork_element[0])
1772 stored_player[0].artwork_element = EL_SP_MURPHY;
1775 Tile[x][y] = EL_PLAYER_1;
1781 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1782 int jx = player->jx, jy = player->jy;
1784 player->present = TRUE;
1786 player->block_last_field = (element == EL_SP_MURPHY ?
1787 level.sp_block_last_field :
1788 level.block_last_field);
1790 // ---------- initialize player's last field block delay ------------------
1792 // always start with reliable default value (no adjustment needed)
1793 player->block_delay_adjustment = 0;
1795 // special case 1: in Supaplex, Murphy blocks last field one more frame
1796 if (player->block_last_field && element == EL_SP_MURPHY)
1797 player->block_delay_adjustment = 1;
1799 // special case 2: in game engines before 3.1.1, blocking was different
1800 if (game.use_block_last_field_bug)
1801 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1803 if (!network.enabled || player->connected_network)
1805 player->active = TRUE;
1807 // remove potentially duplicate players
1808 if (IN_LEV_FIELD(jx, jy) && StorePlayer[jx][jy] == Tile[x][y])
1809 StorePlayer[jx][jy] = 0;
1811 StorePlayer[x][y] = Tile[x][y];
1813 #if DEBUG_INIT_PLAYER
1814 Debug("game:init:player", "- player element %d activated",
1815 player->element_nr);
1816 Debug("game:init:player", " (local player is %d and currently %s)",
1817 local_player->element_nr,
1818 local_player->active ? "active" : "not active");
1822 Tile[x][y] = EL_EMPTY;
1824 player->jx = player->last_jx = x;
1825 player->jy = player->last_jy = y;
1828 // always check if player was just killed and should be reanimated
1830 int player_nr = GET_PLAYER_NR(element);
1831 struct PlayerInfo *player = &stored_player[player_nr];
1833 if (player->active && player->killed)
1834 player->reanimated = TRUE; // if player was just killed, reanimate him
1838 static void InitField(int x, int y, boolean init_game)
1840 int element = Tile[x][y];
1849 InitPlayerField(x, y, element, init_game);
1852 case EL_SOKOBAN_FIELD_PLAYER:
1853 element = Tile[x][y] = EL_PLAYER_1;
1854 InitField(x, y, init_game);
1856 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1857 InitField(x, y, init_game);
1860 case EL_SOKOBAN_FIELD_EMPTY:
1861 IncrementSokobanFieldsNeeded();
1864 case EL_SOKOBAN_OBJECT:
1865 IncrementSokobanObjectsNeeded();
1869 if (x < lev_fieldx - 1 && Tile[x + 1][y] == EL_ACID)
1870 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1871 else if (x > 0 && Tile[x - 1][y] == EL_ACID)
1872 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1873 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPLEFT)
1874 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1875 else if (y > 0 && Tile[x][y - 1] == EL_ACID)
1876 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1877 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPRIGHT)
1878 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1887 case EL_SPACESHIP_RIGHT:
1888 case EL_SPACESHIP_UP:
1889 case EL_SPACESHIP_LEFT:
1890 case EL_SPACESHIP_DOWN:
1891 case EL_BD_BUTTERFLY:
1892 case EL_BD_BUTTERFLY_RIGHT:
1893 case EL_BD_BUTTERFLY_UP:
1894 case EL_BD_BUTTERFLY_LEFT:
1895 case EL_BD_BUTTERFLY_DOWN:
1897 case EL_BD_FIREFLY_RIGHT:
1898 case EL_BD_FIREFLY_UP:
1899 case EL_BD_FIREFLY_LEFT:
1900 case EL_BD_FIREFLY_DOWN:
1901 case EL_PACMAN_RIGHT:
1903 case EL_PACMAN_LEFT:
1904 case EL_PACMAN_DOWN:
1906 case EL_YAMYAM_LEFT:
1907 case EL_YAMYAM_RIGHT:
1909 case EL_YAMYAM_DOWN:
1910 case EL_DARK_YAMYAM:
1913 case EL_SP_SNIKSNAK:
1914 case EL_SP_ELECTRON:
1920 case EL_SPRING_LEFT:
1921 case EL_SPRING_RIGHT:
1925 case EL_AMOEBA_FULL:
1930 case EL_AMOEBA_DROP:
1931 if (y == lev_fieldy - 1)
1933 Tile[x][y] = EL_AMOEBA_GROWING;
1934 Store[x][y] = EL_AMOEBA_WET;
1938 case EL_DYNAMITE_ACTIVE:
1939 case EL_SP_DISK_RED_ACTIVE:
1940 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1941 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1942 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1943 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1944 MovDelay[x][y] = 96;
1947 case EL_EM_DYNAMITE_ACTIVE:
1948 MovDelay[x][y] = 32;
1952 game.lights_still_needed++;
1956 game.friends_still_needed++;
1961 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1964 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1965 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1966 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1967 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1968 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1969 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1970 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1971 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1972 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1973 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1974 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1975 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1978 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1979 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1980 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1982 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1984 game.belt_dir[belt_nr] = belt_dir;
1985 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1987 else // more than one switch -- set it like the first switch
1989 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1994 case EL_LIGHT_SWITCH_ACTIVE:
1996 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1999 case EL_INVISIBLE_STEELWALL:
2000 case EL_INVISIBLE_WALL:
2001 case EL_INVISIBLE_SAND:
2002 if (game.light_time_left > 0 ||
2003 game.lenses_time_left > 0)
2004 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
2007 case EL_EMC_MAGIC_BALL:
2008 if (game.ball_active)
2009 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
2012 case EL_EMC_MAGIC_BALL_SWITCH:
2013 if (game.ball_active)
2014 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
2017 case EL_TRIGGER_PLAYER:
2018 case EL_TRIGGER_ELEMENT:
2019 case EL_TRIGGER_CE_VALUE:
2020 case EL_TRIGGER_CE_SCORE:
2022 case EL_ANY_ELEMENT:
2023 case EL_CURRENT_CE_VALUE:
2024 case EL_CURRENT_CE_SCORE:
2041 // reference elements should not be used on the playfield
2042 Tile[x][y] = EL_EMPTY;
2046 if (IS_CUSTOM_ELEMENT(element))
2048 if (CAN_MOVE(element))
2051 if (!element_info[element].use_last_ce_value || init_game)
2052 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2054 else if (IS_GROUP_ELEMENT(element))
2056 Tile[x][y] = GetElementFromGroupElement(element);
2058 InitField(x, y, init_game);
2060 else if (IS_EMPTY_ELEMENT(element))
2062 GfxElementEmpty[x][y] = element;
2063 Tile[x][y] = EL_EMPTY;
2065 if (element_info[element].use_gfx_element)
2066 game.use_masked_elements = TRUE;
2073 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2076 static void InitField_WithBug1(int x, int y, boolean init_game)
2078 InitField(x, y, init_game);
2080 // not needed to call InitMovDir() -- already done by InitField()!
2081 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2082 CAN_MOVE(Tile[x][y]))
2086 static void InitField_WithBug2(int x, int y, boolean init_game)
2088 int old_element = Tile[x][y];
2090 InitField(x, y, init_game);
2092 // not needed to call InitMovDir() -- already done by InitField()!
2093 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2094 CAN_MOVE(old_element) &&
2095 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2098 /* this case is in fact a combination of not less than three bugs:
2099 first, it calls InitMovDir() for elements that can move, although this is
2100 already done by InitField(); then, it checks the element that was at this
2101 field _before_ the call to InitField() (which can change it); lastly, it
2102 was not called for "mole with direction" elements, which were treated as
2103 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2107 static int get_key_element_from_nr(int key_nr)
2109 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2110 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2111 EL_EM_KEY_1 : EL_KEY_1);
2113 return key_base_element + key_nr;
2116 static int get_next_dropped_element(struct PlayerInfo *player)
2118 return (player->inventory_size > 0 ?
2119 player->inventory_element[player->inventory_size - 1] :
2120 player->inventory_infinite_element != EL_UNDEFINED ?
2121 player->inventory_infinite_element :
2122 player->dynabombs_left > 0 ?
2123 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2127 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2129 // pos >= 0: get element from bottom of the stack;
2130 // pos < 0: get element from top of the stack
2134 int min_inventory_size = -pos;
2135 int inventory_pos = player->inventory_size - min_inventory_size;
2136 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2138 return (player->inventory_size >= min_inventory_size ?
2139 player->inventory_element[inventory_pos] :
2140 player->inventory_infinite_element != EL_UNDEFINED ?
2141 player->inventory_infinite_element :
2142 player->dynabombs_left >= min_dynabombs_left ?
2143 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2148 int min_dynabombs_left = pos + 1;
2149 int min_inventory_size = pos + 1 - player->dynabombs_left;
2150 int inventory_pos = pos - player->dynabombs_left;
2152 return (player->inventory_infinite_element != EL_UNDEFINED ?
2153 player->inventory_infinite_element :
2154 player->dynabombs_left >= min_dynabombs_left ?
2155 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2156 player->inventory_size >= min_inventory_size ?
2157 player->inventory_element[inventory_pos] :
2162 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2164 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2165 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2168 if (gpo1->sort_priority != gpo2->sort_priority)
2169 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2171 compare_result = gpo1->nr - gpo2->nr;
2173 return compare_result;
2176 int getPlayerInventorySize(int player_nr)
2178 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2179 return game_em.ply[player_nr]->dynamite;
2180 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2181 return game_sp.red_disk_count;
2183 return stored_player[player_nr].inventory_size;
2186 static void InitGameControlValues(void)
2190 for (i = 0; game_panel_controls[i].nr != -1; i++)
2192 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2193 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2194 struct TextPosInfo *pos = gpc->pos;
2196 int type = gpc->type;
2200 Error("'game_panel_controls' structure corrupted at %d", i);
2202 Fail("this should not happen -- please debug");
2205 // force update of game controls after initialization
2206 gpc->value = gpc->last_value = -1;
2207 gpc->frame = gpc->last_frame = -1;
2208 gpc->gfx_frame = -1;
2210 // determine panel value width for later calculation of alignment
2211 if (type == TYPE_INTEGER || type == TYPE_STRING)
2213 pos->width = pos->size * getFontWidth(pos->font);
2214 pos->height = getFontHeight(pos->font);
2216 else if (type == TYPE_ELEMENT)
2218 pos->width = pos->size;
2219 pos->height = pos->size;
2222 // fill structure for game panel draw order
2224 gpo->sort_priority = pos->sort_priority;
2227 // sort game panel controls according to sort_priority and control number
2228 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2229 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2232 static void UpdatePlayfieldElementCount(void)
2234 boolean use_element_count = FALSE;
2237 // first check if it is needed at all to calculate playfield element count
2238 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2239 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2240 use_element_count = TRUE;
2242 if (!use_element_count)
2245 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2246 element_info[i].element_count = 0;
2248 SCAN_PLAYFIELD(x, y)
2250 element_info[Tile[x][y]].element_count++;
2253 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2254 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2255 if (IS_IN_GROUP(j, i))
2256 element_info[EL_GROUP_START + i].element_count +=
2257 element_info[j].element_count;
2260 static void UpdateGameControlValues(void)
2263 int time = (game.LevelSolved ?
2264 game.LevelSolved_CountingTime :
2265 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2267 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2268 game_sp.time_played :
2269 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2270 game_mm.energy_left :
2271 game.no_level_time_limit ? TimePlayed : TimeLeft);
2272 int score = (game.LevelSolved ?
2273 game.LevelSolved_CountingScore :
2274 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2275 game_em.lev->score :
2276 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2278 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2281 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2282 game_em.lev->gems_needed :
2283 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2284 game_sp.infotrons_still_needed :
2285 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2286 game_mm.kettles_still_needed :
2287 game.gems_still_needed);
2288 int gems_total = level.gems_needed;
2289 int gems_collected = gems_total - gems;
2290 int gems_score = level.score[SC_EMERALD];
2291 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2292 game_em.lev->gems_needed > 0 :
2293 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2294 game_sp.infotrons_still_needed > 0 :
2295 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2296 game_mm.kettles_still_needed > 0 ||
2297 game_mm.lights_still_needed > 0 :
2298 game.gems_still_needed > 0 ||
2299 game.sokoban_fields_still_needed > 0 ||
2300 game.sokoban_objects_still_needed > 0 ||
2301 game.lights_still_needed > 0);
2302 int health = (game.LevelSolved ?
2303 game.LevelSolved_CountingHealth :
2304 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2305 MM_HEALTH(game_mm.laser_overload_value) :
2307 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2309 UpdatePlayfieldElementCount();
2311 // update game panel control values
2313 // used instead of "level_nr" (for network games)
2314 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2315 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2316 game_panel_controls[GAME_PANEL_GEMS_TOTAL].value = gems_total;
2317 game_panel_controls[GAME_PANEL_GEMS_COLLECTED].value = gems_collected;
2318 game_panel_controls[GAME_PANEL_GEMS_SCORE].value = gems_score;
2320 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2321 for (i = 0; i < MAX_NUM_KEYS; i++)
2322 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2323 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2324 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2326 if (game.centered_player_nr == -1)
2328 for (i = 0; i < MAX_PLAYERS; i++)
2330 // only one player in Supaplex game engine
2331 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2334 for (k = 0; k < MAX_NUM_KEYS; k++)
2336 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2338 if (game_em.ply[i]->keys & (1 << k))
2339 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2340 get_key_element_from_nr(k);
2342 else if (stored_player[i].key[k])
2343 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2344 get_key_element_from_nr(k);
2347 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2348 getPlayerInventorySize(i);
2350 if (stored_player[i].num_white_keys > 0)
2351 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2354 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2355 stored_player[i].num_white_keys;
2360 int player_nr = game.centered_player_nr;
2362 for (k = 0; k < MAX_NUM_KEYS; k++)
2364 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2366 if (game_em.ply[player_nr]->keys & (1 << k))
2367 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2368 get_key_element_from_nr(k);
2370 else if (stored_player[player_nr].key[k])
2371 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2372 get_key_element_from_nr(k);
2375 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2376 getPlayerInventorySize(player_nr);
2378 if (stored_player[player_nr].num_white_keys > 0)
2379 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2381 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2382 stored_player[player_nr].num_white_keys;
2385 // re-arrange keys on game panel, if needed or if defined by style settings
2386 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2388 int nr = GAME_PANEL_KEY_1 + i;
2389 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2390 struct TextPosInfo *pos = gpc->pos;
2392 // skip check if key is not in the player's inventory
2393 if (gpc->value == EL_EMPTY)
2396 // check if keys should be arranged on panel from left to right
2397 if (pos->style == STYLE_LEFTMOST_POSITION)
2399 // check previous key positions (left from current key)
2400 for (k = 0; k < i; k++)
2402 int nr_new = GAME_PANEL_KEY_1 + k;
2404 if (game_panel_controls[nr_new].value == EL_EMPTY)
2406 game_panel_controls[nr_new].value = gpc->value;
2407 gpc->value = EL_EMPTY;
2414 // check if "undefined" keys can be placed at some other position
2415 if (pos->x == -1 && pos->y == -1)
2417 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2419 // 1st try: display key at the same position as normal or EM keys
2420 if (game_panel_controls[nr_new].value == EL_EMPTY)
2422 game_panel_controls[nr_new].value = gpc->value;
2426 // 2nd try: display key at the next free position in the key panel
2427 for (k = 0; k < STD_NUM_KEYS; k++)
2429 nr_new = GAME_PANEL_KEY_1 + k;
2431 if (game_panel_controls[nr_new].value == EL_EMPTY)
2433 game_panel_controls[nr_new].value = gpc->value;
2442 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2444 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2445 get_inventory_element_from_pos(local_player, i);
2446 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2447 get_inventory_element_from_pos(local_player, -i - 1);
2450 game_panel_controls[GAME_PANEL_SCORE].value = score;
2451 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2453 game_panel_controls[GAME_PANEL_TIME].value = time;
2455 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2456 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2457 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2459 if (level.time == 0)
2460 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2462 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2464 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2465 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2467 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2469 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2470 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2472 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2473 local_player->shield_normal_time_left;
2474 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2475 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2477 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2478 local_player->shield_deadly_time_left;
2480 game_panel_controls[GAME_PANEL_EXIT].value =
2481 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2483 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2484 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2485 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2486 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2487 EL_EMC_MAGIC_BALL_SWITCH);
2489 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2490 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2491 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2492 game.light_time_left;
2494 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2495 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2496 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2497 game.timegate_time_left;
2499 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2500 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2502 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2503 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2504 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2505 game.lenses_time_left;
2507 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2508 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2509 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2510 game.magnify_time_left;
2512 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2513 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2514 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2515 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2516 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2517 EL_BALLOON_SWITCH_NONE);
2519 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2520 local_player->dynabomb_count;
2521 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2522 local_player->dynabomb_size;
2523 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2524 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2526 game_panel_controls[GAME_PANEL_PENGUINS].value =
2527 game.friends_still_needed;
2529 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2530 game.sokoban_objects_still_needed;
2531 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2532 game.sokoban_fields_still_needed;
2534 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2535 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2537 for (i = 0; i < NUM_BELTS; i++)
2539 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2540 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2541 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2542 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2543 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2546 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2547 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2548 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2549 game.magic_wall_time_left;
2551 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2552 local_player->gravity;
2554 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2555 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2557 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2558 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2559 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2560 game.panel.element[i].id : EL_UNDEFINED);
2562 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2563 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2564 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2565 element_info[game.panel.element_count[i].id].element_count : 0);
2567 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2568 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2569 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2570 element_info[game.panel.ce_score[i].id].collect_score : 0);
2572 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2573 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2574 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2575 element_info[game.panel.ce_score_element[i].id].collect_score :
2578 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2579 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2580 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2582 // update game panel control frames
2584 for (i = 0; game_panel_controls[i].nr != -1; i++)
2586 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2588 if (gpc->type == TYPE_ELEMENT)
2590 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2592 int last_anim_random_frame = gfx.anim_random_frame;
2593 int element = gpc->value;
2594 int graphic = el2panelimg(element);
2595 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2597 graphic_info[graphic].anim_global_anim_sync ?
2598 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2600 if (gpc->value != gpc->last_value)
2603 gpc->gfx_random = init_gfx_random;
2609 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2610 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2611 gpc->gfx_random = init_gfx_random;
2614 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2615 gfx.anim_random_frame = gpc->gfx_random;
2617 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2618 gpc->gfx_frame = element_info[element].collect_score;
2620 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2622 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2623 gfx.anim_random_frame = last_anim_random_frame;
2626 else if (gpc->type == TYPE_GRAPHIC)
2628 if (gpc->graphic != IMG_UNDEFINED)
2630 int last_anim_random_frame = gfx.anim_random_frame;
2631 int graphic = gpc->graphic;
2632 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2634 graphic_info[graphic].anim_global_anim_sync ?
2635 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2637 if (gpc->value != gpc->last_value)
2640 gpc->gfx_random = init_gfx_random;
2646 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2647 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2648 gpc->gfx_random = init_gfx_random;
2651 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2652 gfx.anim_random_frame = gpc->gfx_random;
2654 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2656 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2657 gfx.anim_random_frame = last_anim_random_frame;
2663 static void DisplayGameControlValues(void)
2665 boolean redraw_panel = FALSE;
2668 for (i = 0; game_panel_controls[i].nr != -1; i++)
2670 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2672 if (PANEL_DEACTIVATED(gpc->pos))
2675 if (gpc->value == gpc->last_value &&
2676 gpc->frame == gpc->last_frame)
2679 redraw_panel = TRUE;
2685 // copy default game door content to main double buffer
2687 // !!! CHECK AGAIN !!!
2688 SetPanelBackground();
2689 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2690 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2692 // redraw game control buttons
2693 RedrawGameButtons();
2695 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2697 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2699 int nr = game_panel_order[i].nr;
2700 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2701 struct TextPosInfo *pos = gpc->pos;
2702 int type = gpc->type;
2703 int value = gpc->value;
2704 int frame = gpc->frame;
2705 int size = pos->size;
2706 int font = pos->font;
2707 boolean draw_masked = pos->draw_masked;
2708 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2710 if (PANEL_DEACTIVATED(pos))
2713 if (pos->class == get_hash_from_key("extra_panel_items") &&
2714 !setup.prefer_extra_panel_items)
2717 gpc->last_value = value;
2718 gpc->last_frame = frame;
2720 if (type == TYPE_INTEGER)
2722 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2723 nr == GAME_PANEL_INVENTORY_COUNT ||
2724 nr == GAME_PANEL_SCORE ||
2725 nr == GAME_PANEL_HIGHSCORE ||
2726 nr == GAME_PANEL_TIME)
2728 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2730 if (use_dynamic_size) // use dynamic number of digits
2732 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 :
2733 nr == GAME_PANEL_INVENTORY_COUNT ||
2734 nr == GAME_PANEL_TIME ? 1000 : 100000);
2735 int size_add = (nr == GAME_PANEL_LEVEL_NUMBER ||
2736 nr == GAME_PANEL_INVENTORY_COUNT ||
2737 nr == GAME_PANEL_TIME ? 1 : 2);
2738 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 :
2739 nr == GAME_PANEL_INVENTORY_COUNT ||
2740 nr == GAME_PANEL_TIME ? 3 : 5);
2741 int size2 = size1 + size_add;
2742 int font1 = pos->font;
2743 int font2 = pos->font_alt;
2745 size = (value < value_change ? size1 : size2);
2746 font = (value < value_change ? font1 : font2);
2750 // correct text size if "digits" is zero or less
2752 size = strlen(int2str(value, size));
2754 // dynamically correct text alignment
2755 pos->width = size * getFontWidth(font);
2757 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2758 int2str(value, size), font, mask_mode);
2760 else if (type == TYPE_ELEMENT)
2762 int element, graphic;
2766 int dst_x = PANEL_XPOS(pos);
2767 int dst_y = PANEL_YPOS(pos);
2769 if (value != EL_UNDEFINED && value != EL_EMPTY)
2772 graphic = el2panelimg(value);
2775 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2776 element, EL_NAME(element), size);
2779 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2782 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2785 width = graphic_info[graphic].width * size / TILESIZE;
2786 height = graphic_info[graphic].height * size / TILESIZE;
2789 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2792 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2796 else if (type == TYPE_GRAPHIC)
2798 int graphic = gpc->graphic;
2799 int graphic_active = gpc->graphic_active;
2803 int dst_x = PANEL_XPOS(pos);
2804 int dst_y = PANEL_YPOS(pos);
2805 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2806 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2808 if (graphic != IMG_UNDEFINED && !skip)
2810 if (pos->style == STYLE_REVERSE)
2811 value = 100 - value;
2813 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2815 if (pos->direction & MV_HORIZONTAL)
2817 width = graphic_info[graphic_active].width * value / 100;
2818 height = graphic_info[graphic_active].height;
2820 if (pos->direction == MV_LEFT)
2822 src_x += graphic_info[graphic_active].width - width;
2823 dst_x += graphic_info[graphic_active].width - width;
2828 width = graphic_info[graphic_active].width;
2829 height = graphic_info[graphic_active].height * value / 100;
2831 if (pos->direction == MV_UP)
2833 src_y += graphic_info[graphic_active].height - height;
2834 dst_y += graphic_info[graphic_active].height - height;
2839 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2842 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2845 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2847 if (pos->direction & MV_HORIZONTAL)
2849 if (pos->direction == MV_RIGHT)
2856 dst_x = PANEL_XPOS(pos);
2859 width = graphic_info[graphic].width - width;
2863 if (pos->direction == MV_DOWN)
2870 dst_y = PANEL_YPOS(pos);
2873 height = graphic_info[graphic].height - height;
2877 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2880 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2884 else if (type == TYPE_STRING)
2886 boolean active = (value != 0);
2887 char *state_normal = "off";
2888 char *state_active = "on";
2889 char *state = (active ? state_active : state_normal);
2890 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2891 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2892 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2893 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2895 if (nr == GAME_PANEL_GRAVITY_STATE)
2897 int font1 = pos->font; // (used for normal state)
2898 int font2 = pos->font_alt; // (used for active state)
2900 font = (active ? font2 : font1);
2909 // don't truncate output if "chars" is zero or less
2912 // dynamically correct text alignment
2913 pos->width = size * getFontWidth(font);
2916 s_cut = getStringCopyN(s, size);
2918 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2919 s_cut, font, mask_mode);
2925 redraw_mask |= REDRAW_DOOR_1;
2928 SetGameStatus(GAME_MODE_PLAYING);
2931 void UpdateAndDisplayGameControlValues(void)
2933 if (tape.deactivate_display)
2936 UpdateGameControlValues();
2937 DisplayGameControlValues();
2940 void UpdateGameDoorValues(void)
2942 UpdateGameControlValues();
2945 void DrawGameDoorValues(void)
2947 DisplayGameControlValues();
2951 // ============================================================================
2953 // ----------------------------------------------------------------------------
2954 // initialize game engine due to level / tape version number
2955 // ============================================================================
2957 static void InitGameEngine(void)
2959 int i, j, k, l, x, y;
2961 // set game engine from tape file when re-playing, else from level file
2962 game.engine_version = (tape.playing ? tape.engine_version :
2963 level.game_version);
2965 // set single or multi-player game mode (needed for re-playing tapes)
2966 game.team_mode = setup.team_mode;
2970 int num_players = 0;
2972 for (i = 0; i < MAX_PLAYERS; i++)
2973 if (tape.player_participates[i])
2976 // multi-player tapes contain input data for more than one player
2977 game.team_mode = (num_players > 1);
2981 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2982 level.game_version);
2983 Debug("game:init:level", " tape.file_version == %06d",
2985 Debug("game:init:level", " tape.game_version == %06d",
2987 Debug("game:init:level", " tape.engine_version == %06d",
2988 tape.engine_version);
2989 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2990 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2993 // --------------------------------------------------------------------------
2994 // set flags for bugs and changes according to active game engine version
2995 // --------------------------------------------------------------------------
2999 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
3001 Bug was introduced in version:
3004 Bug was fixed in version:
3008 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
3009 but the property "can fall" was missing, which caused some levels to be
3010 unsolvable. This was fixed in version 4.2.0.0.
3012 Affected levels/tapes:
3013 An example for a tape that was fixed by this bugfix is tape 029 from the
3014 level set "rnd_sam_bateman".
3015 The wrong behaviour will still be used for all levels or tapes that were
3016 created/recorded with it. An example for this is tape 023 from the level
3017 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
3020 boolean use_amoeba_dropping_cannot_fall_bug =
3021 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
3022 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
3024 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3025 tape.game_version < VERSION_IDENT(4,2,0,0)));
3028 Summary of bugfix/change:
3029 Fixed move speed of elements entering or leaving magic wall.
3031 Fixed/changed in version:
3035 Before 2.0.1, move speed of elements entering or leaving magic wall was
3036 twice as fast as it is now.
3037 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
3039 Affected levels/tapes:
3040 The first condition is generally needed for all levels/tapes before version
3041 2.0.1, which might use the old behaviour before it was changed; known tapes
3042 that are affected: Tape 014 from the level set "rnd_conor_mancone".
3043 The second condition is an exception from the above case and is needed for
3044 the special case of tapes recorded with game (not engine!) version 2.0.1 or
3045 above, but before it was known that this change would break tapes like the
3046 above and was fixed in 4.2.0.0, so that the changed behaviour was active
3047 although the engine version while recording maybe was before 2.0.1. There
3048 are a lot of tapes that are affected by this exception, like tape 006 from
3049 the level set "rnd_conor_mancone".
3052 boolean use_old_move_stepsize_for_magic_wall =
3053 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
3055 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3056 tape.game_version < VERSION_IDENT(4,2,0,0)));
3059 Summary of bugfix/change:
3060 Fixed handling for custom elements that change when pushed by the player.
3062 Fixed/changed in version:
3066 Before 3.1.0, custom elements that "change when pushing" changed directly
3067 after the player started pushing them (until then handled in "DigField()").
3068 Since 3.1.0, these custom elements are not changed until the "pushing"
3069 move of the element is finished (now handled in "ContinueMoving()").
3071 Affected levels/tapes:
3072 The first condition is generally needed for all levels/tapes before version
3073 3.1.0, which might use the old behaviour before it was changed; known tapes
3074 that are affected are some tapes from the level set "Walpurgis Gardens" by
3076 The second condition is an exception from the above case and is needed for
3077 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3078 above (including some development versions of 3.1.0), but before it was
3079 known that this change would break tapes like the above and was fixed in
3080 3.1.1, so that the changed behaviour was active although the engine version
3081 while recording maybe was before 3.1.0. There is at least one tape that is
3082 affected by this exception, which is the tape for the one-level set "Bug
3083 Machine" by Juergen Bonhagen.
3086 game.use_change_when_pushing_bug =
3087 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3089 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3090 tape.game_version < VERSION_IDENT(3,1,1,0)));
3093 Summary of bugfix/change:
3094 Fixed handling for blocking the field the player leaves when moving.
3096 Fixed/changed in version:
3100 Before 3.1.1, when "block last field when moving" was enabled, the field
3101 the player is leaving when moving was blocked for the time of the move,
3102 and was directly unblocked afterwards. This resulted in the last field
3103 being blocked for exactly one less than the number of frames of one player
3104 move. Additionally, even when blocking was disabled, the last field was
3105 blocked for exactly one frame.
3106 Since 3.1.1, due to changes in player movement handling, the last field
3107 is not blocked at all when blocking is disabled. When blocking is enabled,
3108 the last field is blocked for exactly the number of frames of one player
3109 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3110 last field is blocked for exactly one more than the number of frames of
3113 Affected levels/tapes:
3114 (!!! yet to be determined -- probably many !!!)
3117 game.use_block_last_field_bug =
3118 (game.engine_version < VERSION_IDENT(3,1,1,0));
3120 /* various special flags and settings for native Emerald Mine game engine */
3122 game_em.use_single_button =
3123 (game.engine_version > VERSION_IDENT(4,0,0,2));
3125 game_em.use_push_delay =
3126 (game.engine_version > VERSION_IDENT(4,3,7,1));
3128 game_em.use_snap_key_bug =
3129 (game.engine_version < VERSION_IDENT(4,0,1,0));
3131 game_em.use_random_bug =
3132 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3134 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3136 game_em.use_old_explosions = use_old_em_engine;
3137 game_em.use_old_android = use_old_em_engine;
3138 game_em.use_old_push_elements = use_old_em_engine;
3139 game_em.use_old_push_into_acid = use_old_em_engine;
3141 game_em.use_wrap_around = !use_old_em_engine;
3143 // --------------------------------------------------------------------------
3145 // set maximal allowed number of custom element changes per game frame
3146 game.max_num_changes_per_frame = 1;
3148 // default scan direction: scan playfield from top/left to bottom/right
3149 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3151 // dynamically adjust element properties according to game engine version
3152 InitElementPropertiesEngine(game.engine_version);
3154 // ---------- initialize special element properties -------------------------
3156 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3157 if (use_amoeba_dropping_cannot_fall_bug)
3158 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3160 // ---------- initialize player's initial move delay ------------------------
3162 // dynamically adjust player properties according to level information
3163 for (i = 0; i < MAX_PLAYERS; i++)
3164 game.initial_move_delay_value[i] =
3165 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3167 // dynamically adjust player properties according to game engine version
3168 for (i = 0; i < MAX_PLAYERS; i++)
3169 game.initial_move_delay[i] =
3170 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3171 game.initial_move_delay_value[i] : 0);
3173 // ---------- initialize player's initial push delay ------------------------
3175 // dynamically adjust player properties according to game engine version
3176 game.initial_push_delay_value =
3177 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3179 // ---------- initialize changing elements ----------------------------------
3181 // initialize changing elements information
3182 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3184 struct ElementInfo *ei = &element_info[i];
3186 // this pointer might have been changed in the level editor
3187 ei->change = &ei->change_page[0];
3189 if (!IS_CUSTOM_ELEMENT(i))
3191 ei->change->target_element = EL_EMPTY_SPACE;
3192 ei->change->delay_fixed = 0;
3193 ei->change->delay_random = 0;
3194 ei->change->delay_frames = 1;
3197 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3199 ei->has_change_event[j] = FALSE;
3201 ei->event_page_nr[j] = 0;
3202 ei->event_page[j] = &ei->change_page[0];
3206 // add changing elements from pre-defined list
3207 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3209 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3210 struct ElementInfo *ei = &element_info[ch_delay->element];
3212 ei->change->target_element = ch_delay->target_element;
3213 ei->change->delay_fixed = ch_delay->change_delay;
3215 ei->change->pre_change_function = ch_delay->pre_change_function;
3216 ei->change->change_function = ch_delay->change_function;
3217 ei->change->post_change_function = ch_delay->post_change_function;
3219 ei->change->can_change = TRUE;
3220 ei->change->can_change_or_has_action = TRUE;
3222 ei->has_change_event[CE_DELAY] = TRUE;
3224 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3225 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3228 // ---------- initialize if element can trigger global animations -----------
3230 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3232 struct ElementInfo *ei = &element_info[i];
3234 ei->has_anim_event = FALSE;
3237 InitGlobalAnimEventsForCustomElements();
3239 // ---------- initialize internal run-time variables ------------------------
3241 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3243 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3245 for (j = 0; j < ei->num_change_pages; j++)
3247 ei->change_page[j].can_change_or_has_action =
3248 (ei->change_page[j].can_change |
3249 ei->change_page[j].has_action);
3253 // add change events from custom element configuration
3254 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3256 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3258 for (j = 0; j < ei->num_change_pages; j++)
3260 if (!ei->change_page[j].can_change_or_has_action)
3263 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3265 // only add event page for the first page found with this event
3266 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3268 ei->has_change_event[k] = TRUE;
3270 ei->event_page_nr[k] = j;
3271 ei->event_page[k] = &ei->change_page[j];
3277 // ---------- initialize reference elements in change conditions ------------
3279 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3281 int element = EL_CUSTOM_START + i;
3282 struct ElementInfo *ei = &element_info[element];
3284 for (j = 0; j < ei->num_change_pages; j++)
3286 int trigger_element = ei->change_page[j].initial_trigger_element;
3288 if (trigger_element >= EL_PREV_CE_8 &&
3289 trigger_element <= EL_NEXT_CE_8)
3290 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3292 ei->change_page[j].trigger_element = trigger_element;
3296 // ---------- initialize run-time trigger player and element ----------------
3298 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3300 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3302 for (j = 0; j < ei->num_change_pages; j++)
3304 struct ElementChangeInfo *change = &ei->change_page[j];
3306 change->actual_trigger_element = EL_EMPTY;
3307 change->actual_trigger_player = EL_EMPTY;
3308 change->actual_trigger_player_bits = CH_PLAYER_NONE;
3309 change->actual_trigger_side = CH_SIDE_NONE;
3310 change->actual_trigger_ce_value = 0;
3311 change->actual_trigger_ce_score = 0;
3312 change->actual_trigger_x = -1;
3313 change->actual_trigger_y = -1;
3317 // ---------- initialize trigger events -------------------------------------
3319 // initialize trigger events information
3320 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3321 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3322 trigger_events[i][j] = FALSE;
3324 // add trigger events from element change event properties
3325 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3327 struct ElementInfo *ei = &element_info[i];
3329 for (j = 0; j < ei->num_change_pages; j++)
3331 struct ElementChangeInfo *change = &ei->change_page[j];
3333 if (!change->can_change_or_has_action)
3336 if (change->has_event[CE_BY_OTHER_ACTION])
3338 int trigger_element = change->trigger_element;
3340 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3342 if (change->has_event[k])
3344 if (IS_GROUP_ELEMENT(trigger_element))
3346 struct ElementGroupInfo *group =
3347 element_info[trigger_element].group;
3349 for (l = 0; l < group->num_elements_resolved; l++)
3350 trigger_events[group->element_resolved[l]][k] = TRUE;
3352 else if (trigger_element == EL_ANY_ELEMENT)
3353 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3354 trigger_events[l][k] = TRUE;
3356 trigger_events[trigger_element][k] = TRUE;
3363 // ---------- initialize push delay -----------------------------------------
3365 // initialize push delay values to default
3366 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3368 if (!IS_CUSTOM_ELEMENT(i))
3370 // set default push delay values (corrected since version 3.0.7-1)
3371 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3373 element_info[i].push_delay_fixed = 2;
3374 element_info[i].push_delay_random = 8;
3378 element_info[i].push_delay_fixed = 8;
3379 element_info[i].push_delay_random = 8;
3384 // set push delay value for certain elements from pre-defined list
3385 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3387 int e = push_delay_list[i].element;
3389 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3390 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3393 // set push delay value for Supaplex elements for newer engine versions
3394 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3396 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3398 if (IS_SP_ELEMENT(i))
3400 // set SP push delay to just enough to push under a falling zonk
3401 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3403 element_info[i].push_delay_fixed = delay;
3404 element_info[i].push_delay_random = 0;
3409 // ---------- initialize move stepsize --------------------------------------
3411 // initialize move stepsize values to default
3412 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3413 if (!IS_CUSTOM_ELEMENT(i))
3414 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3416 // set move stepsize value for certain elements from pre-defined list
3417 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3419 int e = move_stepsize_list[i].element;
3421 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3423 // set move stepsize value for certain elements for older engine versions
3424 if (use_old_move_stepsize_for_magic_wall)
3426 if (e == EL_MAGIC_WALL_FILLING ||
3427 e == EL_MAGIC_WALL_EMPTYING ||
3428 e == EL_BD_MAGIC_WALL_FILLING ||
3429 e == EL_BD_MAGIC_WALL_EMPTYING)
3430 element_info[e].move_stepsize *= 2;
3434 // ---------- initialize collect score --------------------------------------
3436 // initialize collect score values for custom elements from initial value
3437 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3438 if (IS_CUSTOM_ELEMENT(i))
3439 element_info[i].collect_score = element_info[i].collect_score_initial;
3441 // ---------- initialize collect count --------------------------------------
3443 // initialize collect count values for non-custom elements
3444 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3445 if (!IS_CUSTOM_ELEMENT(i))
3446 element_info[i].collect_count_initial = 0;
3448 // add collect count values for all elements from pre-defined list
3449 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3450 element_info[collect_count_list[i].element].collect_count_initial =
3451 collect_count_list[i].count;
3453 // ---------- initialize access direction -----------------------------------
3455 // initialize access direction values to default (access from every side)
3456 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3457 if (!IS_CUSTOM_ELEMENT(i))
3458 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3460 // set access direction value for certain elements from pre-defined list
3461 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3462 element_info[access_direction_list[i].element].access_direction =
3463 access_direction_list[i].direction;
3465 // ---------- initialize explosion content ----------------------------------
3466 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3468 if (IS_CUSTOM_ELEMENT(i))
3471 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3473 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3475 element_info[i].content.e[x][y] =
3476 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3477 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3478 i == EL_PLAYER_3 ? EL_EMERALD :
3479 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3480 i == EL_MOLE ? EL_EMERALD_RED :
3481 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3482 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3483 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3484 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3485 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3486 i == EL_WALL_EMERALD ? EL_EMERALD :
3487 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3488 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3489 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3490 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3491 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3492 i == EL_WALL_PEARL ? EL_PEARL :
3493 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3498 // ---------- initialize recursion detection --------------------------------
3499 recursion_loop_depth = 0;
3500 recursion_loop_detected = FALSE;
3501 recursion_loop_element = EL_UNDEFINED;
3503 // ---------- initialize graphics engine ------------------------------------
3504 game.scroll_delay_value =
3505 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3506 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3507 !setup.forced_scroll_delay ? 0 :
3508 setup.scroll_delay ? setup.scroll_delay_value : 0);
3509 if (game.forced_scroll_delay_value == -1)
3510 game.scroll_delay_value =
3511 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3513 // ---------- initialize game engine snapshots ------------------------------
3514 for (i = 0; i < MAX_PLAYERS; i++)
3515 game.snapshot.last_action[i] = 0;
3516 game.snapshot.changed_action = FALSE;
3517 game.snapshot.collected_item = FALSE;
3518 game.snapshot.mode =
3519 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3520 SNAPSHOT_MODE_EVERY_STEP :
3521 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3522 SNAPSHOT_MODE_EVERY_MOVE :
3523 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3524 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3525 game.snapshot.save_snapshot = FALSE;
3527 // ---------- initialize level time for Supaplex engine ---------------------
3528 // Supaplex levels with time limit currently unsupported -- should be added
3529 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3532 // ---------- initialize flags for handling game actions --------------------
3534 // set flags for game actions to default values
3535 game.use_key_actions = TRUE;
3536 game.use_mouse_actions = FALSE;
3538 // when using Mirror Magic game engine, handle mouse events only
3539 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3541 game.use_key_actions = FALSE;
3542 game.use_mouse_actions = TRUE;
3545 // check for custom elements with mouse click events
3546 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3548 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3550 int element = EL_CUSTOM_START + i;
3552 if (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3553 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3554 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3555 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3556 game.use_mouse_actions = TRUE;
3561 static int get_num_special_action(int element, int action_first,
3564 int num_special_action = 0;
3567 for (i = action_first; i <= action_last; i++)
3569 boolean found = FALSE;
3571 for (j = 0; j < NUM_DIRECTIONS; j++)
3572 if (el_act_dir2img(element, i, j) !=
3573 el_act_dir2img(element, ACTION_DEFAULT, j))
3577 num_special_action++;
3582 return num_special_action;
3586 // ============================================================================
3588 // ----------------------------------------------------------------------------
3589 // initialize and start new game
3590 // ============================================================================
3592 #if DEBUG_INIT_PLAYER
3593 static void DebugPrintPlayerStatus(char *message)
3600 Debug("game:init:player", "%s:", message);
3602 for (i = 0; i < MAX_PLAYERS; i++)
3604 struct PlayerInfo *player = &stored_player[i];
3606 Debug("game:init:player",
3607 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3611 player->connected_locally,
3612 player->connected_network,
3614 (local_player == player ? " (local player)" : ""));
3621 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3622 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3623 int fade_mask = REDRAW_FIELD;
3624 boolean restarting = (game_status == GAME_MODE_PLAYING);
3625 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3626 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3627 int initial_move_dir = MV_DOWN;
3630 // required here to update video display before fading (FIX THIS)
3631 DrawMaskedBorder(REDRAW_DOOR_2);
3633 if (!game.restart_level)
3634 CloseDoor(DOOR_CLOSE_1);
3638 // force fading out global animations displayed during game play
3639 SetGameStatus(GAME_MODE_PSEUDO_RESTARTING);
3643 SetGameStatus(GAME_MODE_PLAYING);
3646 if (level_editor_test_game)
3647 FadeSkipNextFadeOut();
3649 FadeSetEnterScreen();
3652 fade_mask = REDRAW_ALL;
3654 FadeLevelSoundsAndMusic();
3656 ExpireSoundLoops(TRUE);
3662 // force restarting global animations displayed during game play
3663 RestartGlobalAnimsByStatus(GAME_MODE_PSEUDO_RESTARTING);
3665 // this is required for "transforming" fade modes like cross-fading
3666 // (else global animations will be stopped, but not restarted here)
3667 SetAnimStatusBeforeFading(GAME_MODE_PSEUDO_RESTARTING);
3669 SetGameStatus(GAME_MODE_PLAYING);
3672 if (level_editor_test_game)
3673 FadeSkipNextFadeIn();
3675 // needed if different viewport properties defined for playing
3676 ChangeViewportPropertiesIfNeeded();
3680 DrawCompleteVideoDisplay();
3682 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3685 InitGameControlValues();
3689 // initialize tape actions from game when recording tape
3690 tape.use_key_actions = game.use_key_actions;
3691 tape.use_mouse_actions = game.use_mouse_actions;
3693 // initialize visible playfield size when recording tape (for team mode)
3694 tape.scr_fieldx = SCR_FIELDX;
3695 tape.scr_fieldy = SCR_FIELDY;
3698 // don't play tapes over network
3699 network_playing = (network.enabled && !tape.playing);
3701 for (i = 0; i < MAX_PLAYERS; i++)
3703 struct PlayerInfo *player = &stored_player[i];
3705 player->index_nr = i;
3706 player->index_bit = (1 << i);
3707 player->element_nr = EL_PLAYER_1 + i;
3709 player->present = FALSE;
3710 player->active = FALSE;
3711 player->mapped = FALSE;
3713 player->killed = FALSE;
3714 player->reanimated = FALSE;
3715 player->buried = FALSE;
3718 player->effective_action = 0;
3719 player->programmed_action = 0;
3720 player->snap_action = 0;
3722 player->mouse_action.lx = 0;
3723 player->mouse_action.ly = 0;
3724 player->mouse_action.button = 0;
3725 player->mouse_action.button_hint = 0;
3727 player->effective_mouse_action.lx = 0;
3728 player->effective_mouse_action.ly = 0;
3729 player->effective_mouse_action.button = 0;
3730 player->effective_mouse_action.button_hint = 0;
3732 for (j = 0; j < MAX_NUM_KEYS; j++)
3733 player->key[j] = FALSE;
3735 player->num_white_keys = 0;
3737 player->dynabomb_count = 0;
3738 player->dynabomb_size = 1;
3739 player->dynabombs_left = 0;
3740 player->dynabomb_xl = FALSE;
3742 player->MovDir = initial_move_dir;
3745 player->GfxDir = initial_move_dir;
3746 player->GfxAction = ACTION_DEFAULT;
3748 player->StepFrame = 0;
3750 player->initial_element = player->element_nr;
3751 player->artwork_element =
3752 (level.use_artwork_element[i] ? level.artwork_element[i] :
3753 player->element_nr);
3754 player->use_murphy = FALSE;
3756 player->block_last_field = FALSE; // initialized in InitPlayerField()
3757 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3759 player->gravity = level.initial_player_gravity[i];
3761 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3763 player->actual_frame_counter.count = 0;
3764 player->actual_frame_counter.value = 1;
3766 player->step_counter = 0;
3768 player->last_move_dir = initial_move_dir;
3770 player->is_active = FALSE;
3772 player->is_waiting = FALSE;
3773 player->is_moving = FALSE;
3774 player->is_auto_moving = FALSE;
3775 player->is_digging = FALSE;
3776 player->is_snapping = FALSE;
3777 player->is_collecting = FALSE;
3778 player->is_pushing = FALSE;
3779 player->is_switching = FALSE;
3780 player->is_dropping = FALSE;
3781 player->is_dropping_pressed = FALSE;
3783 player->is_bored = FALSE;
3784 player->is_sleeping = FALSE;
3786 player->was_waiting = TRUE;
3787 player->was_moving = FALSE;
3788 player->was_snapping = FALSE;
3789 player->was_dropping = FALSE;
3791 player->force_dropping = FALSE;
3793 player->frame_counter_bored = -1;
3794 player->frame_counter_sleeping = -1;
3796 player->anim_delay_counter = 0;
3797 player->post_delay_counter = 0;
3799 player->dir_waiting = initial_move_dir;
3800 player->action_waiting = ACTION_DEFAULT;
3801 player->last_action_waiting = ACTION_DEFAULT;
3802 player->special_action_bored = ACTION_DEFAULT;
3803 player->special_action_sleeping = ACTION_DEFAULT;
3805 player->switch_x = -1;
3806 player->switch_y = -1;
3808 player->drop_x = -1;
3809 player->drop_y = -1;
3811 player->show_envelope = 0;
3813 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3815 player->push_delay = -1; // initialized when pushing starts
3816 player->push_delay_value = game.initial_push_delay_value;
3818 player->drop_delay = 0;
3819 player->drop_pressed_delay = 0;
3821 player->last_jx = -1;
3822 player->last_jy = -1;
3826 player->shield_normal_time_left = 0;
3827 player->shield_deadly_time_left = 0;
3829 player->last_removed_element = EL_UNDEFINED;
3831 player->inventory_infinite_element = EL_UNDEFINED;
3832 player->inventory_size = 0;
3834 if (level.use_initial_inventory[i])
3836 for (j = 0; j < level.initial_inventory_size[i]; j++)
3838 int element = level.initial_inventory_content[i][j];
3839 int collect_count = element_info[element].collect_count_initial;
3842 if (!IS_CUSTOM_ELEMENT(element))
3845 if (collect_count == 0)
3846 player->inventory_infinite_element = element;
3848 for (k = 0; k < collect_count; k++)
3849 if (player->inventory_size < MAX_INVENTORY_SIZE)
3850 player->inventory_element[player->inventory_size++] = element;
3854 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3855 SnapField(player, 0, 0);
3857 map_player_action[i] = i;
3860 network_player_action_received = FALSE;
3862 // initial null action
3863 if (network_playing)
3864 SendToServer_MovePlayer(MV_NONE);
3869 TimeLeft = level.time;
3874 ScreenMovDir = MV_NONE;
3878 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3880 game.robot_wheel_x = -1;
3881 game.robot_wheel_y = -1;
3886 game.all_players_gone = FALSE;
3888 game.LevelSolved = FALSE;
3889 game.GameOver = FALSE;
3891 game.GamePlayed = !tape.playing;
3893 game.LevelSolved_GameWon = FALSE;
3894 game.LevelSolved_GameEnd = FALSE;
3895 game.LevelSolved_SaveTape = FALSE;
3896 game.LevelSolved_SaveScore = FALSE;
3898 game.LevelSolved_CountingTime = 0;
3899 game.LevelSolved_CountingScore = 0;
3900 game.LevelSolved_CountingHealth = 0;
3902 game.RestartGameRequested = FALSE;
3904 game.panel.active = TRUE;
3906 game.no_level_time_limit = (level.time == 0);
3907 game.time_limit = (leveldir_current->time_limit && setup.time_limit);
3909 game.yamyam_content_nr = 0;
3910 game.robot_wheel_active = FALSE;
3911 game.magic_wall_active = FALSE;
3912 game.magic_wall_time_left = 0;
3913 game.light_time_left = 0;
3914 game.timegate_time_left = 0;
3915 game.switchgate_pos = 0;
3916 game.wind_direction = level.wind_direction_initial;
3918 game.time_final = 0;
3919 game.score_time_final = 0;
3922 game.score_final = 0;
3924 game.health = MAX_HEALTH;
3925 game.health_final = MAX_HEALTH;
3927 game.gems_still_needed = level.gems_needed;
3928 game.sokoban_fields_still_needed = 0;
3929 game.sokoban_objects_still_needed = 0;
3930 game.lights_still_needed = 0;
3931 game.players_still_needed = 0;
3932 game.friends_still_needed = 0;
3934 game.lenses_time_left = 0;
3935 game.magnify_time_left = 0;
3937 game.ball_active = level.ball_active_initial;
3938 game.ball_content_nr = 0;
3940 game.explosions_delayed = TRUE;
3942 game.envelope_active = FALSE;
3944 // special case: set custom artwork setting to initial value
3945 game.use_masked_elements = game.use_masked_elements_initial;
3947 for (i = 0; i < NUM_BELTS; i++)
3949 game.belt_dir[i] = MV_NONE;
3950 game.belt_dir_nr[i] = 3; // not moving, next moving left
3953 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3954 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3956 #if DEBUG_INIT_PLAYER
3957 DebugPrintPlayerStatus("Player status at level initialization");
3960 SCAN_PLAYFIELD(x, y)
3962 Tile[x][y] = Last[x][y] = level.field[x][y];
3963 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3964 ChangeDelay[x][y] = 0;
3965 ChangePage[x][y] = -1;
3966 CustomValue[x][y] = 0; // initialized in InitField()
3967 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3969 WasJustMoving[x][y] = 0;
3970 WasJustFalling[x][y] = 0;
3971 CheckCollision[x][y] = 0;
3972 CheckImpact[x][y] = 0;
3974 Pushed[x][y] = FALSE;
3976 ChangeCount[x][y] = 0;
3977 ChangeEvent[x][y] = -1;
3979 ExplodePhase[x][y] = 0;
3980 ExplodeDelay[x][y] = 0;
3981 ExplodeField[x][y] = EX_TYPE_NONE;
3983 RunnerVisit[x][y] = 0;
3984 PlayerVisit[x][y] = 0;
3987 GfxRandom[x][y] = INIT_GFX_RANDOM();
3988 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
3989 GfxElement[x][y] = EL_UNDEFINED;
3990 GfxElementEmpty[x][y] = EL_EMPTY;
3991 GfxAction[x][y] = ACTION_DEFAULT;
3992 GfxDir[x][y] = MV_NONE;
3993 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3996 SCAN_PLAYFIELD(x, y)
3998 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
4000 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
4003 InitField(x, y, TRUE);
4005 ResetGfxAnimation(x, y);
4010 // required if level does not contain any "empty space" element
4011 if (element_info[EL_EMPTY].use_gfx_element)
4012 game.use_masked_elements = TRUE;
4014 for (i = 0; i < MAX_PLAYERS; i++)
4016 struct PlayerInfo *player = &stored_player[i];
4018 // set number of special actions for bored and sleeping animation
4019 player->num_special_action_bored =
4020 get_num_special_action(player->artwork_element,
4021 ACTION_BORING_1, ACTION_BORING_LAST);
4022 player->num_special_action_sleeping =
4023 get_num_special_action(player->artwork_element,
4024 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
4027 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
4028 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
4030 // initialize type of slippery elements
4031 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4033 if (!IS_CUSTOM_ELEMENT(i))
4035 // default: elements slip down either to the left or right randomly
4036 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
4038 // SP style elements prefer to slip down on the left side
4039 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
4040 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4042 // BD style elements prefer to slip down on the left side
4043 if (game.emulation == EMU_BOULDERDASH)
4044 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4048 // initialize explosion and ignition delay
4049 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4051 if (!IS_CUSTOM_ELEMENT(i))
4054 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
4055 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
4056 game.emulation == EMU_SUPAPLEX ? 3 : 2);
4057 int last_phase = (num_phase + 1) * delay;
4058 int half_phase = (num_phase / 2) * delay;
4060 element_info[i].explosion_delay = last_phase - 1;
4061 element_info[i].ignition_delay = half_phase;
4063 if (i == EL_BLACK_ORB)
4064 element_info[i].ignition_delay = 1;
4068 // correct non-moving belts to start moving left
4069 for (i = 0; i < NUM_BELTS; i++)
4070 if (game.belt_dir[i] == MV_NONE)
4071 game.belt_dir_nr[i] = 3; // not moving, next moving left
4073 #if USE_NEW_PLAYER_ASSIGNMENTS
4074 // use preferred player also in local single-player mode
4075 if (!network.enabled && !game.team_mode)
4077 int new_index_nr = setup.network_player_nr;
4079 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
4081 for (i = 0; i < MAX_PLAYERS; i++)
4082 stored_player[i].connected_locally = FALSE;
4084 stored_player[new_index_nr].connected_locally = TRUE;
4088 for (i = 0; i < MAX_PLAYERS; i++)
4090 stored_player[i].connected = FALSE;
4092 // in network game mode, the local player might not be the first player
4093 if (stored_player[i].connected_locally)
4094 local_player = &stored_player[i];
4097 if (!network.enabled)
4098 local_player->connected = TRUE;
4102 for (i = 0; i < MAX_PLAYERS; i++)
4103 stored_player[i].connected = tape.player_participates[i];
4105 else if (network.enabled)
4107 // add team mode players connected over the network (needed for correct
4108 // assignment of player figures from level to locally playing players)
4110 for (i = 0; i < MAX_PLAYERS; i++)
4111 if (stored_player[i].connected_network)
4112 stored_player[i].connected = TRUE;
4114 else if (game.team_mode)
4116 // try to guess locally connected team mode players (needed for correct
4117 // assignment of player figures from level to locally playing players)
4119 for (i = 0; i < MAX_PLAYERS; i++)
4120 if (setup.input[i].use_joystick ||
4121 setup.input[i].key.left != KSYM_UNDEFINED)
4122 stored_player[i].connected = TRUE;
4125 #if DEBUG_INIT_PLAYER
4126 DebugPrintPlayerStatus("Player status after level initialization");
4129 #if DEBUG_INIT_PLAYER
4130 Debug("game:init:player", "Reassigning players ...");
4133 // check if any connected player was not found in playfield
4134 for (i = 0; i < MAX_PLAYERS; i++)
4136 struct PlayerInfo *player = &stored_player[i];
4138 if (player->connected && !player->present)
4140 struct PlayerInfo *field_player = NULL;
4142 #if DEBUG_INIT_PLAYER
4143 Debug("game:init:player",
4144 "- looking for field player for player %d ...", i + 1);
4147 // assign first free player found that is present in the playfield
4149 // first try: look for unmapped playfield player that is not connected
4150 for (j = 0; j < MAX_PLAYERS; j++)
4151 if (field_player == NULL &&
4152 stored_player[j].present &&
4153 !stored_player[j].mapped &&
4154 !stored_player[j].connected)
4155 field_player = &stored_player[j];
4157 // second try: look for *any* unmapped playfield player
4158 for (j = 0; j < MAX_PLAYERS; j++)
4159 if (field_player == NULL &&
4160 stored_player[j].present &&
4161 !stored_player[j].mapped)
4162 field_player = &stored_player[j];
4164 if (field_player != NULL)
4166 int jx = field_player->jx, jy = field_player->jy;
4168 #if DEBUG_INIT_PLAYER
4169 Debug("game:init:player", "- found player %d",
4170 field_player->index_nr + 1);
4173 player->present = FALSE;
4174 player->active = FALSE;
4176 field_player->present = TRUE;
4177 field_player->active = TRUE;
4180 player->initial_element = field_player->initial_element;
4181 player->artwork_element = field_player->artwork_element;
4183 player->block_last_field = field_player->block_last_field;
4184 player->block_delay_adjustment = field_player->block_delay_adjustment;
4187 StorePlayer[jx][jy] = field_player->element_nr;
4189 field_player->jx = field_player->last_jx = jx;
4190 field_player->jy = field_player->last_jy = jy;
4192 if (local_player == player)
4193 local_player = field_player;
4195 map_player_action[field_player->index_nr] = i;
4197 field_player->mapped = TRUE;
4199 #if DEBUG_INIT_PLAYER
4200 Debug("game:init:player", "- map_player_action[%d] == %d",
4201 field_player->index_nr + 1, i + 1);
4206 if (player->connected && player->present)
4207 player->mapped = TRUE;
4210 #if DEBUG_INIT_PLAYER
4211 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4216 // check if any connected player was not found in playfield
4217 for (i = 0; i < MAX_PLAYERS; i++)
4219 struct PlayerInfo *player = &stored_player[i];
4221 if (player->connected && !player->present)
4223 for (j = 0; j < MAX_PLAYERS; j++)
4225 struct PlayerInfo *field_player = &stored_player[j];
4226 int jx = field_player->jx, jy = field_player->jy;
4228 // assign first free player found that is present in the playfield
4229 if (field_player->present && !field_player->connected)
4231 player->present = TRUE;
4232 player->active = TRUE;
4234 field_player->present = FALSE;
4235 field_player->active = FALSE;
4237 player->initial_element = field_player->initial_element;
4238 player->artwork_element = field_player->artwork_element;
4240 player->block_last_field = field_player->block_last_field;
4241 player->block_delay_adjustment = field_player->block_delay_adjustment;
4243 StorePlayer[jx][jy] = player->element_nr;
4245 player->jx = player->last_jx = jx;
4246 player->jy = player->last_jy = jy;
4256 Debug("game:init:player", "local_player->present == %d",
4257 local_player->present);
4260 // set focus to local player for network games, else to all players
4261 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4262 game.centered_player_nr_next = game.centered_player_nr;
4263 game.set_centered_player = FALSE;
4264 game.set_centered_player_wrap = FALSE;
4266 if (network_playing && tape.recording)
4268 // store client dependent player focus when recording network games
4269 tape.centered_player_nr_next = game.centered_player_nr_next;
4270 tape.set_centered_player = TRUE;
4275 // when playing a tape, eliminate all players who do not participate
4277 #if USE_NEW_PLAYER_ASSIGNMENTS
4279 if (!game.team_mode)
4281 for (i = 0; i < MAX_PLAYERS; i++)
4283 if (stored_player[i].active &&
4284 !tape.player_participates[map_player_action[i]])
4286 struct PlayerInfo *player = &stored_player[i];
4287 int jx = player->jx, jy = player->jy;
4289 #if DEBUG_INIT_PLAYER
4290 Debug("game:init:player", "Removing player %d at (%d, %d)",
4294 player->active = FALSE;
4295 StorePlayer[jx][jy] = 0;
4296 Tile[jx][jy] = EL_EMPTY;
4303 for (i = 0; i < MAX_PLAYERS; i++)
4305 if (stored_player[i].active &&
4306 !tape.player_participates[i])
4308 struct PlayerInfo *player = &stored_player[i];
4309 int jx = player->jx, jy = player->jy;
4311 player->active = FALSE;
4312 StorePlayer[jx][jy] = 0;
4313 Tile[jx][jy] = EL_EMPTY;
4318 else if (!network.enabled && !game.team_mode) // && !tape.playing
4320 // when in single player mode, eliminate all but the local player
4322 for (i = 0; i < MAX_PLAYERS; i++)
4324 struct PlayerInfo *player = &stored_player[i];
4326 if (player->active && player != local_player)
4328 int jx = player->jx, jy = player->jy;
4330 player->active = FALSE;
4331 player->present = FALSE;
4333 StorePlayer[jx][jy] = 0;
4334 Tile[jx][jy] = EL_EMPTY;
4339 for (i = 0; i < MAX_PLAYERS; i++)
4340 if (stored_player[i].active)
4341 game.players_still_needed++;
4343 if (level.solved_by_one_player)
4344 game.players_still_needed = 1;
4346 // when recording the game, store which players take part in the game
4349 #if USE_NEW_PLAYER_ASSIGNMENTS
4350 for (i = 0; i < MAX_PLAYERS; i++)
4351 if (stored_player[i].connected)
4352 tape.player_participates[i] = TRUE;
4354 for (i = 0; i < MAX_PLAYERS; i++)
4355 if (stored_player[i].active)
4356 tape.player_participates[i] = TRUE;
4360 #if DEBUG_INIT_PLAYER
4361 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4364 if (BorderElement == EL_EMPTY)
4367 SBX_Right = lev_fieldx - SCR_FIELDX;
4369 SBY_Lower = lev_fieldy - SCR_FIELDY;
4374 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4376 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4379 if (full_lev_fieldx <= SCR_FIELDX)
4380 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4381 if (full_lev_fieldy <= SCR_FIELDY)
4382 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4384 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4386 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4389 // if local player not found, look for custom element that might create
4390 // the player (make some assumptions about the right custom element)
4391 if (!local_player->present)
4393 int start_x = 0, start_y = 0;
4394 int found_rating = 0;
4395 int found_element = EL_UNDEFINED;
4396 int player_nr = local_player->index_nr;
4398 SCAN_PLAYFIELD(x, y)
4400 int element = Tile[x][y];
4405 if (level.use_start_element[player_nr] &&
4406 level.start_element[player_nr] == element &&
4413 found_element = element;
4416 if (!IS_CUSTOM_ELEMENT(element))
4419 if (CAN_CHANGE(element))
4421 for (i = 0; i < element_info[element].num_change_pages; i++)
4423 // check for player created from custom element as single target
4424 content = element_info[element].change_page[i].target_element;
4425 is_player = IS_PLAYER_ELEMENT(content);
4427 if (is_player && (found_rating < 3 ||
4428 (found_rating == 3 && element < found_element)))
4434 found_element = element;
4439 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4441 // check for player created from custom element as explosion content
4442 content = element_info[element].content.e[xx][yy];
4443 is_player = IS_PLAYER_ELEMENT(content);
4445 if (is_player && (found_rating < 2 ||
4446 (found_rating == 2 && element < found_element)))
4448 start_x = x + xx - 1;
4449 start_y = y + yy - 1;
4452 found_element = element;
4455 if (!CAN_CHANGE(element))
4458 for (i = 0; i < element_info[element].num_change_pages; i++)
4460 // check for player created from custom element as extended target
4462 element_info[element].change_page[i].target_content.e[xx][yy];
4464 is_player = IS_PLAYER_ELEMENT(content);
4466 if (is_player && (found_rating < 1 ||
4467 (found_rating == 1 && element < found_element)))
4469 start_x = x + xx - 1;
4470 start_y = y + yy - 1;
4473 found_element = element;
4479 scroll_x = SCROLL_POSITION_X(start_x);
4480 scroll_y = SCROLL_POSITION_Y(start_y);
4484 scroll_x = SCROLL_POSITION_X(local_player->jx);
4485 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4488 if (game.forced_scroll_x != ARG_UNDEFINED_VALUE)
4489 scroll_x = game.forced_scroll_x;
4490 if (game.forced_scroll_y != ARG_UNDEFINED_VALUE)
4491 scroll_y = game.forced_scroll_y;
4493 // !!! FIX THIS (START) !!!
4494 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4496 InitGameEngine_EM();
4498 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4500 InitGameEngine_SP();
4502 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4504 InitGameEngine_MM();
4508 DrawLevel(REDRAW_FIELD);
4511 // after drawing the level, correct some elements
4512 if (game.timegate_time_left == 0)
4513 CloseAllOpenTimegates();
4516 // blit playfield from scroll buffer to normal back buffer for fading in
4517 BlitScreenToBitmap(backbuffer);
4518 // !!! FIX THIS (END) !!!
4520 DrawMaskedBorder(fade_mask);
4525 // full screen redraw is required at this point in the following cases:
4526 // - special editor door undrawn when game was started from level editor
4527 // - drawing area (playfield) was changed and has to be removed completely
4528 redraw_mask = REDRAW_ALL;
4532 if (!game.restart_level)
4534 // copy default game door content to main double buffer
4536 // !!! CHECK AGAIN !!!
4537 SetPanelBackground();
4538 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4539 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4542 SetPanelBackground();
4543 SetDrawBackgroundMask(REDRAW_DOOR_1);
4545 UpdateAndDisplayGameControlValues();
4547 if (!game.restart_level)
4553 CreateGameButtons();
4558 // copy actual game door content to door double buffer for OpenDoor()
4559 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4561 OpenDoor(DOOR_OPEN_ALL);
4563 KeyboardAutoRepeatOffUnlessAutoplay();
4565 #if DEBUG_INIT_PLAYER
4566 DebugPrintPlayerStatus("Player status (final)");
4575 if (!game.restart_level && !tape.playing)
4577 LevelStats_incPlayed(level_nr);
4579 SaveLevelSetup_SeriesInfo();
4582 game.restart_level = FALSE;
4583 game.request_active = FALSE;
4585 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4586 InitGameActions_MM();
4588 SaveEngineSnapshotToListInitial();
4590 if (!game.restart_level)
4592 PlaySound(SND_GAME_STARTING);
4594 if (setup.sound_music)
4598 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4601 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4602 int actual_player_x, int actual_player_y)
4604 // this is used for non-R'n'D game engines to update certain engine values
4606 // needed to determine if sounds are played within the visible screen area
4607 scroll_x = actual_scroll_x;
4608 scroll_y = actual_scroll_y;
4610 // needed to get player position for "follow finger" playing input method
4611 local_player->jx = actual_player_x;
4612 local_player->jy = actual_player_y;
4615 void InitMovDir(int x, int y)
4617 int i, element = Tile[x][y];
4618 static int xy[4][2] =
4625 static int direction[3][4] =
4627 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4628 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4629 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4638 Tile[x][y] = EL_BUG;
4639 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4642 case EL_SPACESHIP_RIGHT:
4643 case EL_SPACESHIP_UP:
4644 case EL_SPACESHIP_LEFT:
4645 case EL_SPACESHIP_DOWN:
4646 Tile[x][y] = EL_SPACESHIP;
4647 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4650 case EL_BD_BUTTERFLY_RIGHT:
4651 case EL_BD_BUTTERFLY_UP:
4652 case EL_BD_BUTTERFLY_LEFT:
4653 case EL_BD_BUTTERFLY_DOWN:
4654 Tile[x][y] = EL_BD_BUTTERFLY;
4655 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4658 case EL_BD_FIREFLY_RIGHT:
4659 case EL_BD_FIREFLY_UP:
4660 case EL_BD_FIREFLY_LEFT:
4661 case EL_BD_FIREFLY_DOWN:
4662 Tile[x][y] = EL_BD_FIREFLY;
4663 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4666 case EL_PACMAN_RIGHT:
4668 case EL_PACMAN_LEFT:
4669 case EL_PACMAN_DOWN:
4670 Tile[x][y] = EL_PACMAN;
4671 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4674 case EL_YAMYAM_LEFT:
4675 case EL_YAMYAM_RIGHT:
4677 case EL_YAMYAM_DOWN:
4678 Tile[x][y] = EL_YAMYAM;
4679 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4682 case EL_SP_SNIKSNAK:
4683 MovDir[x][y] = MV_UP;
4686 case EL_SP_ELECTRON:
4687 MovDir[x][y] = MV_LEFT;
4694 Tile[x][y] = EL_MOLE;
4695 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4698 case EL_SPRING_LEFT:
4699 case EL_SPRING_RIGHT:
4700 Tile[x][y] = EL_SPRING;
4701 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4705 if (IS_CUSTOM_ELEMENT(element))
4707 struct ElementInfo *ei = &element_info[element];
4708 int move_direction_initial = ei->move_direction_initial;
4709 int move_pattern = ei->move_pattern;
4711 if (move_direction_initial == MV_START_PREVIOUS)
4713 if (MovDir[x][y] != MV_NONE)
4716 move_direction_initial = MV_START_AUTOMATIC;
4719 if (move_direction_initial == MV_START_RANDOM)
4720 MovDir[x][y] = 1 << RND(4);
4721 else if (move_direction_initial & MV_ANY_DIRECTION)
4722 MovDir[x][y] = move_direction_initial;
4723 else if (move_pattern == MV_ALL_DIRECTIONS ||
4724 move_pattern == MV_TURNING_LEFT ||
4725 move_pattern == MV_TURNING_RIGHT ||
4726 move_pattern == MV_TURNING_LEFT_RIGHT ||
4727 move_pattern == MV_TURNING_RIGHT_LEFT ||
4728 move_pattern == MV_TURNING_RANDOM)
4729 MovDir[x][y] = 1 << RND(4);
4730 else if (move_pattern == MV_HORIZONTAL)
4731 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4732 else if (move_pattern == MV_VERTICAL)
4733 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4734 else if (move_pattern & MV_ANY_DIRECTION)
4735 MovDir[x][y] = element_info[element].move_pattern;
4736 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4737 move_pattern == MV_ALONG_RIGHT_SIDE)
4739 // use random direction as default start direction
4740 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4741 MovDir[x][y] = 1 << RND(4);
4743 for (i = 0; i < NUM_DIRECTIONS; i++)
4745 int x1 = x + xy[i][0];
4746 int y1 = y + xy[i][1];
4748 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4750 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4751 MovDir[x][y] = direction[0][i];
4753 MovDir[x][y] = direction[1][i];
4762 MovDir[x][y] = 1 << RND(4);
4764 if (element != EL_BUG &&
4765 element != EL_SPACESHIP &&
4766 element != EL_BD_BUTTERFLY &&
4767 element != EL_BD_FIREFLY)
4770 for (i = 0; i < NUM_DIRECTIONS; i++)
4772 int x1 = x + xy[i][0];
4773 int y1 = y + xy[i][1];
4775 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4777 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4779 MovDir[x][y] = direction[0][i];
4782 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4783 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4785 MovDir[x][y] = direction[1][i];
4794 GfxDir[x][y] = MovDir[x][y];
4797 void InitAmoebaNr(int x, int y)
4800 int group_nr = AmoebaNeighbourNr(x, y);
4804 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4806 if (AmoebaCnt[i] == 0)
4814 AmoebaNr[x][y] = group_nr;
4815 AmoebaCnt[group_nr]++;
4816 AmoebaCnt2[group_nr]++;
4819 static void LevelSolved_SetFinalGameValues(void)
4821 game.time_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.time_played :
4822 game.no_level_time_limit ? TimePlayed : TimeLeft);
4823 game.score_time_final = (level.use_step_counter ? TimePlayed :
4824 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4826 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.score :
4827 level.game_engine_type == GAME_ENGINE_TYPE_EM ? game_em.lev->score :
4828 level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.score :
4831 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4832 MM_HEALTH(game_mm.laser_overload_value) :
4835 game.LevelSolved_CountingTime = game.time_final;
4836 game.LevelSolved_CountingScore = game.score_final;
4837 game.LevelSolved_CountingHealth = game.health_final;
4840 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4842 game.LevelSolved_CountingTime = time;
4843 game.LevelSolved_CountingScore = score;
4844 game.LevelSolved_CountingHealth = health;
4846 game_panel_controls[GAME_PANEL_TIME].value = time;
4847 game_panel_controls[GAME_PANEL_SCORE].value = score;
4848 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4850 DisplayGameControlValues();
4853 static void LevelSolved(void)
4855 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4856 game.players_still_needed > 0)
4859 game.LevelSolved = TRUE;
4860 game.GameOver = TRUE;
4864 // needed here to display correct panel values while player walks into exit
4865 LevelSolved_SetFinalGameValues();
4870 static int time_count_steps;
4871 static int time, time_final;
4872 static float score, score_final; // needed for time score < 10 for 10 seconds
4873 static int health, health_final;
4874 static int game_over_delay_1 = 0;
4875 static int game_over_delay_2 = 0;
4876 static int game_over_delay_3 = 0;
4877 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4878 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4880 if (!game.LevelSolved_GameWon)
4884 // do not start end game actions before the player stops moving (to exit)
4885 if (local_player->active && local_player->MovPos)
4888 // calculate final game values after player finished walking into exit
4889 LevelSolved_SetFinalGameValues();
4891 game.LevelSolved_GameWon = TRUE;
4892 game.LevelSolved_SaveTape = tape.recording;
4893 game.LevelSolved_SaveScore = !tape.playing;
4897 LevelStats_incSolved(level_nr);
4899 SaveLevelSetup_SeriesInfo();
4902 if (tape.auto_play) // tape might already be stopped here
4903 tape.auto_play_level_solved = TRUE;
4907 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4908 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4909 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4911 time = time_final = game.time_final;
4912 score = score_final = game.score_final;
4913 health = health_final = game.health_final;
4915 // update game panel values before (delayed) counting of score (if any)
4916 LevelSolved_DisplayFinalGameValues(time, score, health);
4918 // if level has time score defined, calculate new final game values
4921 int time_final_max = 999;
4922 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4923 int time_frames = 0;
4924 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4925 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4930 time_frames = time_frames_left;
4932 else if (game.no_level_time_limit && TimePlayed < time_final_max)
4934 time_final = time_final_max;
4935 time_frames = time_frames_final_max - time_frames_played;
4938 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4940 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4942 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
4944 // keep previous values (final values already processed here)
4946 score_final = score;
4948 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4951 score_final += health * time_score;
4954 game.score_final = score_final;
4955 game.health_final = health_final;
4958 // if not counting score after game, immediately update game panel values
4959 if (level_editor_test_game || !setup.count_score_after_game)
4962 score = score_final;
4964 LevelSolved_DisplayFinalGameValues(time, score, health);
4967 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4969 // check if last player has left the level
4970 if (game.exit_x >= 0 &&
4973 int x = game.exit_x;
4974 int y = game.exit_y;
4975 int element = Tile[x][y];
4977 // close exit door after last player
4978 if ((game.all_players_gone &&
4979 (element == EL_EXIT_OPEN ||
4980 element == EL_SP_EXIT_OPEN ||
4981 element == EL_STEEL_EXIT_OPEN)) ||
4982 element == EL_EM_EXIT_OPEN ||
4983 element == EL_EM_STEEL_EXIT_OPEN)
4987 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4988 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4989 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4990 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4991 EL_EM_STEEL_EXIT_CLOSING);
4993 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4996 // player disappears
4997 DrawLevelField(x, y);
5000 for (i = 0; i < MAX_PLAYERS; i++)
5002 struct PlayerInfo *player = &stored_player[i];
5004 if (player->present)
5006 RemovePlayer(player);
5008 // player disappears
5009 DrawLevelField(player->jx, player->jy);
5014 PlaySound(SND_GAME_WINNING);
5017 if (setup.count_score_after_game)
5019 if (time != time_final)
5021 if (game_over_delay_1 > 0)
5023 game_over_delay_1--;
5028 int time_to_go = ABS(time_final - time);
5029 int time_count_dir = (time < time_final ? +1 : -1);
5031 if (time_to_go < time_count_steps)
5032 time_count_steps = 1;
5034 time += time_count_steps * time_count_dir;
5035 score += time_count_steps * time_score;
5037 // set final score to correct rounding differences after counting score
5038 if (time == time_final)
5039 score = score_final;
5041 LevelSolved_DisplayFinalGameValues(time, score, health);
5043 if (time == time_final)
5044 StopSound(SND_GAME_LEVELTIME_BONUS);
5045 else if (setup.sound_loops)
5046 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5048 PlaySound(SND_GAME_LEVELTIME_BONUS);
5053 if (health != health_final)
5055 if (game_over_delay_2 > 0)
5057 game_over_delay_2--;
5062 int health_count_dir = (health < health_final ? +1 : -1);
5064 health += health_count_dir;
5065 score += time_score;
5067 LevelSolved_DisplayFinalGameValues(time, score, health);
5069 if (health == health_final)
5070 StopSound(SND_GAME_LEVELTIME_BONUS);
5071 else if (setup.sound_loops)
5072 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5074 PlaySound(SND_GAME_LEVELTIME_BONUS);
5080 game.panel.active = FALSE;
5082 if (game_over_delay_3 > 0)
5084 game_over_delay_3--;
5094 // used instead of "level_nr" (needed for network games)
5095 int last_level_nr = levelset.level_nr;
5096 boolean tape_saved = FALSE;
5098 game.LevelSolved_GameEnd = TRUE;
5100 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5102 // make sure that request dialog to save tape does not open door again
5103 if (!global.use_envelope_request)
5104 CloseDoor(DOOR_CLOSE_1);
5107 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5109 // set unique basename for score tape (also saved in high score table)
5110 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5113 // if no tape is to be saved, close both doors simultaneously
5114 CloseDoor(DOOR_CLOSE_ALL);
5116 if (level_editor_test_game || score_info_tape_play)
5118 SetGameStatus(GAME_MODE_MAIN);
5125 if (!game.LevelSolved_SaveScore)
5127 SetGameStatus(GAME_MODE_MAIN);
5134 if (level_nr == leveldir_current->handicap_level)
5136 leveldir_current->handicap_level++;
5138 SaveLevelSetup_SeriesInfo();
5141 // save score and score tape before potentially erasing tape below
5142 NewHighScore(last_level_nr, tape_saved);
5144 if (setup.increment_levels &&
5145 level_nr < leveldir_current->last_level &&
5148 level_nr++; // advance to next level
5149 TapeErase(); // start with empty tape
5151 if (setup.auto_play_next_level)
5153 scores.continue_playing = TRUE;
5154 scores.next_level_nr = level_nr;
5156 LoadLevel(level_nr);
5158 SaveLevelSetup_SeriesInfo();
5162 if (scores.last_added >= 0 && setup.show_scores_after_game)
5164 SetGameStatus(GAME_MODE_SCORES);
5166 DrawHallOfFame(last_level_nr);
5168 else if (scores.continue_playing)
5170 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5174 SetGameStatus(GAME_MODE_MAIN);
5180 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5181 boolean one_score_entry_per_name)
5185 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5188 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5190 struct ScoreEntry *entry = &list->entry[i];
5191 boolean score_is_better = (new_entry->score > entry->score);
5192 boolean score_is_equal = (new_entry->score == entry->score);
5193 boolean time_is_better = (new_entry->time < entry->time);
5194 boolean time_is_equal = (new_entry->time == entry->time);
5195 boolean better_by_score = (score_is_better ||
5196 (score_is_equal && time_is_better));
5197 boolean better_by_time = (time_is_better ||
5198 (time_is_equal && score_is_better));
5199 boolean is_better = (level.rate_time_over_score ? better_by_time :
5201 boolean entry_is_empty = (entry->score == 0 &&
5204 // prevent adding server score entries if also existing in local score file
5205 // (special case: historic score entries have an empty tape basename entry)
5206 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5207 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5209 // add fields from server score entry not stored in local score entry
5210 // (currently, this means setting platform, version and country fields;
5211 // in rare cases, this may also correct an invalid score value, as
5212 // historic scores might have been truncated to 16-bit values locally)
5213 *entry = *new_entry;
5218 if (is_better || entry_is_empty)
5220 // player has made it to the hall of fame
5222 if (i < MAX_SCORE_ENTRIES - 1)
5224 int m = MAX_SCORE_ENTRIES - 1;
5227 if (one_score_entry_per_name)
5229 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5230 if (strEqual(list->entry[l].name, new_entry->name))
5233 if (m == i) // player's new highscore overwrites his old one
5237 for (l = m; l > i; l--)
5238 list->entry[l] = list->entry[l - 1];
5243 *entry = *new_entry;
5247 else if (one_score_entry_per_name &&
5248 strEqual(entry->name, new_entry->name))
5250 // player already in high score list with better score or time
5256 // special case: new score is beyond the last high score list position
5257 return MAX_SCORE_ENTRIES;
5260 void NewHighScore(int level_nr, boolean tape_saved)
5262 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5263 boolean one_per_name = FALSE;
5265 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5266 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5268 new_entry.score = game.score_final;
5269 new_entry.time = game.score_time_final;
5271 LoadScore(level_nr);
5273 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5275 if (scores.last_added >= MAX_SCORE_ENTRIES)
5277 scores.last_added = MAX_SCORE_ENTRIES - 1;
5278 scores.force_last_added = TRUE;
5280 scores.entry[scores.last_added] = new_entry;
5282 // store last added local score entry (before merging server scores)
5283 scores.last_added_local = scores.last_added;
5288 if (scores.last_added < 0)
5291 SaveScore(level_nr);
5293 // store last added local score entry (before merging server scores)
5294 scores.last_added_local = scores.last_added;
5296 if (!game.LevelSolved_SaveTape)
5299 SaveScoreTape(level_nr);
5301 if (setup.ask_for_using_api_server)
5303 setup.use_api_server =
5304 Request("Upload your score and tape to the high score server?", REQ_ASK);
5306 if (!setup.use_api_server)
5307 Request("Not using high score server! Use setup menu to enable again!",
5310 runtime.use_api_server = setup.use_api_server;
5312 // after asking for using API server once, do not ask again
5313 setup.ask_for_using_api_server = FALSE;
5315 SaveSetup_ServerSetup();
5318 SaveServerScore(level_nr, tape_saved);
5321 void MergeServerScore(void)
5323 struct ScoreEntry last_added_entry;
5324 boolean one_per_name = FALSE;
5327 if (scores.last_added >= 0)
5328 last_added_entry = scores.entry[scores.last_added];
5330 for (i = 0; i < server_scores.num_entries; i++)
5332 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5334 if (pos >= 0 && pos <= scores.last_added)
5335 scores.last_added++;
5338 if (scores.last_added >= MAX_SCORE_ENTRIES)
5340 scores.last_added = MAX_SCORE_ENTRIES - 1;
5341 scores.force_last_added = TRUE;
5343 scores.entry[scores.last_added] = last_added_entry;
5347 static int getElementMoveStepsizeExt(int x, int y, int direction)
5349 int element = Tile[x][y];
5350 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5351 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5352 int horiz_move = (dx != 0);
5353 int sign = (horiz_move ? dx : dy);
5354 int step = sign * element_info[element].move_stepsize;
5356 // special values for move stepsize for spring and things on conveyor belt
5359 if (CAN_FALL(element) &&
5360 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5361 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5362 else if (element == EL_SPRING)
5363 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5369 static int getElementMoveStepsize(int x, int y)
5371 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5374 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5376 if (player->GfxAction != action || player->GfxDir != dir)
5378 player->GfxAction = action;
5379 player->GfxDir = dir;
5381 player->StepFrame = 0;
5385 static void ResetGfxFrame(int x, int y)
5387 // profiling showed that "autotest" spends 10~20% of its time in this function
5388 if (DrawingDeactivatedField())
5391 int element = Tile[x][y];
5392 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5394 if (graphic_info[graphic].anim_global_sync)
5395 GfxFrame[x][y] = FrameCounter;
5396 else if (graphic_info[graphic].anim_global_anim_sync)
5397 GfxFrame[x][y] = getGlobalAnimSyncFrame();
5398 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5399 GfxFrame[x][y] = CustomValue[x][y];
5400 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5401 GfxFrame[x][y] = element_info[element].collect_score;
5402 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5403 GfxFrame[x][y] = ChangeDelay[x][y];
5406 static void ResetGfxAnimation(int x, int y)
5408 GfxAction[x][y] = ACTION_DEFAULT;
5409 GfxDir[x][y] = MovDir[x][y];
5412 ResetGfxFrame(x, y);
5415 static void ResetRandomAnimationValue(int x, int y)
5417 GfxRandom[x][y] = INIT_GFX_RANDOM();
5420 static void InitMovingField(int x, int y, int direction)
5422 int element = Tile[x][y];
5423 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5424 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5427 boolean is_moving_before, is_moving_after;
5429 // check if element was/is moving or being moved before/after mode change
5430 is_moving_before = (WasJustMoving[x][y] != 0);
5431 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5433 // reset animation only for moving elements which change direction of moving
5434 // or which just started or stopped moving
5435 // (else CEs with property "can move" / "not moving" are reset each frame)
5436 if (is_moving_before != is_moving_after ||
5437 direction != MovDir[x][y])
5438 ResetGfxAnimation(x, y);
5440 MovDir[x][y] = direction;
5441 GfxDir[x][y] = direction;
5443 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5444 direction == MV_DOWN && CAN_FALL(element) ?
5445 ACTION_FALLING : ACTION_MOVING);
5447 // this is needed for CEs with property "can move" / "not moving"
5449 if (is_moving_after)
5451 if (Tile[newx][newy] == EL_EMPTY)
5452 Tile[newx][newy] = EL_BLOCKED;
5454 MovDir[newx][newy] = MovDir[x][y];
5456 CustomValue[newx][newy] = CustomValue[x][y];
5458 GfxFrame[newx][newy] = GfxFrame[x][y];
5459 GfxRandom[newx][newy] = GfxRandom[x][y];
5460 GfxAction[newx][newy] = GfxAction[x][y];
5461 GfxDir[newx][newy] = GfxDir[x][y];
5465 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5467 int direction = MovDir[x][y];
5468 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5469 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5475 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5477 int direction = MovDir[x][y];
5478 int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0);
5479 int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0);
5481 *comes_from_x = oldx;
5482 *comes_from_y = oldy;
5485 static int MovingOrBlocked2Element(int x, int y)
5487 int element = Tile[x][y];
5489 if (element == EL_BLOCKED)
5493 Blocked2Moving(x, y, &oldx, &oldy);
5495 return Tile[oldx][oldy];
5501 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5503 // like MovingOrBlocked2Element(), but if element is moving
5504 // and (x, y) is the field the moving element is just leaving,
5505 // return EL_BLOCKED instead of the element value
5506 int element = Tile[x][y];
5508 if (IS_MOVING(x, y))
5510 if (element == EL_BLOCKED)
5514 Blocked2Moving(x, y, &oldx, &oldy);
5515 return Tile[oldx][oldy];
5524 static void RemoveField(int x, int y)
5526 Tile[x][y] = EL_EMPTY;
5532 CustomValue[x][y] = 0;
5535 ChangeDelay[x][y] = 0;
5536 ChangePage[x][y] = -1;
5537 Pushed[x][y] = FALSE;
5539 GfxElement[x][y] = EL_UNDEFINED;
5540 GfxAction[x][y] = ACTION_DEFAULT;
5541 GfxDir[x][y] = MV_NONE;
5544 static void RemoveMovingField(int x, int y)
5546 int oldx = x, oldy = y, newx = x, newy = y;
5547 int element = Tile[x][y];
5548 int next_element = EL_UNDEFINED;
5550 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5553 if (IS_MOVING(x, y))
5555 Moving2Blocked(x, y, &newx, &newy);
5557 if (Tile[newx][newy] != EL_BLOCKED)
5559 // element is moving, but target field is not free (blocked), but
5560 // already occupied by something different (example: acid pool);
5561 // in this case, only remove the moving field, but not the target
5563 RemoveField(oldx, oldy);
5565 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5567 TEST_DrawLevelField(oldx, oldy);
5572 else if (element == EL_BLOCKED)
5574 Blocked2Moving(x, y, &oldx, &oldy);
5575 if (!IS_MOVING(oldx, oldy))
5579 if (element == EL_BLOCKED &&
5580 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5581 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5582 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5583 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5584 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5585 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5586 next_element = get_next_element(Tile[oldx][oldy]);
5588 RemoveField(oldx, oldy);
5589 RemoveField(newx, newy);
5591 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5593 if (next_element != EL_UNDEFINED)
5594 Tile[oldx][oldy] = next_element;
5596 TEST_DrawLevelField(oldx, oldy);
5597 TEST_DrawLevelField(newx, newy);
5600 void DrawDynamite(int x, int y)
5602 int sx = SCREENX(x), sy = SCREENY(y);
5603 int graphic = el2img(Tile[x][y]);
5606 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5609 if (IS_WALKABLE_INSIDE(Back[x][y]))
5613 DrawLevelElement(x, y, Back[x][y]);
5614 else if (Store[x][y])
5615 DrawLevelElement(x, y, Store[x][y]);
5616 else if (game.use_masked_elements)
5617 DrawLevelElement(x, y, EL_EMPTY);
5619 frame = getGraphicAnimationFrameXY(graphic, x, y);
5621 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5622 DrawGraphicThruMask(sx, sy, graphic, frame);
5624 DrawGraphic(sx, sy, graphic, frame);
5627 static void CheckDynamite(int x, int y)
5629 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5633 if (MovDelay[x][y] != 0)
5636 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5642 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5647 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5649 boolean num_checked_players = 0;
5652 for (i = 0; i < MAX_PLAYERS; i++)
5654 if (stored_player[i].active)
5656 int sx = stored_player[i].jx;
5657 int sy = stored_player[i].jy;
5659 if (num_checked_players == 0)
5666 *sx1 = MIN(*sx1, sx);
5667 *sy1 = MIN(*sy1, sy);
5668 *sx2 = MAX(*sx2, sx);
5669 *sy2 = MAX(*sy2, sy);
5672 num_checked_players++;
5677 static boolean checkIfAllPlayersFitToScreen_RND(void)
5679 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5681 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5683 return (sx2 - sx1 < SCR_FIELDX &&
5684 sy2 - sy1 < SCR_FIELDY);
5687 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5689 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5691 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5693 *sx = (sx1 + sx2) / 2;
5694 *sy = (sy1 + sy2) / 2;
5697 static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
5698 boolean center_screen, boolean quick_relocation)
5700 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5701 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5702 boolean no_delay = (tape.warp_forward);
5703 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5704 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5705 int new_scroll_x, new_scroll_y;
5707 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5709 // case 1: quick relocation inside visible screen (without scrolling)
5716 if (!level.shifted_relocation || center_screen)
5718 // relocation _with_ centering of screen
5720 new_scroll_x = SCROLL_POSITION_X(x);
5721 new_scroll_y = SCROLL_POSITION_Y(y);
5725 // relocation _without_ centering of screen
5727 // apply distance between old and new player position to scroll position
5728 int shifted_scroll_x = scroll_x + (x - old_x);
5729 int shifted_scroll_y = scroll_y + (y - old_y);
5731 // make sure that shifted scroll position does not scroll beyond screen
5732 new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
5733 new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
5735 // special case for teleporting from one end of the playfield to the other
5736 // (this kludge prevents the destination area to be shifted by half a tile
5737 // against the source destination for even screen width or screen height;
5738 // probably most useful when used with high "game.forced_scroll_delay_value"
5739 // in combination with "game.forced_scroll_x" and "game.forced_scroll_y")
5740 if (quick_relocation)
5742 if (EVEN(SCR_FIELDX))
5744 // relocate (teleport) between left and right border (half or full)
5745 if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1)
5746 new_scroll_x = SBX_Right;
5747 else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right)
5748 new_scroll_x = SBX_Right - 1;
5749 else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1)
5750 new_scroll_x = SBX_Left;
5751 else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left)
5752 new_scroll_x = SBX_Left + 1;
5755 if (EVEN(SCR_FIELDY))
5757 // relocate (teleport) between top and bottom border (half or full)
5758 if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1)
5759 new_scroll_y = SBY_Lower;
5760 else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower)
5761 new_scroll_y = SBY_Lower - 1;
5762 else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1)
5763 new_scroll_y = SBY_Upper;
5764 else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper)
5765 new_scroll_y = SBY_Upper + 1;
5770 if (quick_relocation)
5772 // case 2: quick relocation (redraw without visible scrolling)
5774 scroll_x = new_scroll_x;
5775 scroll_y = new_scroll_y;
5782 // case 3: visible relocation (with scrolling to new position)
5784 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5786 SetVideoFrameDelay(wait_delay_value);
5788 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5790 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5791 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5793 if (dx == 0 && dy == 0) // no scrolling needed at all
5799 // set values for horizontal/vertical screen scrolling (half tile size)
5800 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5801 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5802 int pos_x = dx * TILEX / 2;
5803 int pos_y = dy * TILEY / 2;
5804 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5805 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5807 ScrollLevel(dx, dy);
5810 // scroll in two steps of half tile size to make things smoother
5811 BlitScreenToBitmapExt_RND(window, fx, fy);
5813 // scroll second step to align at full tile size
5814 BlitScreenToBitmap(window);
5820 SetVideoFrameDelay(frame_delay_value_old);
5823 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5825 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5826 int player_nr = GET_PLAYER_NR(el_player);
5827 struct PlayerInfo *player = &stored_player[player_nr];
5828 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5829 boolean no_delay = (tape.warp_forward);
5830 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5831 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5832 int old_jx = player->jx;
5833 int old_jy = player->jy;
5834 int old_element = Tile[old_jx][old_jy];
5835 int element = Tile[jx][jy];
5836 boolean player_relocated = (old_jx != jx || old_jy != jy);
5838 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5839 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5840 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5841 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5842 int leave_side_horiz = move_dir_horiz;
5843 int leave_side_vert = move_dir_vert;
5844 int enter_side = enter_side_horiz | enter_side_vert;
5845 int leave_side = leave_side_horiz | leave_side_vert;
5847 if (player->buried) // do not reanimate dead player
5850 if (!player_relocated) // no need to relocate the player
5853 if (IS_PLAYER(jx, jy)) // player already placed at new position
5855 RemoveField(jx, jy); // temporarily remove newly placed player
5856 DrawLevelField(jx, jy);
5859 if (player->present)
5861 while (player->MovPos)
5863 ScrollPlayer(player, SCROLL_GO_ON);
5864 ScrollScreen(NULL, SCROLL_GO_ON);
5866 AdvanceFrameAndPlayerCounters(player->index_nr);
5870 BackToFront_WithFrameDelay(wait_delay_value);
5873 DrawPlayer(player); // needed here only to cleanup last field
5874 DrawLevelField(player->jx, player->jy); // remove player graphic
5876 player->is_moving = FALSE;
5879 if (IS_CUSTOM_ELEMENT(old_element))
5880 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5882 player->index_bit, leave_side);
5884 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5886 player->index_bit, leave_side);
5888 Tile[jx][jy] = el_player;
5889 InitPlayerField(jx, jy, el_player, TRUE);
5891 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5892 possible that the relocation target field did not contain a player element,
5893 but a walkable element, to which the new player was relocated -- in this
5894 case, restore that (already initialized!) element on the player field */
5895 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5897 Tile[jx][jy] = element; // restore previously existing element
5900 // only visually relocate centered player
5901 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy,
5902 FALSE, level.instant_relocation);
5904 TestIfPlayerTouchesBadThing(jx, jy);
5905 TestIfPlayerTouchesCustomElement(jx, jy);
5907 if (IS_CUSTOM_ELEMENT(element))
5908 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5909 player->index_bit, enter_side);
5911 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5912 player->index_bit, enter_side);
5914 if (player->is_switching)
5916 /* ensure that relocation while still switching an element does not cause
5917 a new element to be treated as also switched directly after relocation
5918 (this is important for teleporter switches that teleport the player to
5919 a place where another teleporter switch is in the same direction, which
5920 would then incorrectly be treated as immediately switched before the
5921 direction key that caused the switch was released) */
5923 player->switch_x += jx - old_jx;
5924 player->switch_y += jy - old_jy;
5928 static void Explode(int ex, int ey, int phase, int mode)
5934 if (game.explosions_delayed)
5936 ExplodeField[ex][ey] = mode;
5940 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5942 int center_element = Tile[ex][ey];
5943 int ce_value = CustomValue[ex][ey];
5944 int ce_score = element_info[center_element].collect_score;
5945 int artwork_element, explosion_element; // set these values later
5947 // remove things displayed in background while burning dynamite
5948 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5951 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5953 // put moving element to center field (and let it explode there)
5954 center_element = MovingOrBlocked2Element(ex, ey);
5955 RemoveMovingField(ex, ey);
5956 Tile[ex][ey] = center_element;
5959 // now "center_element" is finally determined -- set related values now
5960 artwork_element = center_element; // for custom player artwork
5961 explosion_element = center_element; // for custom player artwork
5963 if (IS_PLAYER(ex, ey))
5965 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5967 artwork_element = stored_player[player_nr].artwork_element;
5969 if (level.use_explosion_element[player_nr])
5971 explosion_element = level.explosion_element[player_nr];
5972 artwork_element = explosion_element;
5976 if (mode == EX_TYPE_NORMAL ||
5977 mode == EX_TYPE_CENTER ||
5978 mode == EX_TYPE_CROSS)
5979 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5981 last_phase = element_info[explosion_element].explosion_delay + 1;
5983 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5985 int xx = x - ex + 1;
5986 int yy = y - ey + 1;
5989 if (!IN_LEV_FIELD(x, y) ||
5990 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5991 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5994 element = Tile[x][y];
5996 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5998 element = MovingOrBlocked2Element(x, y);
6000 if (!IS_EXPLOSION_PROOF(element))
6001 RemoveMovingField(x, y);
6004 // indestructible elements can only explode in center (but not flames)
6005 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
6006 mode == EX_TYPE_BORDER)) ||
6007 element == EL_FLAMES)
6010 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
6011 behaviour, for example when touching a yamyam that explodes to rocks
6012 with active deadly shield, a rock is created under the player !!! */
6013 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
6015 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
6016 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
6017 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
6019 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
6022 if (IS_ACTIVE_BOMB(element))
6024 // re-activate things under the bomb like gate or penguin
6025 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
6032 // save walkable background elements while explosion on same tile
6033 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
6034 (x != ex || y != ey || mode == EX_TYPE_BORDER))
6035 Back[x][y] = element;
6037 // ignite explodable elements reached by other explosion
6038 if (element == EL_EXPLOSION)
6039 element = Store2[x][y];
6041 if (AmoebaNr[x][y] &&
6042 (element == EL_AMOEBA_FULL ||
6043 element == EL_BD_AMOEBA ||
6044 element == EL_AMOEBA_GROWING))
6046 AmoebaCnt[AmoebaNr[x][y]]--;
6047 AmoebaCnt2[AmoebaNr[x][y]]--;
6052 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
6054 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
6056 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
6058 if (PLAYERINFO(ex, ey)->use_murphy)
6059 Store[x][y] = EL_EMPTY;
6062 // !!! check this case -- currently needed for rnd_rado_negundo_v,
6063 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
6064 else if (IS_PLAYER_ELEMENT(center_element))
6065 Store[x][y] = EL_EMPTY;
6066 else if (center_element == EL_YAMYAM)
6067 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
6068 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
6069 Store[x][y] = element_info[center_element].content.e[xx][yy];
6071 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
6072 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
6073 // otherwise) -- FIX THIS !!!
6074 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
6075 Store[x][y] = element_info[element].content.e[1][1];
6077 else if (!CAN_EXPLODE(element))
6078 Store[x][y] = element_info[element].content.e[1][1];
6081 Store[x][y] = EL_EMPTY;
6083 if (IS_CUSTOM_ELEMENT(center_element))
6084 Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value :
6085 Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score :
6086 Store[x][y] >= EL_PREV_CE_8 &&
6087 Store[x][y] <= EL_NEXT_CE_8 ?
6088 RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) :
6091 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
6092 center_element == EL_AMOEBA_TO_DIAMOND)
6093 Store2[x][y] = element;
6095 Tile[x][y] = EL_EXPLOSION;
6096 GfxElement[x][y] = artwork_element;
6098 ExplodePhase[x][y] = 1;
6099 ExplodeDelay[x][y] = last_phase;
6104 if (center_element == EL_YAMYAM)
6105 game.yamyam_content_nr =
6106 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
6118 GfxFrame[x][y] = 0; // restart explosion animation
6120 last_phase = ExplodeDelay[x][y];
6122 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
6124 // this can happen if the player leaves an explosion just in time
6125 if (GfxElement[x][y] == EL_UNDEFINED)
6126 GfxElement[x][y] = EL_EMPTY;
6128 border_element = Store2[x][y];
6129 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6130 border_element = StorePlayer[x][y];
6132 if (phase == element_info[border_element].ignition_delay ||
6133 phase == last_phase)
6135 boolean border_explosion = FALSE;
6137 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6138 !PLAYER_EXPLOSION_PROTECTED(x, y))
6140 KillPlayerUnlessExplosionProtected(x, y);
6141 border_explosion = TRUE;
6143 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6145 Tile[x][y] = Store2[x][y];
6148 border_explosion = TRUE;
6150 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6152 AmoebaToDiamond(x, y);
6154 border_explosion = TRUE;
6157 // if an element just explodes due to another explosion (chain-reaction),
6158 // do not immediately end the new explosion when it was the last frame of
6159 // the explosion (as it would be done in the following "if"-statement!)
6160 if (border_explosion && phase == last_phase)
6164 // this can happen if the player was just killed by an explosion
6165 if (GfxElement[x][y] == EL_UNDEFINED)
6166 GfxElement[x][y] = EL_EMPTY;
6168 if (phase == last_phase)
6172 element = Tile[x][y] = Store[x][y];
6173 Store[x][y] = Store2[x][y] = 0;
6174 GfxElement[x][y] = EL_UNDEFINED;
6176 // player can escape from explosions and might therefore be still alive
6177 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6178 element <= EL_PLAYER_IS_EXPLODING_4)
6180 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6181 int explosion_element = EL_PLAYER_1 + player_nr;
6182 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6183 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6185 if (level.use_explosion_element[player_nr])
6186 explosion_element = level.explosion_element[player_nr];
6188 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6189 element_info[explosion_element].content.e[xx][yy]);
6192 // restore probably existing indestructible background element
6193 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6194 element = Tile[x][y] = Back[x][y];
6197 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6198 GfxDir[x][y] = MV_NONE;
6199 ChangeDelay[x][y] = 0;
6200 ChangePage[x][y] = -1;
6202 CustomValue[x][y] = 0;
6204 InitField_WithBug2(x, y, FALSE);
6206 TEST_DrawLevelField(x, y);
6208 TestIfElementTouchesCustomElement(x, y);
6210 if (GFX_CRUMBLED(element))
6211 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6213 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6214 StorePlayer[x][y] = 0;
6216 if (IS_PLAYER_ELEMENT(element))
6217 RelocatePlayer(x, y, element);
6219 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6221 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6222 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6225 TEST_DrawLevelFieldCrumbled(x, y);
6227 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6229 DrawLevelElement(x, y, Back[x][y]);
6230 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6232 else if (IS_WALKABLE_UNDER(Back[x][y]))
6234 DrawLevelGraphic(x, y, graphic, frame);
6235 DrawLevelElementThruMask(x, y, Back[x][y]);
6237 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6238 DrawLevelGraphic(x, y, graphic, frame);
6242 static void DynaExplode(int ex, int ey)
6245 int dynabomb_element = Tile[ex][ey];
6246 int dynabomb_size = 1;
6247 boolean dynabomb_xl = FALSE;
6248 struct PlayerInfo *player;
6249 struct XY *xy = xy_topdown;
6251 if (IS_ACTIVE_BOMB(dynabomb_element))
6253 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6254 dynabomb_size = player->dynabomb_size;
6255 dynabomb_xl = player->dynabomb_xl;
6256 player->dynabombs_left++;
6259 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6261 for (i = 0; i < NUM_DIRECTIONS; i++)
6263 for (j = 1; j <= dynabomb_size; j++)
6265 int x = ex + j * xy[i].x;
6266 int y = ey + j * xy[i].y;
6269 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6272 element = Tile[x][y];
6274 // do not restart explosions of fields with active bombs
6275 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6278 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6280 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6281 !IS_DIGGABLE(element) && !dynabomb_xl)
6287 void Bang(int x, int y)
6289 int element = MovingOrBlocked2Element(x, y);
6290 int explosion_type = EX_TYPE_NORMAL;
6292 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6294 struct PlayerInfo *player = PLAYERINFO(x, y);
6296 element = Tile[x][y] = player->initial_element;
6298 if (level.use_explosion_element[player->index_nr])
6300 int explosion_element = level.explosion_element[player->index_nr];
6302 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6303 explosion_type = EX_TYPE_CROSS;
6304 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6305 explosion_type = EX_TYPE_CENTER;
6313 case EL_BD_BUTTERFLY:
6316 case EL_DARK_YAMYAM:
6320 RaiseScoreElement(element);
6323 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6324 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6325 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6326 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6327 case EL_DYNABOMB_INCREASE_NUMBER:
6328 case EL_DYNABOMB_INCREASE_SIZE:
6329 case EL_DYNABOMB_INCREASE_POWER:
6330 explosion_type = EX_TYPE_DYNA;
6333 case EL_DC_LANDMINE:
6334 explosion_type = EX_TYPE_CENTER;
6339 case EL_LAMP_ACTIVE:
6340 case EL_AMOEBA_TO_DIAMOND:
6341 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6342 explosion_type = EX_TYPE_CENTER;
6346 if (element_info[element].explosion_type == EXPLODES_CROSS)
6347 explosion_type = EX_TYPE_CROSS;
6348 else if (element_info[element].explosion_type == EXPLODES_1X1)
6349 explosion_type = EX_TYPE_CENTER;
6353 if (explosion_type == EX_TYPE_DYNA)
6356 Explode(x, y, EX_PHASE_START, explosion_type);
6358 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6361 static void SplashAcid(int x, int y)
6363 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6364 (!IN_LEV_FIELD(x - 1, y - 2) ||
6365 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6366 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6368 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6369 (!IN_LEV_FIELD(x + 1, y - 2) ||
6370 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6371 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6373 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6376 static void InitBeltMovement(void)
6378 static int belt_base_element[4] =
6380 EL_CONVEYOR_BELT_1_LEFT,
6381 EL_CONVEYOR_BELT_2_LEFT,
6382 EL_CONVEYOR_BELT_3_LEFT,
6383 EL_CONVEYOR_BELT_4_LEFT
6385 static int belt_base_active_element[4] =
6387 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6388 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6389 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6390 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6395 // set frame order for belt animation graphic according to belt direction
6396 for (i = 0; i < NUM_BELTS; i++)
6400 for (j = 0; j < NUM_BELT_PARTS; j++)
6402 int element = belt_base_active_element[belt_nr] + j;
6403 int graphic_1 = el2img(element);
6404 int graphic_2 = el2panelimg(element);
6406 if (game.belt_dir[i] == MV_LEFT)
6408 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6409 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6413 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6414 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6419 SCAN_PLAYFIELD(x, y)
6421 int element = Tile[x][y];
6423 for (i = 0; i < NUM_BELTS; i++)
6425 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6427 int e_belt_nr = getBeltNrFromBeltElement(element);
6430 if (e_belt_nr == belt_nr)
6432 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6434 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6441 static void ToggleBeltSwitch(int x, int y)
6443 static int belt_base_element[4] =
6445 EL_CONVEYOR_BELT_1_LEFT,
6446 EL_CONVEYOR_BELT_2_LEFT,
6447 EL_CONVEYOR_BELT_3_LEFT,
6448 EL_CONVEYOR_BELT_4_LEFT
6450 static int belt_base_active_element[4] =
6452 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6453 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6454 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6455 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6457 static int belt_base_switch_element[4] =
6459 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6460 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6461 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6462 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6464 static int belt_move_dir[4] =
6472 int element = Tile[x][y];
6473 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6474 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6475 int belt_dir = belt_move_dir[belt_dir_nr];
6478 if (!IS_BELT_SWITCH(element))
6481 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6482 game.belt_dir[belt_nr] = belt_dir;
6484 if (belt_dir_nr == 3)
6487 // set frame order for belt animation graphic according to belt direction
6488 for (i = 0; i < NUM_BELT_PARTS; i++)
6490 int element = belt_base_active_element[belt_nr] + i;
6491 int graphic_1 = el2img(element);
6492 int graphic_2 = el2panelimg(element);
6494 if (belt_dir == MV_LEFT)
6496 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6497 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6501 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6502 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6506 SCAN_PLAYFIELD(xx, yy)
6508 int element = Tile[xx][yy];
6510 if (IS_BELT_SWITCH(element))
6512 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6514 if (e_belt_nr == belt_nr)
6516 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6517 TEST_DrawLevelField(xx, yy);
6520 else if (IS_BELT(element) && belt_dir != MV_NONE)
6522 int e_belt_nr = getBeltNrFromBeltElement(element);
6524 if (e_belt_nr == belt_nr)
6526 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6528 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6529 TEST_DrawLevelField(xx, yy);
6532 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6534 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6536 if (e_belt_nr == belt_nr)
6538 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6540 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6541 TEST_DrawLevelField(xx, yy);
6547 static void ToggleSwitchgateSwitch(void)
6551 game.switchgate_pos = !game.switchgate_pos;
6553 SCAN_PLAYFIELD(xx, yy)
6555 int element = Tile[xx][yy];
6557 if (element == EL_SWITCHGATE_SWITCH_UP)
6559 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6560 TEST_DrawLevelField(xx, yy);
6562 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6564 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6565 TEST_DrawLevelField(xx, yy);
6567 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6569 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6570 TEST_DrawLevelField(xx, yy);
6572 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6574 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6575 TEST_DrawLevelField(xx, yy);
6577 else if (element == EL_SWITCHGATE_OPEN ||
6578 element == EL_SWITCHGATE_OPENING)
6580 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6582 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6584 else if (element == EL_SWITCHGATE_CLOSED ||
6585 element == EL_SWITCHGATE_CLOSING)
6587 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6589 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6594 static int getInvisibleActiveFromInvisibleElement(int element)
6596 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6597 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6598 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6602 static int getInvisibleFromInvisibleActiveElement(int element)
6604 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6605 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6606 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6610 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6614 SCAN_PLAYFIELD(x, y)
6616 int element = Tile[x][y];
6618 if (element == EL_LIGHT_SWITCH &&
6619 game.light_time_left > 0)
6621 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6622 TEST_DrawLevelField(x, y);
6624 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6625 game.light_time_left == 0)
6627 Tile[x][y] = EL_LIGHT_SWITCH;
6628 TEST_DrawLevelField(x, y);
6630 else if (element == EL_EMC_DRIPPER &&
6631 game.light_time_left > 0)
6633 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6634 TEST_DrawLevelField(x, y);
6636 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6637 game.light_time_left == 0)
6639 Tile[x][y] = EL_EMC_DRIPPER;
6640 TEST_DrawLevelField(x, y);
6642 else if (element == EL_INVISIBLE_STEELWALL ||
6643 element == EL_INVISIBLE_WALL ||
6644 element == EL_INVISIBLE_SAND)
6646 if (game.light_time_left > 0)
6647 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6649 TEST_DrawLevelField(x, y);
6651 // uncrumble neighbour fields, if needed
6652 if (element == EL_INVISIBLE_SAND)
6653 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6655 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6656 element == EL_INVISIBLE_WALL_ACTIVE ||
6657 element == EL_INVISIBLE_SAND_ACTIVE)
6659 if (game.light_time_left == 0)
6660 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6662 TEST_DrawLevelField(x, y);
6664 // re-crumble neighbour fields, if needed
6665 if (element == EL_INVISIBLE_SAND)
6666 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6671 static void RedrawAllInvisibleElementsForLenses(void)
6675 SCAN_PLAYFIELD(x, y)
6677 int element = Tile[x][y];
6679 if (element == EL_EMC_DRIPPER &&
6680 game.lenses_time_left > 0)
6682 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6683 TEST_DrawLevelField(x, y);
6685 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6686 game.lenses_time_left == 0)
6688 Tile[x][y] = EL_EMC_DRIPPER;
6689 TEST_DrawLevelField(x, y);
6691 else if (element == EL_INVISIBLE_STEELWALL ||
6692 element == EL_INVISIBLE_WALL ||
6693 element == EL_INVISIBLE_SAND)
6695 if (game.lenses_time_left > 0)
6696 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6698 TEST_DrawLevelField(x, y);
6700 // uncrumble neighbour fields, if needed
6701 if (element == EL_INVISIBLE_SAND)
6702 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6704 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6705 element == EL_INVISIBLE_WALL_ACTIVE ||
6706 element == EL_INVISIBLE_SAND_ACTIVE)
6708 if (game.lenses_time_left == 0)
6709 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6711 TEST_DrawLevelField(x, y);
6713 // re-crumble neighbour fields, if needed
6714 if (element == EL_INVISIBLE_SAND)
6715 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6720 static void RedrawAllInvisibleElementsForMagnifier(void)
6724 SCAN_PLAYFIELD(x, y)
6726 int element = Tile[x][y];
6728 if (element == EL_EMC_FAKE_GRASS &&
6729 game.magnify_time_left > 0)
6731 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6732 TEST_DrawLevelField(x, y);
6734 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6735 game.magnify_time_left == 0)
6737 Tile[x][y] = EL_EMC_FAKE_GRASS;
6738 TEST_DrawLevelField(x, y);
6740 else if (IS_GATE_GRAY(element) &&
6741 game.magnify_time_left > 0)
6743 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6744 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6745 IS_EM_GATE_GRAY(element) ?
6746 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6747 IS_EMC_GATE_GRAY(element) ?
6748 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6749 IS_DC_GATE_GRAY(element) ?
6750 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6752 TEST_DrawLevelField(x, y);
6754 else if (IS_GATE_GRAY_ACTIVE(element) &&
6755 game.magnify_time_left == 0)
6757 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6758 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6759 IS_EM_GATE_GRAY_ACTIVE(element) ?
6760 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6761 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6762 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6763 IS_DC_GATE_GRAY_ACTIVE(element) ?
6764 EL_DC_GATE_WHITE_GRAY :
6766 TEST_DrawLevelField(x, y);
6771 static void ToggleLightSwitch(int x, int y)
6773 int element = Tile[x][y];
6775 game.light_time_left =
6776 (element == EL_LIGHT_SWITCH ?
6777 level.time_light * FRAMES_PER_SECOND : 0);
6779 RedrawAllLightSwitchesAndInvisibleElements();
6782 static void ActivateTimegateSwitch(int x, int y)
6786 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6788 SCAN_PLAYFIELD(xx, yy)
6790 int element = Tile[xx][yy];
6792 if (element == EL_TIMEGATE_CLOSED ||
6793 element == EL_TIMEGATE_CLOSING)
6795 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6796 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6800 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6802 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6803 TEST_DrawLevelField(xx, yy);
6809 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6810 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6813 static void Impact(int x, int y)
6815 boolean last_line = (y == lev_fieldy - 1);
6816 boolean object_hit = FALSE;
6817 boolean impact = (last_line || object_hit);
6818 int element = Tile[x][y];
6819 int smashed = EL_STEELWALL;
6821 if (!last_line) // check if element below was hit
6823 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6826 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6827 MovDir[x][y + 1] != MV_DOWN ||
6828 MovPos[x][y + 1] <= TILEY / 2));
6830 // do not smash moving elements that left the smashed field in time
6831 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6832 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6835 #if USE_QUICKSAND_IMPACT_BUGFIX
6836 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6838 RemoveMovingField(x, y + 1);
6839 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6840 Tile[x][y + 2] = EL_ROCK;
6841 TEST_DrawLevelField(x, y + 2);
6846 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6848 RemoveMovingField(x, y + 1);
6849 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6850 Tile[x][y + 2] = EL_ROCK;
6851 TEST_DrawLevelField(x, y + 2);
6858 smashed = MovingOrBlocked2Element(x, y + 1);
6860 impact = (last_line || object_hit);
6863 if (!last_line && smashed == EL_ACID) // element falls into acid
6865 SplashAcid(x, y + 1);
6869 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6870 // only reset graphic animation if graphic really changes after impact
6872 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6874 ResetGfxAnimation(x, y);
6875 TEST_DrawLevelField(x, y);
6878 if (impact && CAN_EXPLODE_IMPACT(element))
6883 else if (impact && element == EL_PEARL &&
6884 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6886 ResetGfxAnimation(x, y);
6888 Tile[x][y] = EL_PEARL_BREAKING;
6889 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6892 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6894 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6899 if (impact && element == EL_AMOEBA_DROP)
6901 if (object_hit && IS_PLAYER(x, y + 1))
6902 KillPlayerUnlessEnemyProtected(x, y + 1);
6903 else if (object_hit && smashed == EL_PENGUIN)
6907 Tile[x][y] = EL_AMOEBA_GROWING;
6908 Store[x][y] = EL_AMOEBA_WET;
6910 ResetRandomAnimationValue(x, y);
6915 if (object_hit) // check which object was hit
6917 if ((CAN_PASS_MAGIC_WALL(element) &&
6918 (smashed == EL_MAGIC_WALL ||
6919 smashed == EL_BD_MAGIC_WALL)) ||
6920 (CAN_PASS_DC_MAGIC_WALL(element) &&
6921 smashed == EL_DC_MAGIC_WALL))
6924 int activated_magic_wall =
6925 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6926 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6927 EL_DC_MAGIC_WALL_ACTIVE);
6929 // activate magic wall / mill
6930 SCAN_PLAYFIELD(xx, yy)
6932 if (Tile[xx][yy] == smashed)
6933 Tile[xx][yy] = activated_magic_wall;
6936 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6937 game.magic_wall_active = TRUE;
6939 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6940 SND_MAGIC_WALL_ACTIVATING :
6941 smashed == EL_BD_MAGIC_WALL ?
6942 SND_BD_MAGIC_WALL_ACTIVATING :
6943 SND_DC_MAGIC_WALL_ACTIVATING));
6946 if (IS_PLAYER(x, y + 1))
6948 if (CAN_SMASH_PLAYER(element))
6950 KillPlayerUnlessEnemyProtected(x, y + 1);
6954 else if (smashed == EL_PENGUIN)
6956 if (CAN_SMASH_PLAYER(element))
6962 else if (element == EL_BD_DIAMOND)
6964 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6970 else if (((element == EL_SP_INFOTRON ||
6971 element == EL_SP_ZONK) &&
6972 (smashed == EL_SP_SNIKSNAK ||
6973 smashed == EL_SP_ELECTRON ||
6974 smashed == EL_SP_DISK_ORANGE)) ||
6975 (element == EL_SP_INFOTRON &&
6976 smashed == EL_SP_DISK_YELLOW))
6981 else if (CAN_SMASH_EVERYTHING(element))
6983 if (IS_CLASSIC_ENEMY(smashed) ||
6984 CAN_EXPLODE_SMASHED(smashed))
6989 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6991 if (smashed == EL_LAMP ||
6992 smashed == EL_LAMP_ACTIVE)
6997 else if (smashed == EL_NUT)
6999 Tile[x][y + 1] = EL_NUT_BREAKING;
7000 PlayLevelSound(x, y, SND_NUT_BREAKING);
7001 RaiseScoreElement(EL_NUT);
7004 else if (smashed == EL_PEARL)
7006 ResetGfxAnimation(x, y);
7008 Tile[x][y + 1] = EL_PEARL_BREAKING;
7009 PlayLevelSound(x, y, SND_PEARL_BREAKING);
7012 else if (smashed == EL_DIAMOND)
7014 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
7015 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
7018 else if (IS_BELT_SWITCH(smashed))
7020 ToggleBeltSwitch(x, y + 1);
7022 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
7023 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
7024 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
7025 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
7027 ToggleSwitchgateSwitch();
7029 else if (smashed == EL_LIGHT_SWITCH ||
7030 smashed == EL_LIGHT_SWITCH_ACTIVE)
7032 ToggleLightSwitch(x, y + 1);
7036 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7038 CheckElementChangeBySide(x, y + 1, smashed, element,
7039 CE_SWITCHED, CH_SIDE_TOP);
7040 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
7046 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7051 // play sound of magic wall / mill
7053 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7054 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
7055 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
7057 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7058 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
7059 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7060 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
7061 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7062 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
7067 // play sound of object that hits the ground
7068 if (last_line || object_hit)
7069 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
7072 static void TurnRoundExt(int x, int y)
7084 { 0, 0 }, { 0, 0 }, { 0, 0 },
7089 int left, right, back;
7093 { MV_DOWN, MV_UP, MV_RIGHT },
7094 { MV_UP, MV_DOWN, MV_LEFT },
7096 { MV_LEFT, MV_RIGHT, MV_DOWN },
7100 { MV_RIGHT, MV_LEFT, MV_UP }
7103 int element = Tile[x][y];
7104 int move_pattern = element_info[element].move_pattern;
7106 int old_move_dir = MovDir[x][y];
7107 int left_dir = turn[old_move_dir].left;
7108 int right_dir = turn[old_move_dir].right;
7109 int back_dir = turn[old_move_dir].back;
7111 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
7112 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
7113 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
7114 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
7116 int left_x = x + left_dx, left_y = y + left_dy;
7117 int right_x = x + right_dx, right_y = y + right_dy;
7118 int move_x = x + move_dx, move_y = y + move_dy;
7122 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
7124 TestIfBadThingTouchesOtherBadThing(x, y);
7126 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
7127 MovDir[x][y] = right_dir;
7128 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7129 MovDir[x][y] = left_dir;
7131 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7133 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7136 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7138 TestIfBadThingTouchesOtherBadThing(x, y);
7140 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7141 MovDir[x][y] = left_dir;
7142 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7143 MovDir[x][y] = right_dir;
7145 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7147 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7150 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7152 TestIfBadThingTouchesOtherBadThing(x, y);
7154 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7155 MovDir[x][y] = left_dir;
7156 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7157 MovDir[x][y] = right_dir;
7159 if (MovDir[x][y] != old_move_dir)
7162 else if (element == EL_YAMYAM)
7164 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7165 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7167 if (can_turn_left && can_turn_right)
7168 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7169 else if (can_turn_left)
7170 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7171 else if (can_turn_right)
7172 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7174 MovDir[x][y] = back_dir;
7176 MovDelay[x][y] = 16 + 16 * RND(3);
7178 else if (element == EL_DARK_YAMYAM)
7180 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7182 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7185 if (can_turn_left && can_turn_right)
7186 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7187 else if (can_turn_left)
7188 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7189 else if (can_turn_right)
7190 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7192 MovDir[x][y] = back_dir;
7194 MovDelay[x][y] = 16 + 16 * RND(3);
7196 else if (element == EL_PACMAN)
7198 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7199 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7201 if (can_turn_left && can_turn_right)
7202 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7203 else if (can_turn_left)
7204 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7205 else if (can_turn_right)
7206 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7208 MovDir[x][y] = back_dir;
7210 MovDelay[x][y] = 6 + RND(40);
7212 else if (element == EL_PIG)
7214 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7215 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7216 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7217 boolean should_turn_left, should_turn_right, should_move_on;
7219 int rnd = RND(rnd_value);
7221 should_turn_left = (can_turn_left &&
7223 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7224 y + back_dy + left_dy)));
7225 should_turn_right = (can_turn_right &&
7227 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7228 y + back_dy + right_dy)));
7229 should_move_on = (can_move_on &&
7232 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7233 y + move_dy + left_dy) ||
7234 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7235 y + move_dy + right_dy)));
7237 if (should_turn_left || should_turn_right || should_move_on)
7239 if (should_turn_left && should_turn_right && should_move_on)
7240 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7241 rnd < 2 * rnd_value / 3 ? right_dir :
7243 else if (should_turn_left && should_turn_right)
7244 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7245 else if (should_turn_left && should_move_on)
7246 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7247 else if (should_turn_right && should_move_on)
7248 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7249 else if (should_turn_left)
7250 MovDir[x][y] = left_dir;
7251 else if (should_turn_right)
7252 MovDir[x][y] = right_dir;
7253 else if (should_move_on)
7254 MovDir[x][y] = old_move_dir;
7256 else if (can_move_on && rnd > rnd_value / 8)
7257 MovDir[x][y] = old_move_dir;
7258 else if (can_turn_left && can_turn_right)
7259 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7260 else if (can_turn_left && rnd > rnd_value / 8)
7261 MovDir[x][y] = left_dir;
7262 else if (can_turn_right && rnd > rnd_value/8)
7263 MovDir[x][y] = right_dir;
7265 MovDir[x][y] = back_dir;
7267 xx = x + move_xy[MovDir[x][y]].dx;
7268 yy = y + move_xy[MovDir[x][y]].dy;
7270 if (!IN_LEV_FIELD(xx, yy) ||
7271 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7272 MovDir[x][y] = old_move_dir;
7276 else if (element == EL_DRAGON)
7278 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7279 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7280 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7282 int rnd = RND(rnd_value);
7284 if (can_move_on && rnd > rnd_value / 8)
7285 MovDir[x][y] = old_move_dir;
7286 else if (can_turn_left && can_turn_right)
7287 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7288 else if (can_turn_left && rnd > rnd_value / 8)
7289 MovDir[x][y] = left_dir;
7290 else if (can_turn_right && rnd > rnd_value / 8)
7291 MovDir[x][y] = right_dir;
7293 MovDir[x][y] = back_dir;
7295 xx = x + move_xy[MovDir[x][y]].dx;
7296 yy = y + move_xy[MovDir[x][y]].dy;
7298 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7299 MovDir[x][y] = old_move_dir;
7303 else if (element == EL_MOLE)
7305 boolean can_move_on =
7306 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7307 IS_AMOEBOID(Tile[move_x][move_y]) ||
7308 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7311 boolean can_turn_left =
7312 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7313 IS_AMOEBOID(Tile[left_x][left_y])));
7315 boolean can_turn_right =
7316 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7317 IS_AMOEBOID(Tile[right_x][right_y])));
7319 if (can_turn_left && can_turn_right)
7320 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7321 else if (can_turn_left)
7322 MovDir[x][y] = left_dir;
7324 MovDir[x][y] = right_dir;
7327 if (MovDir[x][y] != old_move_dir)
7330 else if (element == EL_BALLOON)
7332 MovDir[x][y] = game.wind_direction;
7335 else if (element == EL_SPRING)
7337 if (MovDir[x][y] & MV_HORIZONTAL)
7339 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7340 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7342 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7343 ResetGfxAnimation(move_x, move_y);
7344 TEST_DrawLevelField(move_x, move_y);
7346 MovDir[x][y] = back_dir;
7348 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7349 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7350 MovDir[x][y] = MV_NONE;
7355 else if (element == EL_ROBOT ||
7356 element == EL_SATELLITE ||
7357 element == EL_PENGUIN ||
7358 element == EL_EMC_ANDROID)
7360 int attr_x = -1, attr_y = -1;
7362 if (game.all_players_gone)
7364 attr_x = game.exit_x;
7365 attr_y = game.exit_y;
7371 for (i = 0; i < MAX_PLAYERS; i++)
7373 struct PlayerInfo *player = &stored_player[i];
7374 int jx = player->jx, jy = player->jy;
7376 if (!player->active)
7380 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7388 if (element == EL_ROBOT &&
7389 game.robot_wheel_x >= 0 &&
7390 game.robot_wheel_y >= 0 &&
7391 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7392 game.engine_version < VERSION_IDENT(3,1,0,0)))
7394 attr_x = game.robot_wheel_x;
7395 attr_y = game.robot_wheel_y;
7398 if (element == EL_PENGUIN)
7401 struct XY *xy = xy_topdown;
7403 for (i = 0; i < NUM_DIRECTIONS; i++)
7405 int ex = x + xy[i].x;
7406 int ey = y + xy[i].y;
7408 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7409 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7410 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7411 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7420 MovDir[x][y] = MV_NONE;
7422 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7423 else if (attr_x > x)
7424 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7426 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7427 else if (attr_y > y)
7428 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7430 if (element == EL_ROBOT)
7434 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7435 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7436 Moving2Blocked(x, y, &newx, &newy);
7438 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7439 MovDelay[x][y] = 8 + 8 * !RND(3);
7441 MovDelay[x][y] = 16;
7443 else if (element == EL_PENGUIN)
7449 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7451 boolean first_horiz = RND(2);
7452 int new_move_dir = MovDir[x][y];
7455 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7456 Moving2Blocked(x, y, &newx, &newy);
7458 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7462 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7463 Moving2Blocked(x, y, &newx, &newy);
7465 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7468 MovDir[x][y] = old_move_dir;
7472 else if (element == EL_SATELLITE)
7478 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7480 boolean first_horiz = RND(2);
7481 int new_move_dir = MovDir[x][y];
7484 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7485 Moving2Blocked(x, y, &newx, &newy);
7487 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7491 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7492 Moving2Blocked(x, y, &newx, &newy);
7494 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7497 MovDir[x][y] = old_move_dir;
7501 else if (element == EL_EMC_ANDROID)
7503 static int check_pos[16] =
7505 -1, // 0 => (invalid)
7508 -1, // 3 => (invalid)
7510 0, // 5 => MV_LEFT | MV_UP
7511 2, // 6 => MV_RIGHT | MV_UP
7512 -1, // 7 => (invalid)
7514 6, // 9 => MV_LEFT | MV_DOWN
7515 4, // 10 => MV_RIGHT | MV_DOWN
7516 -1, // 11 => (invalid)
7517 -1, // 12 => (invalid)
7518 -1, // 13 => (invalid)
7519 -1, // 14 => (invalid)
7520 -1, // 15 => (invalid)
7528 { -1, -1, MV_LEFT | MV_UP },
7530 { +1, -1, MV_RIGHT | MV_UP },
7531 { +1, 0, MV_RIGHT },
7532 { +1, +1, MV_RIGHT | MV_DOWN },
7534 { -1, +1, MV_LEFT | MV_DOWN },
7537 int start_pos, check_order;
7538 boolean can_clone = FALSE;
7541 // check if there is any free field around current position
7542 for (i = 0; i < 8; i++)
7544 int newx = x + check_xy[i].dx;
7545 int newy = y + check_xy[i].dy;
7547 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7555 if (can_clone) // randomly find an element to clone
7559 start_pos = check_pos[RND(8)];
7560 check_order = (RND(2) ? -1 : +1);
7562 for (i = 0; i < 8; i++)
7564 int pos_raw = start_pos + i * check_order;
7565 int pos = (pos_raw + 8) % 8;
7566 int newx = x + check_xy[pos].dx;
7567 int newy = y + check_xy[pos].dy;
7569 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7571 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7572 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7574 Store[x][y] = Tile[newx][newy];
7583 if (can_clone) // randomly find a direction to move
7587 start_pos = check_pos[RND(8)];
7588 check_order = (RND(2) ? -1 : +1);
7590 for (i = 0; i < 8; i++)
7592 int pos_raw = start_pos + i * check_order;
7593 int pos = (pos_raw + 8) % 8;
7594 int newx = x + check_xy[pos].dx;
7595 int newy = y + check_xy[pos].dy;
7596 int new_move_dir = check_xy[pos].dir;
7598 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7600 MovDir[x][y] = new_move_dir;
7601 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7610 if (can_clone) // cloning and moving successful
7613 // cannot clone -- try to move towards player
7615 start_pos = check_pos[MovDir[x][y] & 0x0f];
7616 check_order = (RND(2) ? -1 : +1);
7618 for (i = 0; i < 3; i++)
7620 // first check start_pos, then previous/next or (next/previous) pos
7621 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7622 int pos = (pos_raw + 8) % 8;
7623 int newx = x + check_xy[pos].dx;
7624 int newy = y + check_xy[pos].dy;
7625 int new_move_dir = check_xy[pos].dir;
7627 if (IS_PLAYER(newx, newy))
7630 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7632 MovDir[x][y] = new_move_dir;
7633 MovDelay[x][y] = level.android_move_time * 8 + 1;
7640 else if (move_pattern == MV_TURNING_LEFT ||
7641 move_pattern == MV_TURNING_RIGHT ||
7642 move_pattern == MV_TURNING_LEFT_RIGHT ||
7643 move_pattern == MV_TURNING_RIGHT_LEFT ||
7644 move_pattern == MV_TURNING_RANDOM ||
7645 move_pattern == MV_ALL_DIRECTIONS)
7647 boolean can_turn_left =
7648 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7649 boolean can_turn_right =
7650 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y);
7652 if (element_info[element].move_stepsize == 0) // "not moving"
7655 if (move_pattern == MV_TURNING_LEFT)
7656 MovDir[x][y] = left_dir;
7657 else if (move_pattern == MV_TURNING_RIGHT)
7658 MovDir[x][y] = right_dir;
7659 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7660 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7661 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7662 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7663 else if (move_pattern == MV_TURNING_RANDOM)
7664 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7665 can_turn_right && !can_turn_left ? right_dir :
7666 RND(2) ? left_dir : right_dir);
7667 else if (can_turn_left && can_turn_right)
7668 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7669 else if (can_turn_left)
7670 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7671 else if (can_turn_right)
7672 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7674 MovDir[x][y] = back_dir;
7676 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7678 else if (move_pattern == MV_HORIZONTAL ||
7679 move_pattern == MV_VERTICAL)
7681 if (move_pattern & old_move_dir)
7682 MovDir[x][y] = back_dir;
7683 else if (move_pattern == MV_HORIZONTAL)
7684 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7685 else if (move_pattern == MV_VERTICAL)
7686 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7688 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7690 else if (move_pattern & MV_ANY_DIRECTION)
7692 MovDir[x][y] = move_pattern;
7693 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7695 else if (move_pattern & MV_WIND_DIRECTION)
7697 MovDir[x][y] = game.wind_direction;
7698 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7700 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7702 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7703 MovDir[x][y] = left_dir;
7704 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7705 MovDir[x][y] = right_dir;
7707 if (MovDir[x][y] != old_move_dir)
7708 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7710 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7712 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7713 MovDir[x][y] = right_dir;
7714 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7715 MovDir[x][y] = left_dir;
7717 if (MovDir[x][y] != old_move_dir)
7718 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7720 else if (move_pattern == MV_TOWARDS_PLAYER ||
7721 move_pattern == MV_AWAY_FROM_PLAYER)
7723 int attr_x = -1, attr_y = -1;
7725 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7727 if (game.all_players_gone)
7729 attr_x = game.exit_x;
7730 attr_y = game.exit_y;
7736 for (i = 0; i < MAX_PLAYERS; i++)
7738 struct PlayerInfo *player = &stored_player[i];
7739 int jx = player->jx, jy = player->jy;
7741 if (!player->active)
7745 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7753 MovDir[x][y] = MV_NONE;
7755 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7756 else if (attr_x > x)
7757 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7759 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7760 else if (attr_y > y)
7761 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7763 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7765 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7767 boolean first_horiz = RND(2);
7768 int new_move_dir = MovDir[x][y];
7770 if (element_info[element].move_stepsize == 0) // "not moving"
7772 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7773 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7779 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7780 Moving2Blocked(x, y, &newx, &newy);
7782 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7786 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7787 Moving2Blocked(x, y, &newx, &newy);
7789 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7792 MovDir[x][y] = old_move_dir;
7795 else if (move_pattern == MV_WHEN_PUSHED ||
7796 move_pattern == MV_WHEN_DROPPED)
7798 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7799 MovDir[x][y] = MV_NONE;
7803 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7805 struct XY *test_xy = xy_topdown;
7806 static int test_dir[4] =
7813 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7814 int move_preference = -1000000; // start with very low preference
7815 int new_move_dir = MV_NONE;
7816 int start_test = RND(4);
7819 for (i = 0; i < NUM_DIRECTIONS; i++)
7821 int j = (start_test + i) % 4;
7822 int move_dir = test_dir[j];
7823 int move_dir_preference;
7825 xx = x + test_xy[j].x;
7826 yy = y + test_xy[j].y;
7828 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7829 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7831 new_move_dir = move_dir;
7836 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7839 move_dir_preference = -1 * RunnerVisit[xx][yy];
7840 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7841 move_dir_preference = PlayerVisit[xx][yy];
7843 if (move_dir_preference > move_preference)
7845 // prefer field that has not been visited for the longest time
7846 move_preference = move_dir_preference;
7847 new_move_dir = move_dir;
7849 else if (move_dir_preference == move_preference &&
7850 move_dir == old_move_dir)
7852 // prefer last direction when all directions are preferred equally
7853 move_preference = move_dir_preference;
7854 new_move_dir = move_dir;
7858 MovDir[x][y] = new_move_dir;
7859 if (old_move_dir != new_move_dir)
7860 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7864 static void TurnRound(int x, int y)
7866 int direction = MovDir[x][y];
7870 GfxDir[x][y] = MovDir[x][y];
7872 if (direction != MovDir[x][y])
7876 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7878 ResetGfxFrame(x, y);
7881 static boolean JustBeingPushed(int x, int y)
7885 for (i = 0; i < MAX_PLAYERS; i++)
7887 struct PlayerInfo *player = &stored_player[i];
7889 if (player->active && player->is_pushing && player->MovPos)
7891 int next_jx = player->jx + (player->jx - player->last_jx);
7892 int next_jy = player->jy + (player->jy - player->last_jy);
7894 if (x == next_jx && y == next_jy)
7902 static void StartMoving(int x, int y)
7904 boolean started_moving = FALSE; // some elements can fall _and_ move
7905 int element = Tile[x][y];
7910 if (MovDelay[x][y] == 0)
7911 GfxAction[x][y] = ACTION_DEFAULT;
7913 if (CAN_FALL(element) && y < lev_fieldy - 1)
7915 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7916 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7917 if (JustBeingPushed(x, y))
7920 if (element == EL_QUICKSAND_FULL)
7922 if (IS_FREE(x, y + 1))
7924 InitMovingField(x, y, MV_DOWN);
7925 started_moving = TRUE;
7927 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7928 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7929 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7930 Store[x][y] = EL_ROCK;
7932 Store[x][y] = EL_ROCK;
7935 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7937 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7939 if (!MovDelay[x][y])
7941 MovDelay[x][y] = TILEY + 1;
7943 ResetGfxAnimation(x, y);
7944 ResetGfxAnimation(x, y + 1);
7949 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7950 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7957 Tile[x][y] = EL_QUICKSAND_EMPTY;
7958 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7959 Store[x][y + 1] = Store[x][y];
7962 PlayLevelSoundAction(x, y, ACTION_FILLING);
7964 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7966 if (!MovDelay[x][y])
7968 MovDelay[x][y] = TILEY + 1;
7970 ResetGfxAnimation(x, y);
7971 ResetGfxAnimation(x, y + 1);
7976 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7977 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7984 Tile[x][y] = EL_QUICKSAND_EMPTY;
7985 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7986 Store[x][y + 1] = Store[x][y];
7989 PlayLevelSoundAction(x, y, ACTION_FILLING);
7992 else if (element == EL_QUICKSAND_FAST_FULL)
7994 if (IS_FREE(x, y + 1))
7996 InitMovingField(x, y, MV_DOWN);
7997 started_moving = TRUE;
7999 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
8000 #if USE_QUICKSAND_BD_ROCK_BUGFIX
8001 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
8002 Store[x][y] = EL_ROCK;
8004 Store[x][y] = EL_ROCK;
8007 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
8009 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8011 if (!MovDelay[x][y])
8013 MovDelay[x][y] = TILEY + 1;
8015 ResetGfxAnimation(x, y);
8016 ResetGfxAnimation(x, y + 1);
8021 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8022 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8029 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8030 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8031 Store[x][y + 1] = Store[x][y];
8034 PlayLevelSoundAction(x, y, ACTION_FILLING);
8036 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8038 if (!MovDelay[x][y])
8040 MovDelay[x][y] = TILEY + 1;
8042 ResetGfxAnimation(x, y);
8043 ResetGfxAnimation(x, y + 1);
8048 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8049 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8056 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8057 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8058 Store[x][y + 1] = Store[x][y];
8061 PlayLevelSoundAction(x, y, ACTION_FILLING);
8064 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8065 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8067 InitMovingField(x, y, MV_DOWN);
8068 started_moving = TRUE;
8070 Tile[x][y] = EL_QUICKSAND_FILLING;
8071 Store[x][y] = element;
8073 PlayLevelSoundAction(x, y, ACTION_FILLING);
8075 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8076 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8078 InitMovingField(x, y, MV_DOWN);
8079 started_moving = TRUE;
8081 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
8082 Store[x][y] = element;
8084 PlayLevelSoundAction(x, y, ACTION_FILLING);
8086 else if (element == EL_MAGIC_WALL_FULL)
8088 if (IS_FREE(x, y + 1))
8090 InitMovingField(x, y, MV_DOWN);
8091 started_moving = TRUE;
8093 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
8094 Store[x][y] = EL_CHANGED(Store[x][y]);
8096 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
8098 if (!MovDelay[x][y])
8099 MovDelay[x][y] = TILEY / 4 + 1;
8108 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
8109 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
8110 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8114 else if (element == EL_BD_MAGIC_WALL_FULL)
8116 if (IS_FREE(x, y + 1))
8118 InitMovingField(x, y, MV_DOWN);
8119 started_moving = TRUE;
8121 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8122 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8124 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8126 if (!MovDelay[x][y])
8127 MovDelay[x][y] = TILEY / 4 + 1;
8136 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8137 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8138 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8142 else if (element == EL_DC_MAGIC_WALL_FULL)
8144 if (IS_FREE(x, y + 1))
8146 InitMovingField(x, y, MV_DOWN);
8147 started_moving = TRUE;
8149 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8150 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8152 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8154 if (!MovDelay[x][y])
8155 MovDelay[x][y] = TILEY / 4 + 1;
8164 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8165 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8166 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8170 else if ((CAN_PASS_MAGIC_WALL(element) &&
8171 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8172 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8173 (CAN_PASS_DC_MAGIC_WALL(element) &&
8174 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8177 InitMovingField(x, y, MV_DOWN);
8178 started_moving = TRUE;
8181 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8182 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8183 EL_DC_MAGIC_WALL_FILLING);
8184 Store[x][y] = element;
8186 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8188 SplashAcid(x, y + 1);
8190 InitMovingField(x, y, MV_DOWN);
8191 started_moving = TRUE;
8193 Store[x][y] = EL_ACID;
8196 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8197 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8198 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8199 CAN_FALL(element) && WasJustFalling[x][y] &&
8200 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8202 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8203 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8204 (Tile[x][y + 1] == EL_BLOCKED)))
8206 /* this is needed for a special case not covered by calling "Impact()"
8207 from "ContinueMoving()": if an element moves to a tile directly below
8208 another element which was just falling on that tile (which was empty
8209 in the previous frame), the falling element above would just stop
8210 instead of smashing the element below (in previous version, the above
8211 element was just checked for "moving" instead of "falling", resulting
8212 in incorrect smashes caused by horizontal movement of the above
8213 element; also, the case of the player being the element to smash was
8214 simply not covered here... :-/ ) */
8216 CheckCollision[x][y] = 0;
8217 CheckImpact[x][y] = 0;
8221 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8223 if (MovDir[x][y] == MV_NONE)
8225 InitMovingField(x, y, MV_DOWN);
8226 started_moving = TRUE;
8229 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8231 if (WasJustFalling[x][y]) // prevent animation from being restarted
8232 MovDir[x][y] = MV_DOWN;
8234 InitMovingField(x, y, MV_DOWN);
8235 started_moving = TRUE;
8237 else if (element == EL_AMOEBA_DROP)
8239 Tile[x][y] = EL_AMOEBA_GROWING;
8240 Store[x][y] = EL_AMOEBA_WET;
8242 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8243 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8244 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8245 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8247 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8248 (IS_FREE(x - 1, y + 1) ||
8249 Tile[x - 1][y + 1] == EL_ACID));
8250 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8251 (IS_FREE(x + 1, y + 1) ||
8252 Tile[x + 1][y + 1] == EL_ACID));
8253 boolean can_fall_any = (can_fall_left || can_fall_right);
8254 boolean can_fall_both = (can_fall_left && can_fall_right);
8255 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8257 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8259 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8260 can_fall_right = FALSE;
8261 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8262 can_fall_left = FALSE;
8263 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8264 can_fall_right = FALSE;
8265 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8266 can_fall_left = FALSE;
8268 can_fall_any = (can_fall_left || can_fall_right);
8269 can_fall_both = FALSE;
8274 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8275 can_fall_right = FALSE; // slip down on left side
8277 can_fall_left = !(can_fall_right = RND(2));
8279 can_fall_both = FALSE;
8284 // if not determined otherwise, prefer left side for slipping down
8285 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8286 started_moving = TRUE;
8289 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8291 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8292 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8293 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8294 int belt_dir = game.belt_dir[belt_nr];
8296 if ((belt_dir == MV_LEFT && left_is_free) ||
8297 (belt_dir == MV_RIGHT && right_is_free))
8299 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8301 InitMovingField(x, y, belt_dir);
8302 started_moving = TRUE;
8304 Pushed[x][y] = TRUE;
8305 Pushed[nextx][y] = TRUE;
8307 GfxAction[x][y] = ACTION_DEFAULT;
8311 MovDir[x][y] = 0; // if element was moving, stop it
8316 // not "else if" because of elements that can fall and move (EL_SPRING)
8317 if (CAN_MOVE(element) && !started_moving)
8319 int move_pattern = element_info[element].move_pattern;
8322 Moving2Blocked(x, y, &newx, &newy);
8324 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8327 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8328 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8330 WasJustMoving[x][y] = 0;
8331 CheckCollision[x][y] = 0;
8333 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8335 if (Tile[x][y] != element) // element has changed
8339 if (!MovDelay[x][y]) // start new movement phase
8341 // all objects that can change their move direction after each step
8342 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8344 if (element != EL_YAMYAM &&
8345 element != EL_DARK_YAMYAM &&
8346 element != EL_PACMAN &&
8347 !(move_pattern & MV_ANY_DIRECTION) &&
8348 move_pattern != MV_TURNING_LEFT &&
8349 move_pattern != MV_TURNING_RIGHT &&
8350 move_pattern != MV_TURNING_LEFT_RIGHT &&
8351 move_pattern != MV_TURNING_RIGHT_LEFT &&
8352 move_pattern != MV_TURNING_RANDOM)
8356 if (MovDelay[x][y] && (element == EL_BUG ||
8357 element == EL_SPACESHIP ||
8358 element == EL_SP_SNIKSNAK ||
8359 element == EL_SP_ELECTRON ||
8360 element == EL_MOLE))
8361 TEST_DrawLevelField(x, y);
8365 if (MovDelay[x][y]) // wait some time before next movement
8369 if (element == EL_ROBOT ||
8370 element == EL_YAMYAM ||
8371 element == EL_DARK_YAMYAM)
8373 DrawLevelElementAnimationIfNeeded(x, y, element);
8374 PlayLevelSoundAction(x, y, ACTION_WAITING);
8376 else if (element == EL_SP_ELECTRON)
8377 DrawLevelElementAnimationIfNeeded(x, y, element);
8378 else if (element == EL_DRAGON)
8381 int dir = MovDir[x][y];
8382 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8383 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8384 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8385 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8386 dir == MV_UP ? IMG_FLAMES_1_UP :
8387 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8388 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8390 GfxAction[x][y] = ACTION_ATTACKING;
8392 if (IS_PLAYER(x, y))
8393 DrawPlayerField(x, y);
8395 TEST_DrawLevelField(x, y);
8397 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8399 for (i = 1; i <= 3; i++)
8401 int xx = x + i * dx;
8402 int yy = y + i * dy;
8403 int sx = SCREENX(xx);
8404 int sy = SCREENY(yy);
8405 int flame_graphic = graphic + (i - 1);
8407 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8412 int flamed = MovingOrBlocked2Element(xx, yy);
8414 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8417 RemoveMovingField(xx, yy);
8419 ChangeDelay[xx][yy] = 0;
8421 Tile[xx][yy] = EL_FLAMES;
8423 if (IN_SCR_FIELD(sx, sy))
8425 TEST_DrawLevelFieldCrumbled(xx, yy);
8426 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8431 if (Tile[xx][yy] == EL_FLAMES)
8432 Tile[xx][yy] = EL_EMPTY;
8433 TEST_DrawLevelField(xx, yy);
8438 if (MovDelay[x][y]) // element still has to wait some time
8440 PlayLevelSoundAction(x, y, ACTION_WAITING);
8446 // now make next step
8448 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8450 if (DONT_COLLIDE_WITH(element) &&
8451 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8452 !PLAYER_ENEMY_PROTECTED(newx, newy))
8454 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8459 else if (CAN_MOVE_INTO_ACID(element) &&
8460 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8461 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8462 (MovDir[x][y] == MV_DOWN ||
8463 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8465 SplashAcid(newx, newy);
8466 Store[x][y] = EL_ACID;
8468 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8470 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8471 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8472 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8473 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8476 TEST_DrawLevelField(x, y);
8478 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8479 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8480 DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0);
8482 game.friends_still_needed--;
8483 if (!game.friends_still_needed &&
8485 game.all_players_gone)
8490 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8492 if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING)
8493 TEST_DrawLevelField(newx, newy);
8495 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8497 else if (!IS_FREE(newx, newy))
8499 GfxAction[x][y] = ACTION_WAITING;
8501 if (IS_PLAYER(x, y))
8502 DrawPlayerField(x, y);
8504 TEST_DrawLevelField(x, y);
8509 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8511 if (IS_FOOD_PIG(Tile[newx][newy]))
8513 if (IS_MOVING(newx, newy))
8514 RemoveMovingField(newx, newy);
8517 Tile[newx][newy] = EL_EMPTY;
8518 TEST_DrawLevelField(newx, newy);
8521 PlayLevelSound(x, y, SND_PIG_DIGGING);
8523 else if (!IS_FREE(newx, newy))
8525 if (IS_PLAYER(x, y))
8526 DrawPlayerField(x, y);
8528 TEST_DrawLevelField(x, y);
8533 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8535 if (Store[x][y] != EL_EMPTY)
8537 boolean can_clone = FALSE;
8540 // check if element to clone is still there
8541 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8543 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8551 // cannot clone or target field not free anymore -- do not clone
8552 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8553 Store[x][y] = EL_EMPTY;
8556 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8558 if (IS_MV_DIAGONAL(MovDir[x][y]))
8560 int diagonal_move_dir = MovDir[x][y];
8561 int stored = Store[x][y];
8562 int change_delay = 8;
8565 // android is moving diagonally
8567 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8569 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8570 GfxElement[x][y] = EL_EMC_ANDROID;
8571 GfxAction[x][y] = ACTION_SHRINKING;
8572 GfxDir[x][y] = diagonal_move_dir;
8573 ChangeDelay[x][y] = change_delay;
8575 if (Store[x][y] == EL_EMPTY)
8576 Store[x][y] = GfxElementEmpty[x][y];
8578 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8581 DrawLevelGraphicAnimation(x, y, graphic);
8582 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8584 if (Tile[newx][newy] == EL_ACID)
8586 SplashAcid(newx, newy);
8591 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8593 Store[newx][newy] = EL_EMC_ANDROID;
8594 GfxElement[newx][newy] = EL_EMC_ANDROID;
8595 GfxAction[newx][newy] = ACTION_GROWING;
8596 GfxDir[newx][newy] = diagonal_move_dir;
8597 ChangeDelay[newx][newy] = change_delay;
8599 graphic = el_act_dir2img(GfxElement[newx][newy],
8600 GfxAction[newx][newy], GfxDir[newx][newy]);
8602 DrawLevelGraphicAnimation(newx, newy, graphic);
8603 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8609 Tile[newx][newy] = EL_EMPTY;
8610 TEST_DrawLevelField(newx, newy);
8612 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8615 else if (!IS_FREE(newx, newy))
8620 else if (IS_CUSTOM_ELEMENT(element) &&
8621 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8623 if (!DigFieldByCE(newx, newy, element))
8626 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8628 RunnerVisit[x][y] = FrameCounter;
8629 PlayerVisit[x][y] /= 8; // expire player visit path
8632 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8634 if (!IS_FREE(newx, newy))
8636 if (IS_PLAYER(x, y))
8637 DrawPlayerField(x, y);
8639 TEST_DrawLevelField(x, y);
8645 boolean wanna_flame = !RND(10);
8646 int dx = newx - x, dy = newy - y;
8647 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8648 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8649 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8650 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8651 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8652 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8655 IS_CLASSIC_ENEMY(element1) ||
8656 IS_CLASSIC_ENEMY(element2)) &&
8657 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8658 element1 != EL_FLAMES && element2 != EL_FLAMES)
8660 ResetGfxAnimation(x, y);
8661 GfxAction[x][y] = ACTION_ATTACKING;
8663 if (IS_PLAYER(x, y))
8664 DrawPlayerField(x, y);
8666 TEST_DrawLevelField(x, y);
8668 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8670 MovDelay[x][y] = 50;
8672 Tile[newx][newy] = EL_FLAMES;
8673 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8674 Tile[newx1][newy1] = EL_FLAMES;
8675 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8676 Tile[newx2][newy2] = EL_FLAMES;
8682 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8683 Tile[newx][newy] == EL_DIAMOND)
8685 if (IS_MOVING(newx, newy))
8686 RemoveMovingField(newx, newy);
8689 Tile[newx][newy] = EL_EMPTY;
8690 TEST_DrawLevelField(newx, newy);
8693 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8695 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8696 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8698 if (AmoebaNr[newx][newy])
8700 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8701 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8702 Tile[newx][newy] == EL_BD_AMOEBA)
8703 AmoebaCnt[AmoebaNr[newx][newy]]--;
8706 if (IS_MOVING(newx, newy))
8708 RemoveMovingField(newx, newy);
8712 Tile[newx][newy] = EL_EMPTY;
8713 TEST_DrawLevelField(newx, newy);
8716 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8718 else if ((element == EL_PACMAN || element == EL_MOLE)
8719 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8721 if (AmoebaNr[newx][newy])
8723 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8724 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8725 Tile[newx][newy] == EL_BD_AMOEBA)
8726 AmoebaCnt[AmoebaNr[newx][newy]]--;
8729 if (element == EL_MOLE)
8731 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8732 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8734 ResetGfxAnimation(x, y);
8735 GfxAction[x][y] = ACTION_DIGGING;
8736 TEST_DrawLevelField(x, y);
8738 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8740 return; // wait for shrinking amoeba
8742 else // element == EL_PACMAN
8744 Tile[newx][newy] = EL_EMPTY;
8745 TEST_DrawLevelField(newx, newy);
8746 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8749 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8750 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8751 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8753 // wait for shrinking amoeba to completely disappear
8756 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8758 // object was running against a wall
8762 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8763 DrawLevelElementAnimation(x, y, element);
8765 if (DONT_TOUCH(element))
8766 TestIfBadThingTouchesPlayer(x, y);
8771 InitMovingField(x, y, MovDir[x][y]);
8773 PlayLevelSoundAction(x, y, ACTION_MOVING);
8777 ContinueMoving(x, y);
8780 void ContinueMoving(int x, int y)
8782 int element = Tile[x][y];
8783 struct ElementInfo *ei = &element_info[element];
8784 int direction = MovDir[x][y];
8785 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8786 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8787 int newx = x + dx, newy = y + dy;
8788 int stored = Store[x][y];
8789 int stored_new = Store[newx][newy];
8790 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8791 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8792 boolean last_line = (newy == lev_fieldy - 1);
8793 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8795 if (pushed_by_player) // special case: moving object pushed by player
8797 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos));
8799 else if (use_step_delay) // special case: moving object has step delay
8801 if (!MovDelay[x][y])
8802 MovPos[x][y] += getElementMoveStepsize(x, y);
8807 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8811 TEST_DrawLevelField(x, y);
8813 return; // element is still waiting
8816 else // normal case: generically moving object
8818 MovPos[x][y] += getElementMoveStepsize(x, y);
8821 if (ABS(MovPos[x][y]) < TILEX)
8823 TEST_DrawLevelField(x, y);
8825 return; // element is still moving
8828 // element reached destination field
8830 Tile[x][y] = EL_EMPTY;
8831 Tile[newx][newy] = element;
8832 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8834 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8836 element = Tile[newx][newy] = EL_ACID;
8838 else if (element == EL_MOLE)
8840 Tile[x][y] = EL_SAND;
8842 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8844 else if (element == EL_QUICKSAND_FILLING)
8846 element = Tile[newx][newy] = get_next_element(element);
8847 Store[newx][newy] = Store[x][y];
8849 else if (element == EL_QUICKSAND_EMPTYING)
8851 Tile[x][y] = get_next_element(element);
8852 element = Tile[newx][newy] = Store[x][y];
8854 else if (element == EL_QUICKSAND_FAST_FILLING)
8856 element = Tile[newx][newy] = get_next_element(element);
8857 Store[newx][newy] = Store[x][y];
8859 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8861 Tile[x][y] = get_next_element(element);
8862 element = Tile[newx][newy] = Store[x][y];
8864 else if (element == EL_MAGIC_WALL_FILLING)
8866 element = Tile[newx][newy] = get_next_element(element);
8867 if (!game.magic_wall_active)
8868 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8869 Store[newx][newy] = Store[x][y];
8871 else if (element == EL_MAGIC_WALL_EMPTYING)
8873 Tile[x][y] = get_next_element(element);
8874 if (!game.magic_wall_active)
8875 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8876 element = Tile[newx][newy] = Store[x][y];
8878 InitField(newx, newy, FALSE);
8880 else if (element == EL_BD_MAGIC_WALL_FILLING)
8882 element = Tile[newx][newy] = get_next_element(element);
8883 if (!game.magic_wall_active)
8884 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8885 Store[newx][newy] = Store[x][y];
8887 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8889 Tile[x][y] = get_next_element(element);
8890 if (!game.magic_wall_active)
8891 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8892 element = Tile[newx][newy] = Store[x][y];
8894 InitField(newx, newy, FALSE);
8896 else if (element == EL_DC_MAGIC_WALL_FILLING)
8898 element = Tile[newx][newy] = get_next_element(element);
8899 if (!game.magic_wall_active)
8900 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8901 Store[newx][newy] = Store[x][y];
8903 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8905 Tile[x][y] = get_next_element(element);
8906 if (!game.magic_wall_active)
8907 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8908 element = Tile[newx][newy] = Store[x][y];
8910 InitField(newx, newy, FALSE);
8912 else if (element == EL_AMOEBA_DROPPING)
8914 Tile[x][y] = get_next_element(element);
8915 element = Tile[newx][newy] = Store[x][y];
8917 else if (element == EL_SOKOBAN_OBJECT)
8920 Tile[x][y] = Back[x][y];
8922 if (Back[newx][newy])
8923 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8925 Back[x][y] = Back[newx][newy] = 0;
8928 Store[x][y] = EL_EMPTY;
8933 MovDelay[newx][newy] = 0;
8935 if (CAN_CHANGE_OR_HAS_ACTION(element))
8937 // copy element change control values to new field
8938 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8939 ChangePage[newx][newy] = ChangePage[x][y];
8940 ChangeCount[newx][newy] = ChangeCount[x][y];
8941 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8944 CustomValue[newx][newy] = CustomValue[x][y];
8946 ChangeDelay[x][y] = 0;
8947 ChangePage[x][y] = -1;
8948 ChangeCount[x][y] = 0;
8949 ChangeEvent[x][y] = -1;
8951 CustomValue[x][y] = 0;
8953 // copy animation control values to new field
8954 GfxFrame[newx][newy] = GfxFrame[x][y];
8955 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8956 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8957 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8959 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8961 // some elements can leave other elements behind after moving
8962 if (ei->move_leave_element != EL_EMPTY &&
8963 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8964 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8966 int move_leave_element = ei->move_leave_element;
8968 // this makes it possible to leave the removed element again
8969 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8970 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8972 Tile[x][y] = move_leave_element;
8974 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8975 MovDir[x][y] = direction;
8977 InitField(x, y, FALSE);
8979 if (GFX_CRUMBLED(Tile[x][y]))
8980 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8982 if (IS_PLAYER_ELEMENT(move_leave_element))
8983 RelocatePlayer(x, y, move_leave_element);
8986 // do this after checking for left-behind element
8987 ResetGfxAnimation(x, y); // reset animation values for old field
8989 if (!CAN_MOVE(element) ||
8990 (CAN_FALL(element) && direction == MV_DOWN &&
8991 (element == EL_SPRING ||
8992 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8993 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8994 GfxDir[x][y] = MovDir[newx][newy] = 0;
8996 TEST_DrawLevelField(x, y);
8997 TEST_DrawLevelField(newx, newy);
8999 Stop[newx][newy] = TRUE; // ignore this element until the next frame
9001 // prevent pushed element from moving on in pushed direction
9002 if (pushed_by_player && CAN_MOVE(element) &&
9003 element_info[element].move_pattern & MV_ANY_DIRECTION &&
9004 !(element_info[element].move_pattern & direction))
9005 TurnRound(newx, newy);
9007 // prevent elements on conveyor belt from moving on in last direction
9008 if (pushed_by_conveyor && CAN_FALL(element) &&
9009 direction & MV_HORIZONTAL)
9010 MovDir[newx][newy] = 0;
9012 if (!pushed_by_player)
9014 int nextx = newx + dx, nexty = newy + dy;
9015 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
9017 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
9019 if (CAN_FALL(element) && direction == MV_DOWN)
9020 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
9022 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
9023 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
9025 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
9026 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
9029 if (DONT_TOUCH(element)) // object may be nasty to player or others
9031 TestIfBadThingTouchesPlayer(newx, newy);
9032 TestIfBadThingTouchesFriend(newx, newy);
9034 if (!IS_CUSTOM_ELEMENT(element))
9035 TestIfBadThingTouchesOtherBadThing(newx, newy);
9037 else if (element == EL_PENGUIN)
9038 TestIfFriendTouchesBadThing(newx, newy);
9040 if (DONT_GET_HIT_BY(element))
9042 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
9045 // give the player one last chance (one more frame) to move away
9046 if (CAN_FALL(element) && direction == MV_DOWN &&
9047 (last_line || (!IS_FREE(x, newy + 1) &&
9048 (!IS_PLAYER(x, newy + 1) ||
9049 game.engine_version < VERSION_IDENT(3,1,1,0)))))
9052 if (pushed_by_player && !game.use_change_when_pushing_bug)
9054 int push_side = MV_DIR_OPPOSITE(direction);
9055 struct PlayerInfo *player = PLAYERINFO(x, y);
9057 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
9058 player->index_bit, push_side);
9059 CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X,
9060 player->index_bit, push_side);
9063 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
9064 MovDelay[newx][newy] = 1;
9066 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
9068 TestIfElementTouchesCustomElement(x, y); // empty or new element
9069 TestIfElementHitsCustomElement(newx, newy, direction);
9070 TestIfPlayerTouchesCustomElement(newx, newy);
9071 TestIfElementTouchesCustomElement(newx, newy);
9073 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
9074 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
9075 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
9076 MV_DIR_OPPOSITE(direction));
9079 int AmoebaNeighbourNr(int ax, int ay)
9082 int element = Tile[ax][ay];
9084 struct XY *xy = xy_topdown;
9086 for (i = 0; i < NUM_DIRECTIONS; i++)
9088 int x = ax + xy[i].x;
9089 int y = ay + xy[i].y;
9091 if (!IN_LEV_FIELD(x, y))
9094 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
9095 group_nr = AmoebaNr[x][y];
9101 static void AmoebaMerge(int ax, int ay)
9103 int i, x, y, xx, yy;
9104 int new_group_nr = AmoebaNr[ax][ay];
9105 struct XY *xy = xy_topdown;
9107 if (new_group_nr == 0)
9110 for (i = 0; i < NUM_DIRECTIONS; i++)
9115 if (!IN_LEV_FIELD(x, y))
9118 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9119 Tile[x][y] == EL_BD_AMOEBA ||
9120 Tile[x][y] == EL_AMOEBA_DEAD) &&
9121 AmoebaNr[x][y] != new_group_nr)
9123 int old_group_nr = AmoebaNr[x][y];
9125 if (old_group_nr == 0)
9128 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9129 AmoebaCnt[old_group_nr] = 0;
9130 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9131 AmoebaCnt2[old_group_nr] = 0;
9133 SCAN_PLAYFIELD(xx, yy)
9135 if (AmoebaNr[xx][yy] == old_group_nr)
9136 AmoebaNr[xx][yy] = new_group_nr;
9142 void AmoebaToDiamond(int ax, int ay)
9146 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9148 int group_nr = AmoebaNr[ax][ay];
9153 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9154 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9160 SCAN_PLAYFIELD(x, y)
9162 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9165 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9169 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9170 SND_AMOEBA_TURNING_TO_GEM :
9171 SND_AMOEBA_TURNING_TO_ROCK));
9176 struct XY *xy = xy_topdown;
9178 for (i = 0; i < NUM_DIRECTIONS; i++)
9183 if (!IN_LEV_FIELD(x, y))
9186 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9188 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9189 SND_AMOEBA_TURNING_TO_GEM :
9190 SND_AMOEBA_TURNING_TO_ROCK));
9197 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9200 int group_nr = AmoebaNr[ax][ay];
9201 boolean done = FALSE;
9206 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9207 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9213 SCAN_PLAYFIELD(x, y)
9215 if (AmoebaNr[x][y] == group_nr &&
9216 (Tile[x][y] == EL_AMOEBA_DEAD ||
9217 Tile[x][y] == EL_BD_AMOEBA ||
9218 Tile[x][y] == EL_AMOEBA_GROWING))
9221 Tile[x][y] = new_element;
9222 InitField(x, y, FALSE);
9223 TEST_DrawLevelField(x, y);
9229 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9230 SND_BD_AMOEBA_TURNING_TO_ROCK :
9231 SND_BD_AMOEBA_TURNING_TO_GEM));
9234 static void AmoebaGrowing(int x, int y)
9236 static DelayCounter sound_delay = { 0 };
9238 if (!MovDelay[x][y]) // start new growing cycle
9242 if (DelayReached(&sound_delay))
9244 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9245 sound_delay.value = 30;
9249 if (MovDelay[x][y]) // wait some time before growing bigger
9252 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9254 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9255 6 - MovDelay[x][y]);
9257 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9260 if (!MovDelay[x][y])
9262 Tile[x][y] = Store[x][y];
9264 TEST_DrawLevelField(x, y);
9269 static void AmoebaShrinking(int x, int y)
9271 static DelayCounter sound_delay = { 0 };
9273 if (!MovDelay[x][y]) // start new shrinking cycle
9277 if (DelayReached(&sound_delay))
9278 sound_delay.value = 30;
9281 if (MovDelay[x][y]) // wait some time before shrinking
9284 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9286 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9287 6 - MovDelay[x][y]);
9289 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9292 if (!MovDelay[x][y])
9294 Tile[x][y] = EL_EMPTY;
9295 TEST_DrawLevelField(x, y);
9297 // don't let mole enter this field in this cycle;
9298 // (give priority to objects falling to this field from above)
9304 static void AmoebaReproduce(int ax, int ay)
9307 int element = Tile[ax][ay];
9308 int graphic = el2img(element);
9309 int newax = ax, neway = ay;
9310 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9311 struct XY *xy = xy_topdown;
9313 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9315 Tile[ax][ay] = EL_AMOEBA_DEAD;
9316 TEST_DrawLevelField(ax, ay);
9320 if (IS_ANIMATED(graphic))
9321 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9323 if (!MovDelay[ax][ay]) // start making new amoeba field
9324 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9326 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9329 if (MovDelay[ax][ay])
9333 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9336 int x = ax + xy[start].x;
9337 int y = ay + xy[start].y;
9339 if (!IN_LEV_FIELD(x, y))
9342 if (IS_FREE(x, y) ||
9343 CAN_GROW_INTO(Tile[x][y]) ||
9344 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9345 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9351 if (newax == ax && neway == ay)
9354 else // normal or "filled" (BD style) amoeba
9357 boolean waiting_for_player = FALSE;
9359 for (i = 0; i < NUM_DIRECTIONS; i++)
9361 int j = (start + i) % 4;
9362 int x = ax + xy[j].x;
9363 int y = ay + xy[j].y;
9365 if (!IN_LEV_FIELD(x, y))
9368 if (IS_FREE(x, y) ||
9369 CAN_GROW_INTO(Tile[x][y]) ||
9370 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9371 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9377 else if (IS_PLAYER(x, y))
9378 waiting_for_player = TRUE;
9381 if (newax == ax && neway == ay) // amoeba cannot grow
9383 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9385 Tile[ax][ay] = EL_AMOEBA_DEAD;
9386 TEST_DrawLevelField(ax, ay);
9387 AmoebaCnt[AmoebaNr[ax][ay]]--;
9389 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9391 if (element == EL_AMOEBA_FULL)
9392 AmoebaToDiamond(ax, ay);
9393 else if (element == EL_BD_AMOEBA)
9394 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9399 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9401 // amoeba gets larger by growing in some direction
9403 int new_group_nr = AmoebaNr[ax][ay];
9406 if (new_group_nr == 0)
9408 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9410 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9416 AmoebaNr[newax][neway] = new_group_nr;
9417 AmoebaCnt[new_group_nr]++;
9418 AmoebaCnt2[new_group_nr]++;
9420 // if amoeba touches other amoeba(s) after growing, unify them
9421 AmoebaMerge(newax, neway);
9423 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9425 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9431 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9432 (neway == lev_fieldy - 1 && newax != ax))
9434 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9435 Store[newax][neway] = element;
9437 else if (neway == ay || element == EL_EMC_DRIPPER)
9439 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9441 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9445 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9446 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9447 Store[ax][ay] = EL_AMOEBA_DROP;
9448 ContinueMoving(ax, ay);
9452 TEST_DrawLevelField(newax, neway);
9455 static void Life(int ax, int ay)
9459 int element = Tile[ax][ay];
9460 int graphic = el2img(element);
9461 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9463 boolean changed = FALSE;
9465 if (IS_ANIMATED(graphic))
9466 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9471 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9472 MovDelay[ax][ay] = life_time;
9474 if (MovDelay[ax][ay]) // wait some time before next cycle
9477 if (MovDelay[ax][ay])
9481 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9483 int xx = ax + x1, yy = ay + y1;
9484 int old_element = Tile[xx][yy];
9485 int num_neighbours = 0;
9487 if (!IN_LEV_FIELD(xx, yy))
9490 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9492 int x = xx + x2, y = yy + y2;
9494 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9497 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9498 boolean is_neighbour = FALSE;
9500 if (level.use_life_bugs)
9502 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9503 (IS_FREE(x, y) && Stop[x][y]));
9506 (Last[x][y] == element || is_player_cell);
9512 boolean is_free = FALSE;
9514 if (level.use_life_bugs)
9515 is_free = (IS_FREE(xx, yy));
9517 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9519 if (xx == ax && yy == ay) // field in the middle
9521 if (num_neighbours < life_parameter[0] ||
9522 num_neighbours > life_parameter[1])
9524 Tile[xx][yy] = EL_EMPTY;
9525 if (Tile[xx][yy] != old_element)
9526 TEST_DrawLevelField(xx, yy);
9527 Stop[xx][yy] = TRUE;
9531 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9532 { // free border field
9533 if (num_neighbours >= life_parameter[2] &&
9534 num_neighbours <= life_parameter[3])
9536 Tile[xx][yy] = element;
9537 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9538 if (Tile[xx][yy] != old_element)
9539 TEST_DrawLevelField(xx, yy);
9540 Stop[xx][yy] = TRUE;
9547 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9548 SND_GAME_OF_LIFE_GROWING);
9551 static void InitRobotWheel(int x, int y)
9553 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9556 static void RunRobotWheel(int x, int y)
9558 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9561 static void StopRobotWheel(int x, int y)
9563 if (game.robot_wheel_x == x &&
9564 game.robot_wheel_y == y)
9566 game.robot_wheel_x = -1;
9567 game.robot_wheel_y = -1;
9568 game.robot_wheel_active = FALSE;
9572 static void InitTimegateWheel(int x, int y)
9574 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9577 static void RunTimegateWheel(int x, int y)
9579 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9582 static void InitMagicBallDelay(int x, int y)
9584 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9587 static void ActivateMagicBall(int bx, int by)
9591 if (level.ball_random)
9593 int pos_border = RND(8); // select one of the eight border elements
9594 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9595 int xx = pos_content % 3;
9596 int yy = pos_content / 3;
9601 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9602 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9606 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9608 int xx = x - bx + 1;
9609 int yy = y - by + 1;
9611 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9612 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9616 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9619 static void CheckExit(int x, int y)
9621 if (game.gems_still_needed > 0 ||
9622 game.sokoban_fields_still_needed > 0 ||
9623 game.sokoban_objects_still_needed > 0 ||
9624 game.lights_still_needed > 0)
9626 int element = Tile[x][y];
9627 int graphic = el2img(element);
9629 if (IS_ANIMATED(graphic))
9630 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9635 // do not re-open exit door closed after last player
9636 if (game.all_players_gone)
9639 Tile[x][y] = EL_EXIT_OPENING;
9641 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9644 static void CheckExitEM(int x, int y)
9646 if (game.gems_still_needed > 0 ||
9647 game.sokoban_fields_still_needed > 0 ||
9648 game.sokoban_objects_still_needed > 0 ||
9649 game.lights_still_needed > 0)
9651 int element = Tile[x][y];
9652 int graphic = el2img(element);
9654 if (IS_ANIMATED(graphic))
9655 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9660 // do not re-open exit door closed after last player
9661 if (game.all_players_gone)
9664 Tile[x][y] = EL_EM_EXIT_OPENING;
9666 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9669 static void CheckExitSteel(int x, int y)
9671 if (game.gems_still_needed > 0 ||
9672 game.sokoban_fields_still_needed > 0 ||
9673 game.sokoban_objects_still_needed > 0 ||
9674 game.lights_still_needed > 0)
9676 int element = Tile[x][y];
9677 int graphic = el2img(element);
9679 if (IS_ANIMATED(graphic))
9680 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9685 // do not re-open exit door closed after last player
9686 if (game.all_players_gone)
9689 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9691 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9694 static void CheckExitSteelEM(int x, int y)
9696 if (game.gems_still_needed > 0 ||
9697 game.sokoban_fields_still_needed > 0 ||
9698 game.sokoban_objects_still_needed > 0 ||
9699 game.lights_still_needed > 0)
9701 int element = Tile[x][y];
9702 int graphic = el2img(element);
9704 if (IS_ANIMATED(graphic))
9705 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9710 // do not re-open exit door closed after last player
9711 if (game.all_players_gone)
9714 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9716 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9719 static void CheckExitSP(int x, int y)
9721 if (game.gems_still_needed > 0)
9723 int element = Tile[x][y];
9724 int graphic = el2img(element);
9726 if (IS_ANIMATED(graphic))
9727 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9732 // do not re-open exit door closed after last player
9733 if (game.all_players_gone)
9736 Tile[x][y] = EL_SP_EXIT_OPENING;
9738 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9741 static void CloseAllOpenTimegates(void)
9745 SCAN_PLAYFIELD(x, y)
9747 int element = Tile[x][y];
9749 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9751 Tile[x][y] = EL_TIMEGATE_CLOSING;
9753 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9758 static void DrawTwinkleOnField(int x, int y)
9760 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9763 if (Tile[x][y] == EL_BD_DIAMOND)
9766 if (MovDelay[x][y] == 0) // next animation frame
9767 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9769 if (MovDelay[x][y] != 0) // wait some time before next frame
9773 DrawLevelElementAnimation(x, y, Tile[x][y]);
9775 if (MovDelay[x][y] != 0)
9777 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9778 10 - MovDelay[x][y]);
9780 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9785 static void WallGrowing(int x, int y)
9789 if (!MovDelay[x][y]) // next animation frame
9790 MovDelay[x][y] = 3 * delay;
9792 if (MovDelay[x][y]) // wait some time before next frame
9796 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9798 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9799 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9801 DrawLevelGraphic(x, y, graphic, frame);
9804 if (!MovDelay[x][y])
9806 if (MovDir[x][y] == MV_LEFT)
9808 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9809 TEST_DrawLevelField(x - 1, y);
9811 else if (MovDir[x][y] == MV_RIGHT)
9813 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9814 TEST_DrawLevelField(x + 1, y);
9816 else if (MovDir[x][y] == MV_UP)
9818 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9819 TEST_DrawLevelField(x, y - 1);
9823 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9824 TEST_DrawLevelField(x, y + 1);
9827 Tile[x][y] = Store[x][y];
9829 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9830 TEST_DrawLevelField(x, y);
9835 static void CheckWallGrowing(int ax, int ay)
9837 int element = Tile[ax][ay];
9838 int graphic = el2img(element);
9839 boolean free_top = FALSE;
9840 boolean free_bottom = FALSE;
9841 boolean free_left = FALSE;
9842 boolean free_right = FALSE;
9843 boolean stop_top = FALSE;
9844 boolean stop_bottom = FALSE;
9845 boolean stop_left = FALSE;
9846 boolean stop_right = FALSE;
9847 boolean new_wall = FALSE;
9849 boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9850 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9851 element == EL_EXPANDABLE_STEELWALL_ANY);
9853 boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9854 element == EL_EXPANDABLE_WALL_ANY ||
9855 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9856 element == EL_EXPANDABLE_STEELWALL_ANY);
9858 boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9859 element == EL_EXPANDABLE_WALL_ANY ||
9860 element == EL_EXPANDABLE_WALL ||
9861 element == EL_BD_EXPANDABLE_WALL ||
9862 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9863 element == EL_EXPANDABLE_STEELWALL_ANY);
9865 boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9866 element == EL_EXPANDABLE_STEELWALL_VERTICAL);
9868 boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9869 element == EL_EXPANDABLE_WALL ||
9870 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL);
9872 int wall_growing = (is_steelwall ?
9873 EL_EXPANDABLE_STEELWALL_GROWING :
9874 EL_EXPANDABLE_WALL_GROWING);
9876 int gfx_wall_growing_up = (is_steelwall ?
9877 IMG_EXPANDABLE_STEELWALL_GROWING_UP :
9878 IMG_EXPANDABLE_WALL_GROWING_UP);
9879 int gfx_wall_growing_down = (is_steelwall ?
9880 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN :
9881 IMG_EXPANDABLE_WALL_GROWING_DOWN);
9882 int gfx_wall_growing_left = (is_steelwall ?
9883 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT :
9884 IMG_EXPANDABLE_WALL_GROWING_LEFT);
9885 int gfx_wall_growing_right = (is_steelwall ?
9886 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT :
9887 IMG_EXPANDABLE_WALL_GROWING_RIGHT);
9889 if (IS_ANIMATED(graphic))
9890 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9892 if (!MovDelay[ax][ay]) // start building new wall
9893 MovDelay[ax][ay] = 6;
9895 if (MovDelay[ax][ay]) // wait some time before building new wall
9898 if (MovDelay[ax][ay])
9902 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9904 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9906 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9908 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9915 Tile[ax][ay - 1] = wall_growing;
9916 Store[ax][ay - 1] = element;
9917 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9919 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9920 DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0);
9927 Tile[ax][ay + 1] = wall_growing;
9928 Store[ax][ay + 1] = element;
9929 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9931 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9932 DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0);
9938 if (grow_horizontal)
9942 Tile[ax - 1][ay] = wall_growing;
9943 Store[ax - 1][ay] = element;
9944 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9946 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9947 DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0);
9954 Tile[ax + 1][ay] = wall_growing;
9955 Store[ax + 1][ay] = element;
9956 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
9958 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
9959 DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0);
9965 if (element == EL_EXPANDABLE_WALL && (free_left || free_right))
9966 TEST_DrawLevelField(ax, ay);
9968 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
9970 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
9972 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
9974 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
9977 if (((stop_top && stop_bottom) || stop_horizontal) &&
9978 ((stop_left && stop_right) || stop_vertical))
9979 Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL);
9982 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9985 static void CheckForDragon(int x, int y)
9988 boolean dragon_found = FALSE;
9989 struct XY *xy = xy_topdown;
9991 for (i = 0; i < NUM_DIRECTIONS; i++)
9993 for (j = 0; j < 4; j++)
9995 int xx = x + j * xy[i].x;
9996 int yy = y + j * xy[i].y;
9998 if (IN_LEV_FIELD(xx, yy) &&
9999 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
10001 if (Tile[xx][yy] == EL_DRAGON)
10002 dragon_found = TRUE;
10011 for (i = 0; i < NUM_DIRECTIONS; i++)
10013 for (j = 0; j < 3; j++)
10015 int xx = x + j * xy[i].x;
10016 int yy = y + j * xy[i].y;
10018 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
10020 Tile[xx][yy] = EL_EMPTY;
10021 TEST_DrawLevelField(xx, yy);
10030 static void InitBuggyBase(int x, int y)
10032 int element = Tile[x][y];
10033 int activating_delay = FRAMES_PER_SECOND / 4;
10035 ChangeDelay[x][y] =
10036 (element == EL_SP_BUGGY_BASE ?
10037 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10038 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10040 element == EL_SP_BUGGY_BASE_ACTIVE ?
10041 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10044 static void WarnBuggyBase(int x, int y)
10047 struct XY *xy = xy_topdown;
10049 for (i = 0; i < NUM_DIRECTIONS; i++)
10051 int xx = x + xy[i].x;
10052 int yy = y + xy[i].y;
10054 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10056 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10063 static void InitTrap(int x, int y)
10065 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10068 static void ActivateTrap(int x, int y)
10070 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10073 static void ChangeActiveTrap(int x, int y)
10075 int graphic = IMG_TRAP_ACTIVE;
10077 // if new animation frame was drawn, correct crumbled sand border
10078 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10079 TEST_DrawLevelFieldCrumbled(x, y);
10082 static int getSpecialActionElement(int element, int number, int base_element)
10084 return (element != EL_EMPTY ? element :
10085 number != -1 ? base_element + number - 1 :
10089 static int getModifiedActionNumber(int value_old, int operator, int operand,
10090 int value_min, int value_max)
10092 int value_new = (operator == CA_MODE_SET ? operand :
10093 operator == CA_MODE_ADD ? value_old + operand :
10094 operator == CA_MODE_SUBTRACT ? value_old - operand :
10095 operator == CA_MODE_MULTIPLY ? value_old * operand :
10096 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10097 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10100 return (value_new < value_min ? value_min :
10101 value_new > value_max ? value_max :
10105 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10107 struct ElementInfo *ei = &element_info[element];
10108 struct ElementChangeInfo *change = &ei->change_page[page];
10109 int target_element = change->target_element;
10110 int action_type = change->action_type;
10111 int action_mode = change->action_mode;
10112 int action_arg = change->action_arg;
10113 int action_element = change->action_element;
10116 if (!change->has_action)
10119 // ---------- determine action paramater values -----------------------------
10121 int level_time_value =
10122 (level.time > 0 ? TimeLeft :
10125 int action_arg_element_raw =
10126 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10127 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10128 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10129 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10130 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10131 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10132 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10134 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10136 int action_arg_direction =
10137 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10138 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10139 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10140 change->actual_trigger_side :
10141 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10142 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10145 int action_arg_number_min =
10146 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10149 int action_arg_number_max =
10150 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10151 action_type == CA_SET_LEVEL_GEMS ? 999 :
10152 action_type == CA_SET_LEVEL_TIME ? 9999 :
10153 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10154 action_type == CA_SET_CE_VALUE ? 9999 :
10155 action_type == CA_SET_CE_SCORE ? 9999 :
10158 int action_arg_number_reset =
10159 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10160 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10161 action_type == CA_SET_LEVEL_TIME ? level.time :
10162 action_type == CA_SET_LEVEL_SCORE ? 0 :
10163 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10164 action_type == CA_SET_CE_SCORE ? 0 :
10167 int action_arg_number =
10168 (action_arg <= CA_ARG_MAX ? action_arg :
10169 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10170 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10171 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10172 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10173 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10174 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10175 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10176 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10177 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10178 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10179 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10180 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10181 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10182 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10183 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10184 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10185 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10186 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10187 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10188 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10189 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10192 int action_arg_number_old =
10193 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10194 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10195 action_type == CA_SET_LEVEL_SCORE ? game.score :
10196 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10197 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10200 int action_arg_number_new =
10201 getModifiedActionNumber(action_arg_number_old,
10202 action_mode, action_arg_number,
10203 action_arg_number_min, action_arg_number_max);
10205 int trigger_player_bits =
10206 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10207 change->actual_trigger_player_bits : change->trigger_player);
10209 int action_arg_player_bits =
10210 (action_arg >= CA_ARG_PLAYER_1 &&
10211 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10212 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10213 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10216 // ---------- execute action -----------------------------------------------
10218 switch (action_type)
10225 // ---------- level actions ----------------------------------------------
10227 case CA_RESTART_LEVEL:
10229 game.restart_level = TRUE;
10234 case CA_SHOW_ENVELOPE:
10236 int element = getSpecialActionElement(action_arg_element,
10237 action_arg_number, EL_ENVELOPE_1);
10239 if (IS_ENVELOPE(element))
10240 local_player->show_envelope = element;
10245 case CA_SET_LEVEL_TIME:
10247 if (level.time > 0) // only modify limited time value
10249 TimeLeft = action_arg_number_new;
10251 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10253 DisplayGameControlValues();
10255 if (!TimeLeft && game.time_limit)
10256 for (i = 0; i < MAX_PLAYERS; i++)
10257 KillPlayer(&stored_player[i]);
10263 case CA_SET_LEVEL_SCORE:
10265 game.score = action_arg_number_new;
10267 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10269 DisplayGameControlValues();
10274 case CA_SET_LEVEL_GEMS:
10276 game.gems_still_needed = action_arg_number_new;
10278 game.snapshot.collected_item = TRUE;
10280 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10282 DisplayGameControlValues();
10287 case CA_SET_LEVEL_WIND:
10289 game.wind_direction = action_arg_direction;
10294 case CA_SET_LEVEL_RANDOM_SEED:
10296 // ensure that setting a new random seed while playing is predictable
10297 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10302 // ---------- player actions ---------------------------------------------
10304 case CA_MOVE_PLAYER:
10305 case CA_MOVE_PLAYER_NEW:
10307 // automatically move to the next field in specified direction
10308 for (i = 0; i < MAX_PLAYERS; i++)
10309 if (trigger_player_bits & (1 << i))
10310 if (action_type == CA_MOVE_PLAYER ||
10311 stored_player[i].MovPos == 0)
10312 stored_player[i].programmed_action = action_arg_direction;
10317 case CA_EXIT_PLAYER:
10319 for (i = 0; i < MAX_PLAYERS; i++)
10320 if (action_arg_player_bits & (1 << i))
10321 ExitPlayer(&stored_player[i]);
10323 if (game.players_still_needed == 0)
10329 case CA_KILL_PLAYER:
10331 for (i = 0; i < MAX_PLAYERS; i++)
10332 if (action_arg_player_bits & (1 << i))
10333 KillPlayer(&stored_player[i]);
10338 case CA_SET_PLAYER_KEYS:
10340 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10341 int element = getSpecialActionElement(action_arg_element,
10342 action_arg_number, EL_KEY_1);
10344 if (IS_KEY(element))
10346 for (i = 0; i < MAX_PLAYERS; i++)
10348 if (trigger_player_bits & (1 << i))
10350 stored_player[i].key[KEY_NR(element)] = key_state;
10352 DrawGameDoorValues();
10360 case CA_SET_PLAYER_SPEED:
10362 for (i = 0; i < MAX_PLAYERS; i++)
10364 if (trigger_player_bits & (1 << i))
10366 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10368 if (action_arg == CA_ARG_SPEED_FASTER &&
10369 stored_player[i].cannot_move)
10371 action_arg_number = STEPSIZE_VERY_SLOW;
10373 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10374 action_arg == CA_ARG_SPEED_FASTER)
10376 action_arg_number = 2;
10377 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10380 else if (action_arg == CA_ARG_NUMBER_RESET)
10382 action_arg_number = level.initial_player_stepsize[i];
10386 getModifiedActionNumber(move_stepsize,
10389 action_arg_number_min,
10390 action_arg_number_max);
10392 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10399 case CA_SET_PLAYER_SHIELD:
10401 for (i = 0; i < MAX_PLAYERS; i++)
10403 if (trigger_player_bits & (1 << i))
10405 if (action_arg == CA_ARG_SHIELD_OFF)
10407 stored_player[i].shield_normal_time_left = 0;
10408 stored_player[i].shield_deadly_time_left = 0;
10410 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10412 stored_player[i].shield_normal_time_left = 999999;
10414 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10416 stored_player[i].shield_normal_time_left = 999999;
10417 stored_player[i].shield_deadly_time_left = 999999;
10425 case CA_SET_PLAYER_GRAVITY:
10427 for (i = 0; i < MAX_PLAYERS; i++)
10429 if (trigger_player_bits & (1 << i))
10431 stored_player[i].gravity =
10432 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10433 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10434 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10435 stored_player[i].gravity);
10442 case CA_SET_PLAYER_ARTWORK:
10444 for (i = 0; i < MAX_PLAYERS; i++)
10446 if (trigger_player_bits & (1 << i))
10448 int artwork_element = action_arg_element;
10450 if (action_arg == CA_ARG_ELEMENT_RESET)
10452 (level.use_artwork_element[i] ? level.artwork_element[i] :
10453 stored_player[i].element_nr);
10455 if (stored_player[i].artwork_element != artwork_element)
10456 stored_player[i].Frame = 0;
10458 stored_player[i].artwork_element = artwork_element;
10460 SetPlayerWaiting(&stored_player[i], FALSE);
10462 // set number of special actions for bored and sleeping animation
10463 stored_player[i].num_special_action_bored =
10464 get_num_special_action(artwork_element,
10465 ACTION_BORING_1, ACTION_BORING_LAST);
10466 stored_player[i].num_special_action_sleeping =
10467 get_num_special_action(artwork_element,
10468 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10475 case CA_SET_PLAYER_INVENTORY:
10477 for (i = 0; i < MAX_PLAYERS; i++)
10479 struct PlayerInfo *player = &stored_player[i];
10482 if (trigger_player_bits & (1 << i))
10484 int inventory_element = action_arg_element;
10486 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10487 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10488 action_arg == CA_ARG_ELEMENT_ACTION)
10490 int element = inventory_element;
10491 int collect_count = element_info[element].collect_count_initial;
10493 if (!IS_CUSTOM_ELEMENT(element))
10496 if (collect_count == 0)
10497 player->inventory_infinite_element = element;
10499 for (k = 0; k < collect_count; k++)
10500 if (player->inventory_size < MAX_INVENTORY_SIZE)
10501 player->inventory_element[player->inventory_size++] =
10504 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10505 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10506 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10508 if (player->inventory_infinite_element != EL_UNDEFINED &&
10509 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10510 action_arg_element_raw))
10511 player->inventory_infinite_element = EL_UNDEFINED;
10513 for (k = 0, j = 0; j < player->inventory_size; j++)
10515 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10516 action_arg_element_raw))
10517 player->inventory_element[k++] = player->inventory_element[j];
10520 player->inventory_size = k;
10522 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10524 if (player->inventory_size > 0)
10526 for (j = 0; j < player->inventory_size - 1; j++)
10527 player->inventory_element[j] = player->inventory_element[j + 1];
10529 player->inventory_size--;
10532 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10534 if (player->inventory_size > 0)
10535 player->inventory_size--;
10537 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10539 player->inventory_infinite_element = EL_UNDEFINED;
10540 player->inventory_size = 0;
10542 else if (action_arg == CA_ARG_INVENTORY_RESET)
10544 player->inventory_infinite_element = EL_UNDEFINED;
10545 player->inventory_size = 0;
10547 if (level.use_initial_inventory[i])
10549 for (j = 0; j < level.initial_inventory_size[i]; j++)
10551 int element = level.initial_inventory_content[i][j];
10552 int collect_count = element_info[element].collect_count_initial;
10554 if (!IS_CUSTOM_ELEMENT(element))
10557 if (collect_count == 0)
10558 player->inventory_infinite_element = element;
10560 for (k = 0; k < collect_count; k++)
10561 if (player->inventory_size < MAX_INVENTORY_SIZE)
10562 player->inventory_element[player->inventory_size++] =
10573 // ---------- CE actions -------------------------------------------------
10575 case CA_SET_CE_VALUE:
10577 int last_ce_value = CustomValue[x][y];
10579 CustomValue[x][y] = action_arg_number_new;
10581 if (CustomValue[x][y] != last_ce_value)
10583 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10584 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10586 if (CustomValue[x][y] == 0)
10588 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10589 ChangeCount[x][y] = 0; // allow at least one more change
10591 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10592 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10599 case CA_SET_CE_SCORE:
10601 int last_ce_score = ei->collect_score;
10603 ei->collect_score = action_arg_number_new;
10605 if (ei->collect_score != last_ce_score)
10607 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10608 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10610 if (ei->collect_score == 0)
10614 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10615 ChangeCount[x][y] = 0; // allow at least one more change
10617 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10618 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10621 This is a very special case that seems to be a mixture between
10622 CheckElementChange() and CheckTriggeredElementChange(): while
10623 the first one only affects single elements that are triggered
10624 directly, the second one affects multiple elements in the playfield
10625 that are triggered indirectly by another element. This is a third
10626 case: Changing the CE score always affects multiple identical CEs,
10627 so every affected CE must be checked, not only the single CE for
10628 which the CE score was changed in the first place (as every instance
10629 of that CE shares the same CE score, and therefore also can change)!
10631 SCAN_PLAYFIELD(xx, yy)
10633 if (Tile[xx][yy] == element)
10634 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10635 CE_SCORE_GETS_ZERO);
10643 case CA_SET_CE_ARTWORK:
10645 int artwork_element = action_arg_element;
10646 boolean reset_frame = FALSE;
10649 if (action_arg == CA_ARG_ELEMENT_RESET)
10650 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10653 if (ei->gfx_element != artwork_element)
10654 reset_frame = TRUE;
10656 ei->gfx_element = artwork_element;
10658 SCAN_PLAYFIELD(xx, yy)
10660 if (Tile[xx][yy] == element)
10664 ResetGfxAnimation(xx, yy);
10665 ResetRandomAnimationValue(xx, yy);
10668 TEST_DrawLevelField(xx, yy);
10675 // ---------- engine actions ---------------------------------------------
10677 case CA_SET_ENGINE_SCAN_MODE:
10679 InitPlayfieldScanMode(action_arg);
10689 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10691 int old_element = Tile[x][y];
10692 int new_element = GetElementFromGroupElement(element);
10693 int previous_move_direction = MovDir[x][y];
10694 int last_ce_value = CustomValue[x][y];
10695 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10696 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10697 boolean add_player_onto_element = (new_element_is_player &&
10698 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10699 IS_WALKABLE(old_element));
10701 if (!add_player_onto_element)
10703 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10704 RemoveMovingField(x, y);
10708 Tile[x][y] = new_element;
10710 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10711 MovDir[x][y] = previous_move_direction;
10713 if (element_info[new_element].use_last_ce_value)
10714 CustomValue[x][y] = last_ce_value;
10716 InitField_WithBug1(x, y, FALSE);
10718 new_element = Tile[x][y]; // element may have changed
10720 ResetGfxAnimation(x, y);
10721 ResetRandomAnimationValue(x, y);
10723 TEST_DrawLevelField(x, y);
10725 if (GFX_CRUMBLED(new_element))
10726 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10728 if (old_element == EL_EXPLOSION)
10730 Store[x][y] = Store2[x][y] = 0;
10732 // check if new element replaces an exploding player, requiring cleanup
10733 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
10734 StorePlayer[x][y] = 0;
10737 // check if element under the player changes from accessible to unaccessible
10738 // (needed for special case of dropping element which then changes)
10739 // (must be checked after creating new element for walkable group elements)
10740 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10741 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10743 KillPlayer(PLAYERINFO(x, y));
10749 // "ChangeCount" not set yet to allow "entered by player" change one time
10750 if (new_element_is_player)
10751 RelocatePlayer(x, y, new_element);
10754 ChangeCount[x][y]++; // count number of changes in the same frame
10756 TestIfBadThingTouchesPlayer(x, y);
10757 TestIfPlayerTouchesCustomElement(x, y);
10758 TestIfElementTouchesCustomElement(x, y);
10761 static void CreateField(int x, int y, int element)
10763 CreateFieldExt(x, y, element, FALSE);
10766 static void CreateElementFromChange(int x, int y, int element)
10768 element = GET_VALID_RUNTIME_ELEMENT(element);
10770 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10772 int old_element = Tile[x][y];
10774 // prevent changed element from moving in same engine frame
10775 // unless both old and new element can either fall or move
10776 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10777 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10781 CreateFieldExt(x, y, element, TRUE);
10784 static boolean ChangeElement(int x, int y, int element, int page)
10786 struct ElementInfo *ei = &element_info[element];
10787 struct ElementChangeInfo *change = &ei->change_page[page];
10788 int ce_value = CustomValue[x][y];
10789 int ce_score = ei->collect_score;
10790 int target_element;
10791 int old_element = Tile[x][y];
10793 // always use default change event to prevent running into a loop
10794 if (ChangeEvent[x][y] == -1)
10795 ChangeEvent[x][y] = CE_DELAY;
10797 if (ChangeEvent[x][y] == CE_DELAY)
10799 // reset actual trigger element, trigger player and action element
10800 change->actual_trigger_element = EL_EMPTY;
10801 change->actual_trigger_player = EL_EMPTY;
10802 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10803 change->actual_trigger_side = CH_SIDE_NONE;
10804 change->actual_trigger_ce_value = 0;
10805 change->actual_trigger_ce_score = 0;
10806 change->actual_trigger_x = -1;
10807 change->actual_trigger_y = -1;
10810 // do not change elements more than a specified maximum number of changes
10811 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10814 ChangeCount[x][y]++; // count number of changes in the same frame
10816 if (ei->has_anim_event)
10817 HandleGlobalAnimEventByElementChange(element, page, x, y,
10818 change->actual_trigger_x,
10819 change->actual_trigger_y);
10821 if (change->explode)
10828 if (change->use_target_content)
10830 boolean complete_replace = TRUE;
10831 boolean can_replace[3][3];
10834 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10837 boolean is_walkable;
10838 boolean is_diggable;
10839 boolean is_collectible;
10840 boolean is_removable;
10841 boolean is_destructible;
10842 int ex = x + xx - 1;
10843 int ey = y + yy - 1;
10844 int content_element = change->target_content.e[xx][yy];
10847 can_replace[xx][yy] = TRUE;
10849 if (ex == x && ey == y) // do not check changing element itself
10852 if (content_element == EL_EMPTY_SPACE)
10854 can_replace[xx][yy] = FALSE; // do not replace border with space
10859 if (!IN_LEV_FIELD(ex, ey))
10861 can_replace[xx][yy] = FALSE;
10862 complete_replace = FALSE;
10869 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10870 e = MovingOrBlocked2Element(ex, ey);
10872 is_empty = (IS_FREE(ex, ey) ||
10873 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10875 is_walkable = (is_empty || IS_WALKABLE(e));
10876 is_diggable = (is_empty || IS_DIGGABLE(e));
10877 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10878 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10879 is_removable = (is_diggable || is_collectible);
10881 can_replace[xx][yy] =
10882 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10883 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10884 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10885 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10886 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10887 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10888 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10890 if (!can_replace[xx][yy])
10891 complete_replace = FALSE;
10894 if (!change->only_if_complete || complete_replace)
10896 boolean something_has_changed = FALSE;
10898 if (change->only_if_complete && change->use_random_replace &&
10899 RND(100) < change->random_percentage)
10902 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10904 int ex = x + xx - 1;
10905 int ey = y + yy - 1;
10906 int content_element;
10908 if (can_replace[xx][yy] && (!change->use_random_replace ||
10909 RND(100) < change->random_percentage))
10911 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10912 RemoveMovingField(ex, ey);
10914 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10916 content_element = change->target_content.e[xx][yy];
10917 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10918 ce_value, ce_score);
10920 CreateElementFromChange(ex, ey, target_element);
10922 something_has_changed = TRUE;
10924 // for symmetry reasons, freeze newly created border elements
10925 if (ex != x || ey != y)
10926 Stop[ex][ey] = TRUE; // no more moving in this frame
10930 if (something_has_changed)
10932 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10933 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10939 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10940 ce_value, ce_score);
10942 if (element == EL_DIAGONAL_GROWING ||
10943 element == EL_DIAGONAL_SHRINKING)
10945 target_element = Store[x][y];
10947 Store[x][y] = EL_EMPTY;
10950 // special case: element changes to player (and may be kept if walkable)
10951 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10952 CreateElementFromChange(x, y, EL_EMPTY);
10954 CreateElementFromChange(x, y, target_element);
10956 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10957 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10960 // this uses direct change before indirect change
10961 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10966 static void HandleElementChange(int x, int y, int page)
10968 int element = MovingOrBlocked2Element(x, y);
10969 struct ElementInfo *ei = &element_info[element];
10970 struct ElementChangeInfo *change = &ei->change_page[page];
10971 boolean handle_action_before_change = FALSE;
10974 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10975 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10977 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10978 x, y, element, element_info[element].token_name);
10979 Debug("game:playing:HandleElementChange", "This should never happen!");
10983 // this can happen with classic bombs on walkable, changing elements
10984 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10989 if (ChangeDelay[x][y] == 0) // initialize element change
10991 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10993 if (change->can_change)
10995 // !!! not clear why graphic animation should be reset at all here !!!
10996 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10997 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
11000 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
11002 When using an animation frame delay of 1 (this only happens with
11003 "sp_zonk.moving.left/right" in the classic graphics), the default
11004 (non-moving) animation shows wrong animation frames (while the
11005 moving animation, like "sp_zonk.moving.left/right", is correct,
11006 so this graphical bug never shows up with the classic graphics).
11007 For an animation with 4 frames, this causes wrong frames 0,0,1,2
11008 be drawn instead of the correct frames 0,1,2,3. This is caused by
11009 "GfxFrame[][]" being reset *twice* (in two successive frames) after
11010 an element change: First when the change delay ("ChangeDelay[][]")
11011 counter has reached zero after decrementing, then a second time in
11012 the next frame (after "GfxFrame[][]" was already incremented) when
11013 "ChangeDelay[][]" is reset to the initial delay value again.
11015 This causes frame 0 to be drawn twice, while the last frame won't
11016 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
11018 As some animations may already be cleverly designed around this bug
11019 (at least the "Snake Bite" snake tail animation does this), it cannot
11020 simply be fixed here without breaking such existing animations.
11021 Unfortunately, it cannot easily be detected if a graphics set was
11022 designed "before" or "after" the bug was fixed. As a workaround,
11023 a new graphics set option "game.graphics_engine_version" was added
11024 to be able to specify the game's major release version for which the
11025 graphics set was designed, which can then be used to decide if the
11026 bugfix should be used (version 4 and above) or not (version 3 or
11027 below, or if no version was specified at all, as with old sets).
11029 (The wrong/fixed animation frames can be tested with the test level set
11030 "test_gfxframe" and level "000", which contains a specially prepared
11031 custom element at level position (x/y) == (11/9) which uses the zonk
11032 animation mentioned above. Using "game.graphics_engine_version: 4"
11033 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
11034 This can also be seen from the debug output for this test element.)
11037 // when a custom element is about to change (for example by change delay),
11038 // do not reset graphic animation when the custom element is moving
11039 if (game.graphics_engine_version < 4 &&
11042 ResetGfxAnimation(x, y);
11043 ResetRandomAnimationValue(x, y);
11046 if (change->pre_change_function)
11047 change->pre_change_function(x, y);
11051 ChangeDelay[x][y]--;
11053 if (ChangeDelay[x][y] != 0) // continue element change
11055 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11057 // also needed if CE can not change, but has CE delay with CE action
11058 if (IS_ANIMATED(graphic))
11059 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11061 if (change->can_change)
11063 if (change->change_function)
11064 change->change_function(x, y);
11067 else // finish element change
11069 if (ChangePage[x][y] != -1) // remember page from delayed change
11071 page = ChangePage[x][y];
11072 ChangePage[x][y] = -1;
11074 change = &ei->change_page[page];
11077 if (IS_MOVING(x, y)) // never change a running system ;-)
11079 ChangeDelay[x][y] = 1; // try change after next move step
11080 ChangePage[x][y] = page; // remember page to use for change
11085 // special case: set new level random seed before changing element
11086 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11087 handle_action_before_change = TRUE;
11089 if (change->has_action && handle_action_before_change)
11090 ExecuteCustomElementAction(x, y, element, page);
11092 if (change->can_change)
11094 if (ChangeElement(x, y, element, page))
11096 if (change->post_change_function)
11097 change->post_change_function(x, y);
11101 if (change->has_action && !handle_action_before_change)
11102 ExecuteCustomElementAction(x, y, element, page);
11106 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11107 int trigger_element,
11109 int trigger_player,
11113 boolean change_done_any = FALSE;
11114 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11117 if (!(trigger_events[trigger_element][trigger_event]))
11120 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11122 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11124 int element = EL_CUSTOM_START + i;
11125 boolean change_done = FALSE;
11128 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11129 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11132 for (p = 0; p < element_info[element].num_change_pages; p++)
11134 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11136 if (change->can_change_or_has_action &&
11137 change->has_event[trigger_event] &&
11138 change->trigger_side & trigger_side &&
11139 change->trigger_player & trigger_player &&
11140 change->trigger_page & trigger_page_bits &&
11141 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11143 change->actual_trigger_element = trigger_element;
11144 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11145 change->actual_trigger_player_bits = trigger_player;
11146 change->actual_trigger_side = trigger_side;
11147 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11148 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11149 change->actual_trigger_x = trigger_x;
11150 change->actual_trigger_y = trigger_y;
11152 if ((change->can_change && !change_done) || change->has_action)
11156 SCAN_PLAYFIELD(x, y)
11158 if (Tile[x][y] == element)
11160 if (change->can_change && !change_done)
11162 // if element already changed in this frame, not only prevent
11163 // another element change (checked in ChangeElement()), but
11164 // also prevent additional element actions for this element
11166 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11167 !level.use_action_after_change_bug)
11170 ChangeDelay[x][y] = 1;
11171 ChangeEvent[x][y] = trigger_event;
11173 HandleElementChange(x, y, p);
11175 else if (change->has_action)
11177 // if element already changed in this frame, not only prevent
11178 // another element change (checked in ChangeElement()), but
11179 // also prevent additional element actions for this element
11181 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11182 !level.use_action_after_change_bug)
11185 ExecuteCustomElementAction(x, y, element, p);
11186 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11191 if (change->can_change)
11193 change_done = TRUE;
11194 change_done_any = TRUE;
11201 RECURSION_LOOP_DETECTION_END();
11203 return change_done_any;
11206 static boolean CheckElementChangeExt(int x, int y,
11208 int trigger_element,
11210 int trigger_player,
11213 boolean change_done = FALSE;
11216 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11217 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11220 if (Tile[x][y] == EL_BLOCKED)
11222 Blocked2Moving(x, y, &x, &y);
11223 element = Tile[x][y];
11226 // check if element has already changed or is about to change after moving
11227 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11228 Tile[x][y] != element) ||
11230 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11231 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11232 ChangePage[x][y] != -1)))
11235 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11237 for (p = 0; p < element_info[element].num_change_pages; p++)
11239 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11241 /* check trigger element for all events where the element that is checked
11242 for changing interacts with a directly adjacent element -- this is
11243 different to element changes that affect other elements to change on the
11244 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11245 boolean check_trigger_element =
11246 (trigger_event == CE_NEXT_TO_X ||
11247 trigger_event == CE_TOUCHING_X ||
11248 trigger_event == CE_HITTING_X ||
11249 trigger_event == CE_HIT_BY_X ||
11250 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11252 if (change->can_change_or_has_action &&
11253 change->has_event[trigger_event] &&
11254 change->trigger_side & trigger_side &&
11255 change->trigger_player & trigger_player &&
11256 (!check_trigger_element ||
11257 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11259 change->actual_trigger_element = trigger_element;
11260 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11261 change->actual_trigger_player_bits = trigger_player;
11262 change->actual_trigger_side = trigger_side;
11263 change->actual_trigger_ce_value = CustomValue[x][y];
11264 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11265 change->actual_trigger_x = x;
11266 change->actual_trigger_y = y;
11268 // special case: trigger element not at (x,y) position for some events
11269 if (check_trigger_element)
11281 { 0, 0 }, { 0, 0 }, { 0, 0 },
11285 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11286 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11288 change->actual_trigger_ce_value = CustomValue[xx][yy];
11289 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11290 change->actual_trigger_x = xx;
11291 change->actual_trigger_y = yy;
11294 if (change->can_change && !change_done)
11296 ChangeDelay[x][y] = 1;
11297 ChangeEvent[x][y] = trigger_event;
11299 HandleElementChange(x, y, p);
11301 change_done = TRUE;
11303 else if (change->has_action)
11305 ExecuteCustomElementAction(x, y, element, p);
11306 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11311 RECURSION_LOOP_DETECTION_END();
11313 return change_done;
11316 static void PlayPlayerSound(struct PlayerInfo *player)
11318 int jx = player->jx, jy = player->jy;
11319 int sound_element = player->artwork_element;
11320 int last_action = player->last_action_waiting;
11321 int action = player->action_waiting;
11323 if (player->is_waiting)
11325 if (action != last_action)
11326 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11328 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11332 if (action != last_action)
11333 StopSound(element_info[sound_element].sound[last_action]);
11335 if (last_action == ACTION_SLEEPING)
11336 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11340 static void PlayAllPlayersSound(void)
11344 for (i = 0; i < MAX_PLAYERS; i++)
11345 if (stored_player[i].active)
11346 PlayPlayerSound(&stored_player[i]);
11349 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11351 boolean last_waiting = player->is_waiting;
11352 int move_dir = player->MovDir;
11354 player->dir_waiting = move_dir;
11355 player->last_action_waiting = player->action_waiting;
11359 if (!last_waiting) // not waiting -> waiting
11361 player->is_waiting = TRUE;
11363 player->frame_counter_bored =
11365 game.player_boring_delay_fixed +
11366 GetSimpleRandom(game.player_boring_delay_random);
11367 player->frame_counter_sleeping =
11369 game.player_sleeping_delay_fixed +
11370 GetSimpleRandom(game.player_sleeping_delay_random);
11372 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11375 if (game.player_sleeping_delay_fixed +
11376 game.player_sleeping_delay_random > 0 &&
11377 player->anim_delay_counter == 0 &&
11378 player->post_delay_counter == 0 &&
11379 FrameCounter >= player->frame_counter_sleeping)
11380 player->is_sleeping = TRUE;
11381 else if (game.player_boring_delay_fixed +
11382 game.player_boring_delay_random > 0 &&
11383 FrameCounter >= player->frame_counter_bored)
11384 player->is_bored = TRUE;
11386 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11387 player->is_bored ? ACTION_BORING :
11390 if (player->is_sleeping && player->use_murphy)
11392 // special case for sleeping Murphy when leaning against non-free tile
11394 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11395 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11396 !IS_MOVING(player->jx - 1, player->jy)))
11397 move_dir = MV_LEFT;
11398 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11399 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11400 !IS_MOVING(player->jx + 1, player->jy)))
11401 move_dir = MV_RIGHT;
11403 player->is_sleeping = FALSE;
11405 player->dir_waiting = move_dir;
11408 if (player->is_sleeping)
11410 if (player->num_special_action_sleeping > 0)
11412 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11414 int last_special_action = player->special_action_sleeping;
11415 int num_special_action = player->num_special_action_sleeping;
11416 int special_action =
11417 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11418 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11419 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11420 last_special_action + 1 : ACTION_SLEEPING);
11421 int special_graphic =
11422 el_act_dir2img(player->artwork_element, special_action, move_dir);
11424 player->anim_delay_counter =
11425 graphic_info[special_graphic].anim_delay_fixed +
11426 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11427 player->post_delay_counter =
11428 graphic_info[special_graphic].post_delay_fixed +
11429 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11431 player->special_action_sleeping = special_action;
11434 if (player->anim_delay_counter > 0)
11436 player->action_waiting = player->special_action_sleeping;
11437 player->anim_delay_counter--;
11439 else if (player->post_delay_counter > 0)
11441 player->post_delay_counter--;
11445 else if (player->is_bored)
11447 if (player->num_special_action_bored > 0)
11449 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11451 int special_action =
11452 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11453 int special_graphic =
11454 el_act_dir2img(player->artwork_element, special_action, move_dir);
11456 player->anim_delay_counter =
11457 graphic_info[special_graphic].anim_delay_fixed +
11458 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11459 player->post_delay_counter =
11460 graphic_info[special_graphic].post_delay_fixed +
11461 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11463 player->special_action_bored = special_action;
11466 if (player->anim_delay_counter > 0)
11468 player->action_waiting = player->special_action_bored;
11469 player->anim_delay_counter--;
11471 else if (player->post_delay_counter > 0)
11473 player->post_delay_counter--;
11478 else if (last_waiting) // waiting -> not waiting
11480 player->is_waiting = FALSE;
11481 player->is_bored = FALSE;
11482 player->is_sleeping = FALSE;
11484 player->frame_counter_bored = -1;
11485 player->frame_counter_sleeping = -1;
11487 player->anim_delay_counter = 0;
11488 player->post_delay_counter = 0;
11490 player->dir_waiting = player->MovDir;
11491 player->action_waiting = ACTION_DEFAULT;
11493 player->special_action_bored = ACTION_DEFAULT;
11494 player->special_action_sleeping = ACTION_DEFAULT;
11498 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11500 if ((!player->is_moving && player->was_moving) ||
11501 (player->MovPos == 0 && player->was_moving) ||
11502 (player->is_snapping && !player->was_snapping) ||
11503 (player->is_dropping && !player->was_dropping))
11505 if (!CheckSaveEngineSnapshotToList())
11508 player->was_moving = FALSE;
11509 player->was_snapping = TRUE;
11510 player->was_dropping = TRUE;
11514 if (player->is_moving)
11515 player->was_moving = TRUE;
11517 if (!player->is_snapping)
11518 player->was_snapping = FALSE;
11520 if (!player->is_dropping)
11521 player->was_dropping = FALSE;
11524 static struct MouseActionInfo mouse_action_last = { 0 };
11525 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11526 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11529 CheckSaveEngineSnapshotToList();
11531 mouse_action_last = mouse_action;
11534 static void CheckSingleStepMode(struct PlayerInfo *player)
11536 if (tape.single_step && tape.recording && !tape.pausing)
11538 // as it is called "single step mode", just return to pause mode when the
11539 // player stopped moving after one tile (or never starts moving at all)
11540 // (reverse logic needed here in case single step mode used in team mode)
11541 if (player->is_moving ||
11542 player->is_pushing ||
11543 player->is_dropping_pressed ||
11544 player->effective_mouse_action.button)
11545 game.enter_single_step_mode = FALSE;
11548 CheckSaveEngineSnapshot(player);
11551 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11553 int left = player_action & JOY_LEFT;
11554 int right = player_action & JOY_RIGHT;
11555 int up = player_action & JOY_UP;
11556 int down = player_action & JOY_DOWN;
11557 int button1 = player_action & JOY_BUTTON_1;
11558 int button2 = player_action & JOY_BUTTON_2;
11559 int dx = (left ? -1 : right ? 1 : 0);
11560 int dy = (up ? -1 : down ? 1 : 0);
11562 if (!player->active || tape.pausing)
11568 SnapField(player, dx, dy);
11572 DropElement(player);
11574 MovePlayer(player, dx, dy);
11577 CheckSingleStepMode(player);
11579 SetPlayerWaiting(player, FALSE);
11581 return player_action;
11585 // no actions for this player (no input at player's configured device)
11587 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11588 SnapField(player, 0, 0);
11589 CheckGravityMovementWhenNotMoving(player);
11591 if (player->MovPos == 0)
11592 SetPlayerWaiting(player, TRUE);
11594 if (player->MovPos == 0) // needed for tape.playing
11595 player->is_moving = FALSE;
11597 player->is_dropping = FALSE;
11598 player->is_dropping_pressed = FALSE;
11599 player->drop_pressed_delay = 0;
11601 CheckSingleStepMode(player);
11607 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11610 if (!tape.use_mouse_actions)
11613 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11614 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11615 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11618 static void SetTapeActionFromMouseAction(byte *tape_action,
11619 struct MouseActionInfo *mouse_action)
11621 if (!tape.use_mouse_actions)
11624 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11625 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11626 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11629 static void CheckLevelSolved(void)
11631 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11633 if (game_bd.level_solved &&
11634 !game_bd.game_over) // game won
11638 game_bd.game_over = TRUE;
11640 game.all_players_gone = TRUE;
11643 if (game_bd.game_over) // game lost
11644 game.all_players_gone = TRUE;
11646 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11648 if (game_em.level_solved &&
11649 !game_em.game_over) // game won
11653 game_em.game_over = TRUE;
11655 game.all_players_gone = TRUE;
11658 if (game_em.game_over) // game lost
11659 game.all_players_gone = TRUE;
11661 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11663 if (game_sp.level_solved &&
11664 !game_sp.game_over) // game won
11668 game_sp.game_over = TRUE;
11670 game.all_players_gone = TRUE;
11673 if (game_sp.game_over) // game lost
11674 game.all_players_gone = TRUE;
11676 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11678 if (game_mm.level_solved &&
11679 !game_mm.game_over) // game won
11683 game_mm.game_over = TRUE;
11685 game.all_players_gone = TRUE;
11688 if (game_mm.game_over) // game lost
11689 game.all_players_gone = TRUE;
11693 static void PlayTimeoutSound(int seconds_left)
11695 // try to use individual "running out of time" sound for each second left
11696 int sound = SND_GAME_RUNNING_OUT_OF_TIME_0 - seconds_left;
11698 // if special sound per second not defined, use default sound
11699 if (getSoundInfoEntryFilename(sound) == NULL)
11700 sound = SND_GAME_RUNNING_OUT_OF_TIME;
11702 // if out of time, but player still alive, play special "timeout" sound, if defined
11703 if (seconds_left == 0 && !checkGameFailed())
11704 if (getSoundInfoEntryFilename(SND_GAME_TIMEOUT) != NULL)
11705 sound = SND_GAME_TIMEOUT;
11710 static void CheckLevelTime_StepCounter(void)
11720 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11721 PlayTimeoutSound(TimeLeft);
11723 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11725 DisplayGameControlValues();
11727 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11728 for (i = 0; i < MAX_PLAYERS; i++)
11729 KillPlayer(&stored_player[i]);
11731 else if (game.no_level_time_limit && !game.all_players_gone)
11733 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11735 DisplayGameControlValues();
11739 static void CheckLevelTime(void)
11743 if (TimeFrames >= FRAMES_PER_SECOND)
11747 for (i = 0; i < MAX_PLAYERS; i++)
11749 struct PlayerInfo *player = &stored_player[i];
11751 if (SHIELD_ON(player))
11753 player->shield_normal_time_left--;
11755 if (player->shield_deadly_time_left > 0)
11756 player->shield_deadly_time_left--;
11760 if (!game.LevelSolved && !level.use_step_counter)
11768 if (TimeLeft <= 10 && game.time_limit)
11769 PlayTimeoutSound(TimeLeft);
11771 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11772 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11774 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11776 if (!TimeLeft && game.time_limit)
11778 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11779 game_em.lev->killed_out_of_time = TRUE;
11781 for (i = 0; i < MAX_PLAYERS; i++)
11782 KillPlayer(&stored_player[i]);
11785 else if (game.no_level_time_limit && !game.all_players_gone)
11787 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11790 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11794 if (TapeTimeFrames >= FRAMES_PER_SECOND)
11796 TapeTimeFrames = 0;
11799 if (tape.recording || tape.playing)
11800 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11803 if (tape.recording || tape.playing)
11804 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11806 UpdateAndDisplayGameControlValues();
11809 void AdvanceFrameAndPlayerCounters(int player_nr)
11813 // advance frame counters (global frame counter and tape time frame counter)
11817 // advance time frame counter (used to control available time to solve level)
11820 // advance player counters (counters for move delay, move animation etc.)
11821 for (i = 0; i < MAX_PLAYERS; i++)
11823 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11824 int move_delay_value = stored_player[i].move_delay_value;
11825 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11827 if (!advance_player_counters) // not all players may be affected
11830 if (move_frames == 0) // less than one move per game frame
11832 int stepsize = TILEX / move_delay_value;
11833 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11834 int count = (stored_player[i].is_moving ?
11835 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11837 if (count % delay == 0)
11841 stored_player[i].Frame += move_frames;
11843 if (stored_player[i].MovPos != 0)
11844 stored_player[i].StepFrame += move_frames;
11846 if (stored_player[i].move_delay > 0)
11847 stored_player[i].move_delay--;
11849 // due to bugs in previous versions, counter must count up, not down
11850 if (stored_player[i].push_delay != -1)
11851 stored_player[i].push_delay++;
11853 if (stored_player[i].drop_delay > 0)
11854 stored_player[i].drop_delay--;
11856 if (stored_player[i].is_dropping_pressed)
11857 stored_player[i].drop_pressed_delay++;
11861 void AdvanceFrameCounter(void)
11866 void AdvanceGfxFrame(void)
11870 SCAN_PLAYFIELD(x, y)
11876 static void HandleMouseAction(struct MouseActionInfo *mouse_action,
11877 struct MouseActionInfo *mouse_action_last)
11879 if (mouse_action->button)
11881 int new_button = (mouse_action->button && mouse_action_last->button == 0);
11882 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button);
11883 int x = mouse_action->lx;
11884 int y = mouse_action->ly;
11885 int element = Tile[x][y];
11889 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
11890 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
11894 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
11895 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
11898 if (level.use_step_counter)
11900 boolean counted_click = FALSE;
11902 // element clicked that can change when clicked/pressed
11903 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
11904 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
11905 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
11906 counted_click = TRUE;
11908 // element clicked that can trigger change when clicked/pressed
11909 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
11910 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
11911 counted_click = TRUE;
11913 if (new_button && counted_click)
11914 CheckLevelTime_StepCounter();
11919 void StartGameActions(boolean init_network_game, boolean record_tape,
11922 unsigned int new_random_seed = InitRND(random_seed);
11925 TapeStartRecording(new_random_seed);
11927 if (setup.auto_pause_on_start && !tape.pausing)
11928 TapeTogglePause(TAPE_TOGGLE_MANUAL);
11930 if (init_network_game)
11932 SendToServer_LevelFile();
11933 SendToServer_StartPlaying();
11941 static void GameActionsExt(void)
11944 static unsigned int game_frame_delay = 0;
11946 unsigned int game_frame_delay_value;
11947 byte *recorded_player_action;
11948 byte summarized_player_action = 0;
11949 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11952 // detect endless loops, caused by custom element programming
11953 if (recursion_loop_detected && recursion_loop_depth == 0)
11955 char *message = getStringCat3("Internal Error! Element ",
11956 EL_NAME(recursion_loop_element),
11957 " caused endless loop! Quit the game?");
11959 Warn("element '%s' caused endless loop in game engine",
11960 EL_NAME(recursion_loop_element));
11962 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11964 recursion_loop_detected = FALSE; // if game should be continued
11971 if (game.restart_level)
11972 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11974 CheckLevelSolved();
11976 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11979 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11982 if (game_status != GAME_MODE_PLAYING) // status might have changed
11985 game_frame_delay_value =
11986 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11988 if (tape.playing && tape.warp_forward && !tape.pausing)
11989 game_frame_delay_value = 0;
11991 SetVideoFrameDelay(game_frame_delay_value);
11993 // (de)activate virtual buttons depending on current game status
11994 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11996 if (game.all_players_gone) // if no players there to be controlled anymore
11997 SetOverlayActive(FALSE);
11998 else if (!tape.playing) // if game continues after tape stopped playing
11999 SetOverlayActive(TRUE);
12004 // ---------- main game synchronization point ----------
12006 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12008 Debug("game:playing:skip", "skip == %d", skip);
12011 // ---------- main game synchronization point ----------
12013 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12017 if (network_playing && !network_player_action_received)
12019 // try to get network player actions in time
12021 // last chance to get network player actions without main loop delay
12022 HandleNetworking();
12024 // game was quit by network peer
12025 if (game_status != GAME_MODE_PLAYING)
12028 // check if network player actions still missing and game still running
12029 if (!network_player_action_received && !checkGameEnded())
12030 return; // failed to get network player actions in time
12032 // do not yet reset "network_player_action_received" (for tape.pausing)
12038 // at this point we know that we really continue executing the game
12040 network_player_action_received = FALSE;
12042 // when playing tape, read previously recorded player input from tape data
12043 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
12045 local_player->effective_mouse_action = local_player->mouse_action;
12047 if (recorded_player_action != NULL)
12048 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
12049 recorded_player_action);
12051 // TapePlayAction() may return NULL when toggling to "pause before death"
12055 if (tape.set_centered_player)
12057 game.centered_player_nr_next = tape.centered_player_nr_next;
12058 game.set_centered_player = TRUE;
12061 for (i = 0; i < MAX_PLAYERS; i++)
12063 summarized_player_action |= stored_player[i].action;
12065 if (!network_playing && (game.team_mode || tape.playing))
12066 stored_player[i].effective_action = stored_player[i].action;
12069 if (network_playing && !checkGameEnded())
12070 SendToServer_MovePlayer(summarized_player_action);
12072 // summarize all actions at local players mapped input device position
12073 // (this allows using different input devices in single player mode)
12074 if (!network.enabled && !game.team_mode)
12075 stored_player[map_player_action[local_player->index_nr]].effective_action =
12076 summarized_player_action;
12078 // summarize all actions at centered player in local team mode
12079 if (tape.recording &&
12080 setup.team_mode && !network.enabled &&
12081 setup.input_on_focus &&
12082 game.centered_player_nr != -1)
12084 for (i = 0; i < MAX_PLAYERS; i++)
12085 stored_player[map_player_action[i]].effective_action =
12086 (i == game.centered_player_nr ? summarized_player_action : 0);
12089 if (recorded_player_action != NULL)
12090 for (i = 0; i < MAX_PLAYERS; i++)
12091 stored_player[i].effective_action = recorded_player_action[i];
12093 for (i = 0; i < MAX_PLAYERS; i++)
12095 tape_action[i] = stored_player[i].effective_action;
12097 /* (this may happen in the RND game engine if a player was not present on
12098 the playfield on level start, but appeared later from a custom element */
12099 if (setup.team_mode &&
12102 !tape.player_participates[i])
12103 tape.player_participates[i] = TRUE;
12106 SetTapeActionFromMouseAction(tape_action,
12107 &local_player->effective_mouse_action);
12109 // only record actions from input devices, but not programmed actions
12110 if (tape.recording)
12111 TapeRecordAction(tape_action);
12113 // remember if game was played (especially after tape stopped playing)
12114 if (!tape.playing && summarized_player_action && !checkGameFailed())
12115 game.GamePlayed = TRUE;
12117 #if USE_NEW_PLAYER_ASSIGNMENTS
12118 // !!! also map player actions in single player mode !!!
12119 // if (game.team_mode)
12122 byte mapped_action[MAX_PLAYERS];
12124 #if DEBUG_PLAYER_ACTIONS
12125 for (i = 0; i < MAX_PLAYERS; i++)
12126 DebugContinued("", "%d, ", stored_player[i].effective_action);
12129 for (i = 0; i < MAX_PLAYERS; i++)
12130 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
12132 for (i = 0; i < MAX_PLAYERS; i++)
12133 stored_player[i].effective_action = mapped_action[i];
12135 #if DEBUG_PLAYER_ACTIONS
12136 DebugContinued("", "=> ");
12137 for (i = 0; i < MAX_PLAYERS; i++)
12138 DebugContinued("", "%d, ", stored_player[i].effective_action);
12139 DebugContinued("game:playing:player", "\n");
12142 #if DEBUG_PLAYER_ACTIONS
12145 for (i = 0; i < MAX_PLAYERS; i++)
12146 DebugContinued("", "%d, ", stored_player[i].effective_action);
12147 DebugContinued("game:playing:player", "\n");
12152 for (i = 0; i < MAX_PLAYERS; i++)
12154 // allow engine snapshot in case of changed movement attempt
12155 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12156 (stored_player[i].effective_action & KEY_MOTION))
12157 game.snapshot.changed_action = TRUE;
12159 // allow engine snapshot in case of snapping/dropping attempt
12160 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12161 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12162 game.snapshot.changed_action = TRUE;
12164 game.snapshot.last_action[i] = stored_player[i].effective_action;
12167 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12169 GameActions_EM_Main();
12171 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12173 GameActions_SP_Main();
12175 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12177 GameActions_MM_Main();
12181 GameActions_RND_Main();
12184 BlitScreenToBitmap(backbuffer);
12186 CheckLevelSolved();
12189 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12191 if (global.show_frames_per_second)
12193 static unsigned int fps_counter = 0;
12194 static int fps_frames = 0;
12195 unsigned int fps_delay_ms = Counter() - fps_counter;
12199 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12201 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12204 fps_counter = Counter();
12206 // always draw FPS to screen after FPS value was updated
12207 redraw_mask |= REDRAW_FPS;
12210 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12211 if (GetDrawDeactivationMask() == REDRAW_NONE)
12212 redraw_mask |= REDRAW_FPS;
12216 static void GameActions_CheckSaveEngineSnapshot(void)
12218 if (!game.snapshot.save_snapshot)
12221 // clear flag for saving snapshot _before_ saving snapshot
12222 game.snapshot.save_snapshot = FALSE;
12224 SaveEngineSnapshotToList();
12227 void GameActions(void)
12231 GameActions_CheckSaveEngineSnapshot();
12234 void GameActions_EM_Main(void)
12236 byte effective_action[MAX_PLAYERS];
12239 for (i = 0; i < MAX_PLAYERS; i++)
12240 effective_action[i] = stored_player[i].effective_action;
12242 GameActions_EM(effective_action);
12245 void GameActions_SP_Main(void)
12247 byte effective_action[MAX_PLAYERS];
12250 for (i = 0; i < MAX_PLAYERS; i++)
12251 effective_action[i] = stored_player[i].effective_action;
12253 GameActions_SP(effective_action);
12255 for (i = 0; i < MAX_PLAYERS; i++)
12257 if (stored_player[i].force_dropping)
12258 stored_player[i].action |= KEY_BUTTON_DROP;
12260 stored_player[i].force_dropping = FALSE;
12264 void GameActions_MM_Main(void)
12268 GameActions_MM(local_player->effective_mouse_action);
12271 void GameActions_RND_Main(void)
12276 void GameActions_RND(void)
12278 static struct MouseActionInfo mouse_action_last = { 0 };
12279 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12280 int magic_wall_x = 0, magic_wall_y = 0;
12281 int i, x, y, element, graphic, last_gfx_frame;
12283 InitPlayfieldScanModeVars();
12285 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12287 SCAN_PLAYFIELD(x, y)
12289 ChangeCount[x][y] = 0;
12290 ChangeEvent[x][y] = -1;
12294 if (game.set_centered_player)
12296 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12298 // switching to "all players" only possible if all players fit to screen
12299 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12301 game.centered_player_nr_next = game.centered_player_nr;
12302 game.set_centered_player = FALSE;
12305 // do not switch focus to non-existing (or non-active) player
12306 if (game.centered_player_nr_next >= 0 &&
12307 !stored_player[game.centered_player_nr_next].active)
12309 game.centered_player_nr_next = game.centered_player_nr;
12310 game.set_centered_player = FALSE;
12314 if (game.set_centered_player &&
12315 ScreenMovPos == 0) // screen currently aligned at tile position
12319 if (game.centered_player_nr_next == -1)
12321 setScreenCenteredToAllPlayers(&sx, &sy);
12325 sx = stored_player[game.centered_player_nr_next].jx;
12326 sy = stored_player[game.centered_player_nr_next].jy;
12329 game.centered_player_nr = game.centered_player_nr_next;
12330 game.set_centered_player = FALSE;
12332 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12333 DrawGameDoorValues();
12336 // check single step mode (set flag and clear again if any player is active)
12337 game.enter_single_step_mode =
12338 (tape.single_step && tape.recording && !tape.pausing);
12340 for (i = 0; i < MAX_PLAYERS; i++)
12342 int actual_player_action = stored_player[i].effective_action;
12345 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12346 - rnd_equinox_tetrachloride 048
12347 - rnd_equinox_tetrachloride_ii 096
12348 - rnd_emanuel_schmieg 002
12349 - doctor_sloan_ww 001, 020
12351 if (stored_player[i].MovPos == 0)
12352 CheckGravityMovement(&stored_player[i]);
12355 // overwrite programmed action with tape action
12356 if (stored_player[i].programmed_action)
12357 actual_player_action = stored_player[i].programmed_action;
12359 PlayerActions(&stored_player[i], actual_player_action);
12361 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12364 // single step pause mode may already have been toggled by "ScrollPlayer()"
12365 if (game.enter_single_step_mode && !tape.pausing)
12366 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12368 ScrollScreen(NULL, SCROLL_GO_ON);
12370 /* for backwards compatibility, the following code emulates a fixed bug that
12371 occured when pushing elements (causing elements that just made their last
12372 pushing step to already (if possible) make their first falling step in the
12373 same game frame, which is bad); this code is also needed to use the famous
12374 "spring push bug" which is used in older levels and might be wanted to be
12375 used also in newer levels, but in this case the buggy pushing code is only
12376 affecting the "spring" element and no other elements */
12378 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12380 for (i = 0; i < MAX_PLAYERS; i++)
12382 struct PlayerInfo *player = &stored_player[i];
12383 int x = player->jx;
12384 int y = player->jy;
12386 if (player->active && player->is_pushing && player->is_moving &&
12388 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12389 Tile[x][y] == EL_SPRING))
12391 ContinueMoving(x, y);
12393 // continue moving after pushing (this is actually a bug)
12394 if (!IS_MOVING(x, y))
12395 Stop[x][y] = FALSE;
12400 SCAN_PLAYFIELD(x, y)
12402 Last[x][y] = Tile[x][y];
12404 ChangeCount[x][y] = 0;
12405 ChangeEvent[x][y] = -1;
12407 // this must be handled before main playfield loop
12408 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12411 if (MovDelay[x][y] <= 0)
12415 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12418 if (MovDelay[x][y] <= 0)
12420 int element = Store[x][y];
12421 int move_direction = MovDir[x][y];
12422 int player_index_bit = Store2[x][y];
12428 TEST_DrawLevelField(x, y);
12430 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12432 if (IS_ENVELOPE(element))
12433 local_player->show_envelope = element;
12438 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12440 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12442 Debug("game:playing:GameActions_RND", "This should never happen!");
12444 ChangePage[x][y] = -1;
12448 Stop[x][y] = FALSE;
12449 if (WasJustMoving[x][y] > 0)
12450 WasJustMoving[x][y]--;
12451 if (WasJustFalling[x][y] > 0)
12452 WasJustFalling[x][y]--;
12453 if (CheckCollision[x][y] > 0)
12454 CheckCollision[x][y]--;
12455 if (CheckImpact[x][y] > 0)
12456 CheckImpact[x][y]--;
12460 /* reset finished pushing action (not done in ContinueMoving() to allow
12461 continuous pushing animation for elements with zero push delay) */
12462 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12464 ResetGfxAnimation(x, y);
12465 TEST_DrawLevelField(x, y);
12469 if (IS_BLOCKED(x, y))
12473 Blocked2Moving(x, y, &oldx, &oldy);
12474 if (!IS_MOVING(oldx, oldy))
12476 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12477 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12478 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12479 Debug("game:playing:GameActions_RND", "This should never happen!");
12485 HandleMouseAction(&mouse_action, &mouse_action_last);
12487 SCAN_PLAYFIELD(x, y)
12489 element = Tile[x][y];
12490 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12491 last_gfx_frame = GfxFrame[x][y];
12493 if (element == EL_EMPTY)
12494 graphic = el2img(GfxElementEmpty[x][y]);
12496 ResetGfxFrame(x, y);
12498 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12499 DrawLevelGraphicAnimation(x, y, graphic);
12501 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12502 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12503 ResetRandomAnimationValue(x, y);
12505 SetRandomAnimationValue(x, y);
12507 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12509 if (IS_INACTIVE(element))
12511 if (IS_ANIMATED(graphic))
12512 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12517 // this may take place after moving, so 'element' may have changed
12518 if (IS_CHANGING(x, y) &&
12519 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12521 int page = element_info[element].event_page_nr[CE_DELAY];
12523 HandleElementChange(x, y, page);
12525 element = Tile[x][y];
12526 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12529 CheckNextToConditions(x, y);
12531 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12535 element = Tile[x][y];
12536 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12538 if (IS_ANIMATED(graphic) &&
12539 !IS_MOVING(x, y) &&
12541 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12543 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12544 TEST_DrawTwinkleOnField(x, y);
12546 else if (element == EL_ACID)
12549 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12551 else if ((element == EL_EXIT_OPEN ||
12552 element == EL_EM_EXIT_OPEN ||
12553 element == EL_SP_EXIT_OPEN ||
12554 element == EL_STEEL_EXIT_OPEN ||
12555 element == EL_EM_STEEL_EXIT_OPEN ||
12556 element == EL_SP_TERMINAL ||
12557 element == EL_SP_TERMINAL_ACTIVE ||
12558 element == EL_EXTRA_TIME ||
12559 element == EL_SHIELD_NORMAL ||
12560 element == EL_SHIELD_DEADLY) &&
12561 IS_ANIMATED(graphic))
12562 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12563 else if (IS_MOVING(x, y))
12564 ContinueMoving(x, y);
12565 else if (IS_ACTIVE_BOMB(element))
12566 CheckDynamite(x, y);
12567 else if (element == EL_AMOEBA_GROWING)
12568 AmoebaGrowing(x, y);
12569 else if (element == EL_AMOEBA_SHRINKING)
12570 AmoebaShrinking(x, y);
12572 #if !USE_NEW_AMOEBA_CODE
12573 else if (IS_AMOEBALIVE(element))
12574 AmoebaReproduce(x, y);
12577 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12579 else if (element == EL_EXIT_CLOSED)
12581 else if (element == EL_EM_EXIT_CLOSED)
12583 else if (element == EL_STEEL_EXIT_CLOSED)
12584 CheckExitSteel(x, y);
12585 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12586 CheckExitSteelEM(x, y);
12587 else if (element == EL_SP_EXIT_CLOSED)
12589 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12590 element == EL_EXPANDABLE_STEELWALL_GROWING)
12592 else if (element == EL_EXPANDABLE_WALL ||
12593 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12594 element == EL_EXPANDABLE_WALL_VERTICAL ||
12595 element == EL_EXPANDABLE_WALL_ANY ||
12596 element == EL_BD_EXPANDABLE_WALL ||
12597 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12598 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12599 element == EL_EXPANDABLE_STEELWALL_ANY)
12600 CheckWallGrowing(x, y);
12601 else if (element == EL_FLAMES)
12602 CheckForDragon(x, y);
12603 else if (element == EL_EXPLOSION)
12604 ; // drawing of correct explosion animation is handled separately
12605 else if (element == EL_ELEMENT_SNAPPING ||
12606 element == EL_DIAGONAL_SHRINKING ||
12607 element == EL_DIAGONAL_GROWING)
12609 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]);
12611 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12613 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12614 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12616 if (IS_BELT_ACTIVE(element))
12617 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12619 if (game.magic_wall_active)
12621 int jx = local_player->jx, jy = local_player->jy;
12623 // play the element sound at the position nearest to the player
12624 if ((element == EL_MAGIC_WALL_FULL ||
12625 element == EL_MAGIC_WALL_ACTIVE ||
12626 element == EL_MAGIC_WALL_EMPTYING ||
12627 element == EL_BD_MAGIC_WALL_FULL ||
12628 element == EL_BD_MAGIC_WALL_ACTIVE ||
12629 element == EL_BD_MAGIC_WALL_EMPTYING ||
12630 element == EL_DC_MAGIC_WALL_FULL ||
12631 element == EL_DC_MAGIC_WALL_ACTIVE ||
12632 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12633 ABS(x - jx) + ABS(y - jy) <
12634 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12642 #if USE_NEW_AMOEBA_CODE
12643 // new experimental amoeba growth stuff
12644 if (!(FrameCounter % 8))
12646 static unsigned int random = 1684108901;
12648 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12650 x = RND(lev_fieldx);
12651 y = RND(lev_fieldy);
12652 element = Tile[x][y];
12654 if (!IS_PLAYER(x, y) &&
12655 (element == EL_EMPTY ||
12656 CAN_GROW_INTO(element) ||
12657 element == EL_QUICKSAND_EMPTY ||
12658 element == EL_QUICKSAND_FAST_EMPTY ||
12659 element == EL_ACID_SPLASH_LEFT ||
12660 element == EL_ACID_SPLASH_RIGHT))
12662 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12663 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12664 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12665 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12666 Tile[x][y] = EL_AMOEBA_DROP;
12669 random = random * 129 + 1;
12674 game.explosions_delayed = FALSE;
12676 SCAN_PLAYFIELD(x, y)
12678 element = Tile[x][y];
12680 if (ExplodeField[x][y])
12681 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12682 else if (element == EL_EXPLOSION)
12683 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12685 ExplodeField[x][y] = EX_TYPE_NONE;
12688 game.explosions_delayed = TRUE;
12690 if (game.magic_wall_active)
12692 if (!(game.magic_wall_time_left % 4))
12694 int element = Tile[magic_wall_x][magic_wall_y];
12696 if (element == EL_BD_MAGIC_WALL_FULL ||
12697 element == EL_BD_MAGIC_WALL_ACTIVE ||
12698 element == EL_BD_MAGIC_WALL_EMPTYING)
12699 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12700 else if (element == EL_DC_MAGIC_WALL_FULL ||
12701 element == EL_DC_MAGIC_WALL_ACTIVE ||
12702 element == EL_DC_MAGIC_WALL_EMPTYING)
12703 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12705 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12708 if (game.magic_wall_time_left > 0)
12710 game.magic_wall_time_left--;
12712 if (!game.magic_wall_time_left)
12714 SCAN_PLAYFIELD(x, y)
12716 element = Tile[x][y];
12718 if (element == EL_MAGIC_WALL_ACTIVE ||
12719 element == EL_MAGIC_WALL_FULL)
12721 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12722 TEST_DrawLevelField(x, y);
12724 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12725 element == EL_BD_MAGIC_WALL_FULL)
12727 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12728 TEST_DrawLevelField(x, y);
12730 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12731 element == EL_DC_MAGIC_WALL_FULL)
12733 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12734 TEST_DrawLevelField(x, y);
12738 game.magic_wall_active = FALSE;
12743 if (game.light_time_left > 0)
12745 game.light_time_left--;
12747 if (game.light_time_left == 0)
12748 RedrawAllLightSwitchesAndInvisibleElements();
12751 if (game.timegate_time_left > 0)
12753 game.timegate_time_left--;
12755 if (game.timegate_time_left == 0)
12756 CloseAllOpenTimegates();
12759 if (game.lenses_time_left > 0)
12761 game.lenses_time_left--;
12763 if (game.lenses_time_left == 0)
12764 RedrawAllInvisibleElementsForLenses();
12767 if (game.magnify_time_left > 0)
12769 game.magnify_time_left--;
12771 if (game.magnify_time_left == 0)
12772 RedrawAllInvisibleElementsForMagnifier();
12775 for (i = 0; i < MAX_PLAYERS; i++)
12777 struct PlayerInfo *player = &stored_player[i];
12779 if (SHIELD_ON(player))
12781 if (player->shield_deadly_time_left)
12782 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12783 else if (player->shield_normal_time_left)
12784 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12788 #if USE_DELAYED_GFX_REDRAW
12789 SCAN_PLAYFIELD(x, y)
12791 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12793 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12794 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12796 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12797 DrawLevelField(x, y);
12799 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12800 DrawLevelFieldCrumbled(x, y);
12802 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12803 DrawLevelFieldCrumbledNeighbours(x, y);
12805 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12806 DrawTwinkleOnField(x, y);
12809 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12814 PlayAllPlayersSound();
12816 for (i = 0; i < MAX_PLAYERS; i++)
12818 struct PlayerInfo *player = &stored_player[i];
12820 if (player->show_envelope != 0 && (!player->active ||
12821 player->MovPos == 0))
12823 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12825 player->show_envelope = 0;
12829 // use random number generator in every frame to make it less predictable
12830 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12833 mouse_action_last = mouse_action;
12836 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12838 int min_x = x, min_y = y, max_x = x, max_y = y;
12839 int scr_fieldx = getScreenFieldSizeX();
12840 int scr_fieldy = getScreenFieldSizeY();
12843 for (i = 0; i < MAX_PLAYERS; i++)
12845 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12847 if (!stored_player[i].active || &stored_player[i] == player)
12850 min_x = MIN(min_x, jx);
12851 min_y = MIN(min_y, jy);
12852 max_x = MAX(max_x, jx);
12853 max_y = MAX(max_y, jy);
12856 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12859 static boolean AllPlayersInVisibleScreen(void)
12863 for (i = 0; i < MAX_PLAYERS; i++)
12865 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12867 if (!stored_player[i].active)
12870 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12877 void ScrollLevel(int dx, int dy)
12879 int scroll_offset = 2 * TILEX_VAR;
12882 BlitBitmap(drawto_field, drawto_field,
12883 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12884 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12885 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12886 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12887 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12888 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12892 x = (dx == 1 ? BX1 : BX2);
12893 for (y = BY1; y <= BY2; y++)
12894 DrawScreenField(x, y);
12899 y = (dy == 1 ? BY1 : BY2);
12900 for (x = BX1; x <= BX2; x++)
12901 DrawScreenField(x, y);
12904 redraw_mask |= REDRAW_FIELD;
12907 static boolean canFallDown(struct PlayerInfo *player)
12909 int jx = player->jx, jy = player->jy;
12911 return (IN_LEV_FIELD(jx, jy + 1) &&
12912 (IS_FREE(jx, jy + 1) ||
12913 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12914 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12915 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12918 static boolean canPassField(int x, int y, int move_dir)
12920 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12921 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12922 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12923 int nextx = x + dx;
12924 int nexty = y + dy;
12925 int element = Tile[x][y];
12927 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12928 !CAN_MOVE(element) &&
12929 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12930 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12931 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12934 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12936 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12937 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12938 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12942 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12943 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12944 (IS_DIGGABLE(Tile[newx][newy]) ||
12945 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12946 canPassField(newx, newy, move_dir)));
12949 static void CheckGravityMovement(struct PlayerInfo *player)
12951 if (player->gravity && !player->programmed_action)
12953 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12954 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12955 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12956 int jx = player->jx, jy = player->jy;
12957 boolean player_is_moving_to_valid_field =
12958 (!player_is_snapping &&
12959 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12960 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12961 boolean player_can_fall_down = canFallDown(player);
12963 if (player_can_fall_down &&
12964 !player_is_moving_to_valid_field)
12965 player->programmed_action = MV_DOWN;
12969 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12971 return CheckGravityMovement(player);
12973 if (player->gravity && !player->programmed_action)
12975 int jx = player->jx, jy = player->jy;
12976 boolean field_under_player_is_free =
12977 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12978 boolean player_is_standing_on_valid_field =
12979 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12980 (IS_WALKABLE(Tile[jx][jy]) &&
12981 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12983 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12984 player->programmed_action = MV_DOWN;
12989 MovePlayerOneStep()
12990 -----------------------------------------------------------------------------
12991 dx, dy: direction (non-diagonal) to try to move the player to
12992 real_dx, real_dy: direction as read from input device (can be diagonal)
12995 boolean MovePlayerOneStep(struct PlayerInfo *player,
12996 int dx, int dy, int real_dx, int real_dy)
12998 int jx = player->jx, jy = player->jy;
12999 int new_jx = jx + dx, new_jy = jy + dy;
13001 boolean player_can_move = !player->cannot_move;
13003 if (!player->active || (!dx && !dy))
13004 return MP_NO_ACTION;
13006 player->MovDir = (dx < 0 ? MV_LEFT :
13007 dx > 0 ? MV_RIGHT :
13009 dy > 0 ? MV_DOWN : MV_NONE);
13011 if (!IN_LEV_FIELD(new_jx, new_jy))
13012 return MP_NO_ACTION;
13014 if (!player_can_move)
13016 if (player->MovPos == 0)
13018 player->is_moving = FALSE;
13019 player->is_digging = FALSE;
13020 player->is_collecting = FALSE;
13021 player->is_snapping = FALSE;
13022 player->is_pushing = FALSE;
13026 if (!network.enabled && game.centered_player_nr == -1 &&
13027 !AllPlayersInSight(player, new_jx, new_jy))
13028 return MP_NO_ACTION;
13030 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG);
13031 if (can_move != MP_MOVING)
13034 // check if DigField() has caused relocation of the player
13035 if (player->jx != jx || player->jy != jy)
13036 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
13038 StorePlayer[jx][jy] = 0;
13039 player->last_jx = jx;
13040 player->last_jy = jy;
13041 player->jx = new_jx;
13042 player->jy = new_jy;
13043 StorePlayer[new_jx][new_jy] = player->element_nr;
13045 if (player->move_delay_value_next != -1)
13047 player->move_delay_value = player->move_delay_value_next;
13048 player->move_delay_value_next = -1;
13052 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
13054 player->step_counter++;
13056 PlayerVisit[jx][jy] = FrameCounter;
13058 player->is_moving = TRUE;
13061 // should better be called in MovePlayer(), but this breaks some tapes
13062 ScrollPlayer(player, SCROLL_INIT);
13068 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
13070 int jx = player->jx, jy = player->jy;
13071 int old_jx = jx, old_jy = jy;
13072 int moved = MP_NO_ACTION;
13074 if (!player->active)
13079 if (player->MovPos == 0)
13081 player->is_moving = FALSE;
13082 player->is_digging = FALSE;
13083 player->is_collecting = FALSE;
13084 player->is_snapping = FALSE;
13085 player->is_pushing = FALSE;
13091 if (player->move_delay > 0)
13094 player->move_delay = -1; // set to "uninitialized" value
13096 // store if player is automatically moved to next field
13097 player->is_auto_moving = (player->programmed_action != MV_NONE);
13099 // remove the last programmed player action
13100 player->programmed_action = 0;
13102 if (player->MovPos)
13104 // should only happen if pre-1.2 tape recordings are played
13105 // this is only for backward compatibility
13107 int original_move_delay_value = player->move_delay_value;
13110 Debug("game:playing:MovePlayer",
13111 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13115 // scroll remaining steps with finest movement resolution
13116 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13118 while (player->MovPos)
13120 ScrollPlayer(player, SCROLL_GO_ON);
13121 ScrollScreen(NULL, SCROLL_GO_ON);
13123 AdvanceFrameAndPlayerCounters(player->index_nr);
13126 BackToFront_WithFrameDelay(0);
13129 player->move_delay_value = original_move_delay_value;
13132 player->is_active = FALSE;
13134 if (player->last_move_dir & MV_HORIZONTAL)
13136 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13137 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13141 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13142 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13145 if (!moved && !player->is_active)
13147 player->is_moving = FALSE;
13148 player->is_digging = FALSE;
13149 player->is_collecting = FALSE;
13150 player->is_snapping = FALSE;
13151 player->is_pushing = FALSE;
13157 if (moved & MP_MOVING && !ScreenMovPos &&
13158 (player->index_nr == game.centered_player_nr ||
13159 game.centered_player_nr == -1))
13161 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13163 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13165 // actual player has left the screen -- scroll in that direction
13166 if (jx != old_jx) // player has moved horizontally
13167 scroll_x += (jx - old_jx);
13168 else // player has moved vertically
13169 scroll_y += (jy - old_jy);
13173 int offset_raw = game.scroll_delay_value;
13175 if (jx != old_jx) // player has moved horizontally
13177 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13178 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13179 int new_scroll_x = jx - MIDPOSX + offset_x;
13181 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13182 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13183 scroll_x = new_scroll_x;
13185 // don't scroll over playfield boundaries
13186 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13188 // don't scroll more than one field at a time
13189 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13191 // don't scroll against the player's moving direction
13192 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13193 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13194 scroll_x = old_scroll_x;
13196 else // player has moved vertically
13198 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13199 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13200 int new_scroll_y = jy - MIDPOSY + offset_y;
13202 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13203 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13204 scroll_y = new_scroll_y;
13206 // don't scroll over playfield boundaries
13207 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13209 // don't scroll more than one field at a time
13210 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13212 // don't scroll against the player's moving direction
13213 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13214 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13215 scroll_y = old_scroll_y;
13219 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13221 if (!network.enabled && game.centered_player_nr == -1 &&
13222 !AllPlayersInVisibleScreen())
13224 scroll_x = old_scroll_x;
13225 scroll_y = old_scroll_y;
13229 ScrollScreen(player, SCROLL_INIT);
13230 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13235 player->StepFrame = 0;
13237 if (moved & MP_MOVING)
13239 if (old_jx != jx && old_jy == jy)
13240 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13241 else if (old_jx == jx && old_jy != jy)
13242 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13244 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13246 player->last_move_dir = player->MovDir;
13247 player->is_moving = TRUE;
13248 player->is_snapping = FALSE;
13249 player->is_switching = FALSE;
13250 player->is_dropping = FALSE;
13251 player->is_dropping_pressed = FALSE;
13252 player->drop_pressed_delay = 0;
13255 // should better be called here than above, but this breaks some tapes
13256 ScrollPlayer(player, SCROLL_INIT);
13261 CheckGravityMovementWhenNotMoving(player);
13263 player->is_moving = FALSE;
13265 /* at this point, the player is allowed to move, but cannot move right now
13266 (e.g. because of something blocking the way) -- ensure that the player
13267 is also allowed to move in the next frame (in old versions before 3.1.1,
13268 the player was forced to wait again for eight frames before next try) */
13270 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13271 player->move_delay = 0; // allow direct movement in the next frame
13274 if (player->move_delay == -1) // not yet initialized by DigField()
13275 player->move_delay = player->move_delay_value;
13277 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13279 TestIfPlayerTouchesBadThing(jx, jy);
13280 TestIfPlayerTouchesCustomElement(jx, jy);
13283 if (!player->active)
13284 RemovePlayer(player);
13289 void ScrollPlayer(struct PlayerInfo *player, int mode)
13291 int jx = player->jx, jy = player->jy;
13292 int last_jx = player->last_jx, last_jy = player->last_jy;
13293 int move_stepsize = TILEX / player->move_delay_value;
13295 if (!player->active)
13298 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13301 if (mode == SCROLL_INIT)
13303 player->actual_frame_counter.count = FrameCounter;
13304 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13306 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13307 Tile[last_jx][last_jy] == EL_EMPTY)
13309 int last_field_block_delay = 0; // start with no blocking at all
13310 int block_delay_adjustment = player->block_delay_adjustment;
13312 // if player blocks last field, add delay for exactly one move
13313 if (player->block_last_field)
13315 last_field_block_delay += player->move_delay_value;
13317 // when blocking enabled, prevent moving up despite gravity
13318 if (player->gravity && player->MovDir == MV_UP)
13319 block_delay_adjustment = -1;
13322 // add block delay adjustment (also possible when not blocking)
13323 last_field_block_delay += block_delay_adjustment;
13325 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13326 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13329 if (player->MovPos != 0) // player has not yet reached destination
13332 else if (!FrameReached(&player->actual_frame_counter))
13335 if (player->MovPos != 0)
13337 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13338 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13340 // before DrawPlayer() to draw correct player graphic for this case
13341 if (player->MovPos == 0)
13342 CheckGravityMovement(player);
13345 if (player->MovPos == 0) // player reached destination field
13347 if (player->move_delay_reset_counter > 0)
13349 player->move_delay_reset_counter--;
13351 if (player->move_delay_reset_counter == 0)
13353 // continue with normal speed after quickly moving through gate
13354 HALVE_PLAYER_SPEED(player);
13356 // be able to make the next move without delay
13357 player->move_delay = 0;
13361 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13362 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13363 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13364 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13365 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13366 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13367 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13368 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13370 ExitPlayer(player);
13372 if (game.players_still_needed == 0 &&
13373 (game.friends_still_needed == 0 ||
13374 IS_SP_ELEMENT(Tile[jx][jy])))
13378 player->last_jx = jx;
13379 player->last_jy = jy;
13381 // this breaks one level: "machine", level 000
13383 int move_direction = player->MovDir;
13384 int enter_side = MV_DIR_OPPOSITE(move_direction);
13385 int leave_side = move_direction;
13386 int old_jx = last_jx;
13387 int old_jy = last_jy;
13388 int old_element = Tile[old_jx][old_jy];
13389 int new_element = Tile[jx][jy];
13391 if (IS_CUSTOM_ELEMENT(old_element))
13392 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13394 player->index_bit, leave_side);
13396 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13397 CE_PLAYER_LEAVES_X,
13398 player->index_bit, leave_side);
13400 // needed because pushed element has not yet reached its destination,
13401 // so it would trigger a change event at its previous field location
13402 if (!player->is_pushing)
13404 if (IS_CUSTOM_ELEMENT(new_element))
13405 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13406 player->index_bit, enter_side);
13408 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13409 CE_PLAYER_ENTERS_X,
13410 player->index_bit, enter_side);
13413 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13414 CE_MOVE_OF_X, move_direction);
13417 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13419 TestIfPlayerTouchesBadThing(jx, jy);
13420 TestIfPlayerTouchesCustomElement(jx, jy);
13422 // needed because pushed element has not yet reached its destination,
13423 // so it would trigger a change event at its previous field location
13424 if (!player->is_pushing)
13425 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13427 if (level.finish_dig_collect &&
13428 (player->is_digging || player->is_collecting))
13430 int last_element = player->last_removed_element;
13431 int move_direction = player->MovDir;
13432 int enter_side = MV_DIR_OPPOSITE(move_direction);
13433 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13434 CE_PLAYER_COLLECTS_X);
13436 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13437 player->index_bit, enter_side);
13439 player->last_removed_element = EL_UNDEFINED;
13442 if (!player->active)
13443 RemovePlayer(player);
13446 if (level.use_step_counter)
13447 CheckLevelTime_StepCounter();
13449 if (tape.single_step && tape.recording && !tape.pausing &&
13450 !player->programmed_action)
13451 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13453 if (!player->programmed_action)
13454 CheckSaveEngineSnapshot(player);
13458 void ScrollScreen(struct PlayerInfo *player, int mode)
13460 static DelayCounter screen_frame_counter = { 0 };
13462 if (mode == SCROLL_INIT)
13464 // set scrolling step size according to actual player's moving speed
13465 ScrollStepSize = TILEX / player->move_delay_value;
13467 screen_frame_counter.count = FrameCounter;
13468 screen_frame_counter.value = 1;
13470 ScreenMovDir = player->MovDir;
13471 ScreenMovPos = player->MovPos;
13472 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13475 else if (!FrameReached(&screen_frame_counter))
13480 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13481 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13482 redraw_mask |= REDRAW_FIELD;
13485 ScreenMovDir = MV_NONE;
13488 void CheckNextToConditions(int x, int y)
13490 int element = Tile[x][y];
13492 if (IS_PLAYER(x, y))
13493 TestIfPlayerNextToCustomElement(x, y);
13495 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13496 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13497 TestIfElementNextToCustomElement(x, y);
13500 void TestIfPlayerNextToCustomElement(int x, int y)
13502 struct XY *xy = xy_topdown;
13503 static int trigger_sides[4][2] =
13505 // center side border side
13506 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13507 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13508 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13509 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13513 if (!IS_PLAYER(x, y))
13516 struct PlayerInfo *player = PLAYERINFO(x, y);
13518 if (player->is_moving)
13521 for (i = 0; i < NUM_DIRECTIONS; i++)
13523 int xx = x + xy[i].x;
13524 int yy = y + xy[i].y;
13525 int border_side = trigger_sides[i][1];
13526 int border_element;
13528 if (!IN_LEV_FIELD(xx, yy))
13531 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13532 continue; // center and border element not connected
13534 border_element = Tile[xx][yy];
13536 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13537 player->index_bit, border_side);
13538 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13539 CE_PLAYER_NEXT_TO_X,
13540 player->index_bit, border_side);
13542 /* use player element that is initially defined in the level playfield,
13543 not the player element that corresponds to the runtime player number
13544 (example: a level that contains EL_PLAYER_3 as the only player would
13545 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13547 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13548 CE_NEXT_TO_X, border_side);
13552 void TestIfPlayerTouchesCustomElement(int x, int y)
13554 struct XY *xy = xy_topdown;
13555 static int trigger_sides[4][2] =
13557 // center side border side
13558 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13559 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13560 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13561 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13563 static int touch_dir[4] =
13565 MV_LEFT | MV_RIGHT,
13570 int center_element = Tile[x][y]; // should always be non-moving!
13573 for (i = 0; i < NUM_DIRECTIONS; i++)
13575 int xx = x + xy[i].x;
13576 int yy = y + xy[i].y;
13577 int center_side = trigger_sides[i][0];
13578 int border_side = trigger_sides[i][1];
13579 int border_element;
13581 if (!IN_LEV_FIELD(xx, yy))
13584 if (IS_PLAYER(x, y)) // player found at center element
13586 struct PlayerInfo *player = PLAYERINFO(x, y);
13588 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13589 border_element = Tile[xx][yy]; // may be moving!
13590 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13591 border_element = Tile[xx][yy];
13592 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13593 border_element = MovingOrBlocked2Element(xx, yy);
13595 continue; // center and border element do not touch
13597 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13598 player->index_bit, border_side);
13599 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13600 CE_PLAYER_TOUCHES_X,
13601 player->index_bit, border_side);
13604 /* use player element that is initially defined in the level playfield,
13605 not the player element that corresponds to the runtime player number
13606 (example: a level that contains EL_PLAYER_3 as the only player would
13607 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13608 int player_element = PLAYERINFO(x, y)->initial_element;
13610 // as element "X" is the player here, check opposite (center) side
13611 CheckElementChangeBySide(xx, yy, border_element, player_element,
13612 CE_TOUCHING_X, center_side);
13615 else if (IS_PLAYER(xx, yy)) // player found at border element
13617 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13619 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13621 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13622 continue; // center and border element do not touch
13625 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13626 player->index_bit, center_side);
13627 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13628 CE_PLAYER_TOUCHES_X,
13629 player->index_bit, center_side);
13632 /* use player element that is initially defined in the level playfield,
13633 not the player element that corresponds to the runtime player number
13634 (example: a level that contains EL_PLAYER_3 as the only player would
13635 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13636 int player_element = PLAYERINFO(xx, yy)->initial_element;
13638 // as element "X" is the player here, check opposite (border) side
13639 CheckElementChangeBySide(x, y, center_element, player_element,
13640 CE_TOUCHING_X, border_side);
13648 void TestIfElementNextToCustomElement(int x, int y)
13650 struct XY *xy = xy_topdown;
13651 static int trigger_sides[4][2] =
13653 // center side border side
13654 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13655 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13656 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13657 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13659 int center_element = Tile[x][y]; // should always be non-moving!
13662 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13665 for (i = 0; i < NUM_DIRECTIONS; i++)
13667 int xx = x + xy[i].x;
13668 int yy = y + xy[i].y;
13669 int border_side = trigger_sides[i][1];
13670 int border_element;
13672 if (!IN_LEV_FIELD(xx, yy))
13675 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13676 continue; // center and border element not connected
13678 border_element = Tile[xx][yy];
13680 // check for change of center element (but change it only once)
13681 if (CheckElementChangeBySide(x, y, center_element, border_element,
13682 CE_NEXT_TO_X, border_side))
13687 void TestIfElementTouchesCustomElement(int x, int y)
13689 struct XY *xy = xy_topdown;
13690 static int trigger_sides[4][2] =
13692 // center side border side
13693 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13694 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13695 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13696 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13698 static int touch_dir[4] =
13700 MV_LEFT | MV_RIGHT,
13705 boolean change_center_element = FALSE;
13706 int center_element = Tile[x][y]; // should always be non-moving!
13707 int border_element_old[NUM_DIRECTIONS];
13710 for (i = 0; i < NUM_DIRECTIONS; i++)
13712 int xx = x + xy[i].x;
13713 int yy = y + xy[i].y;
13714 int border_element;
13716 border_element_old[i] = -1;
13718 if (!IN_LEV_FIELD(xx, yy))
13721 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13722 border_element = Tile[xx][yy]; // may be moving!
13723 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13724 border_element = Tile[xx][yy];
13725 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13726 border_element = MovingOrBlocked2Element(xx, yy);
13728 continue; // center and border element do not touch
13730 border_element_old[i] = border_element;
13733 for (i = 0; i < NUM_DIRECTIONS; i++)
13735 int xx = x + xy[i].x;
13736 int yy = y + xy[i].y;
13737 int center_side = trigger_sides[i][0];
13738 int border_element = border_element_old[i];
13740 if (border_element == -1)
13743 // check for change of border element
13744 CheckElementChangeBySide(xx, yy, border_element, center_element,
13745 CE_TOUCHING_X, center_side);
13747 // (center element cannot be player, so we don't have to check this here)
13750 for (i = 0; i < NUM_DIRECTIONS; i++)
13752 int xx = x + xy[i].x;
13753 int yy = y + xy[i].y;
13754 int border_side = trigger_sides[i][1];
13755 int border_element = border_element_old[i];
13757 if (border_element == -1)
13760 // check for change of center element (but change it only once)
13761 if (!change_center_element)
13762 change_center_element =
13763 CheckElementChangeBySide(x, y, center_element, border_element,
13764 CE_TOUCHING_X, border_side);
13766 if (IS_PLAYER(xx, yy))
13768 /* use player element that is initially defined in the level playfield,
13769 not the player element that corresponds to the runtime player number
13770 (example: a level that contains EL_PLAYER_3 as the only player would
13771 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13772 int player_element = PLAYERINFO(xx, yy)->initial_element;
13774 // as element "X" is the player here, check opposite (border) side
13775 CheckElementChangeBySide(x, y, center_element, player_element,
13776 CE_TOUCHING_X, border_side);
13781 void TestIfElementHitsCustomElement(int x, int y, int direction)
13783 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13784 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13785 int hitx = x + dx, hity = y + dy;
13786 int hitting_element = Tile[x][y];
13787 int touched_element;
13789 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13792 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13793 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13795 if (IN_LEV_FIELD(hitx, hity))
13797 int opposite_direction = MV_DIR_OPPOSITE(direction);
13798 int hitting_side = direction;
13799 int touched_side = opposite_direction;
13800 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13801 MovDir[hitx][hity] != direction ||
13802 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13808 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13809 CE_HITTING_X, touched_side);
13811 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13812 CE_HIT_BY_X, hitting_side);
13814 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13815 CE_HIT_BY_SOMETHING, opposite_direction);
13817 if (IS_PLAYER(hitx, hity))
13819 /* use player element that is initially defined in the level playfield,
13820 not the player element that corresponds to the runtime player number
13821 (example: a level that contains EL_PLAYER_3 as the only player would
13822 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13823 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13825 CheckElementChangeBySide(x, y, hitting_element, player_element,
13826 CE_HITTING_X, touched_side);
13831 // "hitting something" is also true when hitting the playfield border
13832 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13833 CE_HITTING_SOMETHING, direction);
13836 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13838 int i, kill_x = -1, kill_y = -1;
13840 int bad_element = -1;
13841 struct XY *test_xy = xy_topdown;
13842 static int test_dir[4] =
13850 for (i = 0; i < NUM_DIRECTIONS; i++)
13852 int test_x, test_y, test_move_dir, test_element;
13854 test_x = good_x + test_xy[i].x;
13855 test_y = good_y + test_xy[i].y;
13857 if (!IN_LEV_FIELD(test_x, test_y))
13861 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13863 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13865 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13866 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13868 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13869 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13873 bad_element = test_element;
13879 if (kill_x != -1 || kill_y != -1)
13881 if (IS_PLAYER(good_x, good_y))
13883 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13885 if (player->shield_deadly_time_left > 0 &&
13886 !IS_INDESTRUCTIBLE(bad_element))
13887 Bang(kill_x, kill_y);
13888 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13889 KillPlayer(player);
13892 Bang(good_x, good_y);
13896 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13898 int i, kill_x = -1, kill_y = -1;
13899 int bad_element = Tile[bad_x][bad_y];
13900 struct XY *test_xy = xy_topdown;
13901 static int touch_dir[4] =
13903 MV_LEFT | MV_RIGHT,
13908 static int test_dir[4] =
13916 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13919 for (i = 0; i < NUM_DIRECTIONS; i++)
13921 int test_x, test_y, test_move_dir, test_element;
13923 test_x = bad_x + test_xy[i].x;
13924 test_y = bad_y + test_xy[i].y;
13926 if (!IN_LEV_FIELD(test_x, test_y))
13930 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13932 test_element = Tile[test_x][test_y];
13934 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13935 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13937 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13938 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13940 // good thing is player or penguin that does not move away
13941 if (IS_PLAYER(test_x, test_y))
13943 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13945 if (bad_element == EL_ROBOT && player->is_moving)
13946 continue; // robot does not kill player if he is moving
13948 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13950 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13951 continue; // center and border element do not touch
13959 else if (test_element == EL_PENGUIN)
13969 if (kill_x != -1 || kill_y != -1)
13971 if (IS_PLAYER(kill_x, kill_y))
13973 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13975 if (player->shield_deadly_time_left > 0 &&
13976 !IS_INDESTRUCTIBLE(bad_element))
13977 Bang(bad_x, bad_y);
13978 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13979 KillPlayer(player);
13982 Bang(kill_x, kill_y);
13986 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13988 int bad_element = Tile[bad_x][bad_y];
13989 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13990 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13991 int test_x = bad_x + dx, test_y = bad_y + dy;
13992 int test_move_dir, test_element;
13993 int kill_x = -1, kill_y = -1;
13995 if (!IN_LEV_FIELD(test_x, test_y))
13999 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14001 test_element = Tile[test_x][test_y];
14003 if (test_move_dir != bad_move_dir)
14005 // good thing can be player or penguin that does not move away
14006 if (IS_PLAYER(test_x, test_y))
14008 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14010 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
14011 player as being hit when he is moving towards the bad thing, because
14012 the "get hit by" condition would be lost after the player stops) */
14013 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
14014 return; // player moves away from bad thing
14019 else if (test_element == EL_PENGUIN)
14026 if (kill_x != -1 || kill_y != -1)
14028 if (IS_PLAYER(kill_x, kill_y))
14030 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14032 if (player->shield_deadly_time_left > 0 &&
14033 !IS_INDESTRUCTIBLE(bad_element))
14034 Bang(bad_x, bad_y);
14035 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14036 KillPlayer(player);
14039 Bang(kill_x, kill_y);
14043 void TestIfPlayerTouchesBadThing(int x, int y)
14045 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14048 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
14050 TestIfGoodThingHitsBadThing(x, y, move_dir);
14053 void TestIfBadThingTouchesPlayer(int x, int y)
14055 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14058 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
14060 TestIfBadThingHitsGoodThing(x, y, move_dir);
14063 void TestIfFriendTouchesBadThing(int x, int y)
14065 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14068 void TestIfBadThingTouchesFriend(int x, int y)
14070 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14073 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
14075 int i, kill_x = bad_x, kill_y = bad_y;
14076 struct XY *xy = xy_topdown;
14078 for (i = 0; i < NUM_DIRECTIONS; i++)
14082 x = bad_x + xy[i].x;
14083 y = bad_y + xy[i].y;
14084 if (!IN_LEV_FIELD(x, y))
14087 element = Tile[x][y];
14088 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14089 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14097 if (kill_x != bad_x || kill_y != bad_y)
14098 Bang(bad_x, bad_y);
14101 void KillPlayer(struct PlayerInfo *player)
14103 int jx = player->jx, jy = player->jy;
14105 if (!player->active)
14109 Debug("game:playing:KillPlayer",
14110 "0: killed == %d, active == %d, reanimated == %d",
14111 player->killed, player->active, player->reanimated);
14114 /* the following code was introduced to prevent an infinite loop when calling
14116 -> CheckTriggeredElementChangeExt()
14117 -> ExecuteCustomElementAction()
14119 -> (infinitely repeating the above sequence of function calls)
14120 which occurs when killing the player while having a CE with the setting
14121 "kill player X when explosion of <player X>"; the solution using a new
14122 field "player->killed" was chosen for backwards compatibility, although
14123 clever use of the fields "player->active" etc. would probably also work */
14125 if (player->killed)
14129 player->killed = TRUE;
14131 // remove accessible field at the player's position
14132 RemoveField(jx, jy);
14134 // deactivate shield (else Bang()/Explode() would not work right)
14135 player->shield_normal_time_left = 0;
14136 player->shield_deadly_time_left = 0;
14139 Debug("game:playing:KillPlayer",
14140 "1: killed == %d, active == %d, reanimated == %d",
14141 player->killed, player->active, player->reanimated);
14147 Debug("game:playing:KillPlayer",
14148 "2: killed == %d, active == %d, reanimated == %d",
14149 player->killed, player->active, player->reanimated);
14152 if (player->reanimated) // killed player may have been reanimated
14153 player->killed = player->reanimated = FALSE;
14155 BuryPlayer(player);
14158 static void KillPlayerUnlessEnemyProtected(int x, int y)
14160 if (!PLAYER_ENEMY_PROTECTED(x, y))
14161 KillPlayer(PLAYERINFO(x, y));
14164 static void KillPlayerUnlessExplosionProtected(int x, int y)
14166 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14167 KillPlayer(PLAYERINFO(x, y));
14170 void BuryPlayer(struct PlayerInfo *player)
14172 int jx = player->jx, jy = player->jy;
14174 if (!player->active)
14177 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14179 RemovePlayer(player);
14181 player->buried = TRUE;
14183 if (game.all_players_gone)
14184 game.GameOver = TRUE;
14187 void RemovePlayer(struct PlayerInfo *player)
14189 int jx = player->jx, jy = player->jy;
14190 int i, found = FALSE;
14192 player->present = FALSE;
14193 player->active = FALSE;
14195 // required for some CE actions (even if the player is not active anymore)
14196 player->MovPos = 0;
14198 if (!ExplodeField[jx][jy])
14199 StorePlayer[jx][jy] = 0;
14201 if (player->is_moving)
14202 TEST_DrawLevelField(player->last_jx, player->last_jy);
14204 for (i = 0; i < MAX_PLAYERS; i++)
14205 if (stored_player[i].active)
14210 game.all_players_gone = TRUE;
14211 game.GameOver = TRUE;
14214 game.exit_x = game.robot_wheel_x = jx;
14215 game.exit_y = game.robot_wheel_y = jy;
14218 void ExitPlayer(struct PlayerInfo *player)
14220 DrawPlayer(player); // needed here only to cleanup last field
14221 RemovePlayer(player);
14223 if (game.players_still_needed > 0)
14224 game.players_still_needed--;
14227 static void SetFieldForSnapping(int x, int y, int element, int direction,
14228 int player_index_bit)
14230 struct ElementInfo *ei = &element_info[element];
14231 int direction_bit = MV_DIR_TO_BIT(direction);
14232 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14233 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14234 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14236 Tile[x][y] = EL_ELEMENT_SNAPPING;
14237 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14238 MovDir[x][y] = direction;
14239 Store[x][y] = element;
14240 Store2[x][y] = player_index_bit;
14242 ResetGfxAnimation(x, y);
14244 GfxElement[x][y] = element;
14245 GfxAction[x][y] = action;
14246 GfxDir[x][y] = direction;
14247 GfxFrame[x][y] = -1;
14250 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14251 int player_index_bit)
14253 TestIfElementTouchesCustomElement(x, y); // for empty space
14255 if (level.finish_dig_collect)
14257 int dig_side = MV_DIR_OPPOSITE(direction);
14258 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14259 CE_PLAYER_COLLECTS_X);
14261 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14262 player_index_bit, dig_side);
14263 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14264 player_index_bit, dig_side);
14269 =============================================================================
14270 checkDiagonalPushing()
14271 -----------------------------------------------------------------------------
14272 check if diagonal input device direction results in pushing of object
14273 (by checking if the alternative direction is walkable, diggable, ...)
14274 =============================================================================
14277 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14278 int x, int y, int real_dx, int real_dy)
14280 int jx, jy, dx, dy, xx, yy;
14282 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14285 // diagonal direction: check alternative direction
14290 xx = jx + (dx == 0 ? real_dx : 0);
14291 yy = jy + (dy == 0 ? real_dy : 0);
14293 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14297 =============================================================================
14299 -----------------------------------------------------------------------------
14300 x, y: field next to player (non-diagonal) to try to dig to
14301 real_dx, real_dy: direction as read from input device (can be diagonal)
14302 =============================================================================
14305 static int DigField(struct PlayerInfo *player,
14306 int oldx, int oldy, int x, int y,
14307 int real_dx, int real_dy, int mode)
14309 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14310 boolean player_was_pushing = player->is_pushing;
14311 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14312 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14313 int jx = oldx, jy = oldy;
14314 int dx = x - jx, dy = y - jy;
14315 int nextx = x + dx, nexty = y + dy;
14316 int move_direction = (dx == -1 ? MV_LEFT :
14317 dx == +1 ? MV_RIGHT :
14319 dy == +1 ? MV_DOWN : MV_NONE);
14320 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14321 int dig_side = MV_DIR_OPPOSITE(move_direction);
14322 int old_element = Tile[jx][jy];
14323 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14326 if (is_player) // function can also be called by EL_PENGUIN
14328 if (player->MovPos == 0)
14330 player->is_digging = FALSE;
14331 player->is_collecting = FALSE;
14334 if (player->MovPos == 0) // last pushing move finished
14335 player->is_pushing = FALSE;
14337 if (mode == DF_NO_PUSH) // player just stopped pushing
14339 player->is_switching = FALSE;
14340 player->push_delay = -1;
14342 return MP_NO_ACTION;
14345 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14346 old_element = Back[jx][jy];
14348 // in case of element dropped at player position, check background
14349 else if (Back[jx][jy] != EL_EMPTY &&
14350 game.engine_version >= VERSION_IDENT(2,2,0,0))
14351 old_element = Back[jx][jy];
14353 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14354 return MP_NO_ACTION; // field has no opening in this direction
14356 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction))
14357 return MP_NO_ACTION; // field has no opening in this direction
14359 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14363 Tile[jx][jy] = player->artwork_element;
14364 InitMovingField(jx, jy, MV_DOWN);
14365 Store[jx][jy] = EL_ACID;
14366 ContinueMoving(jx, jy);
14367 BuryPlayer(player);
14369 return MP_DONT_RUN_INTO;
14372 if (player_can_move && DONT_RUN_INTO(element))
14374 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14376 return MP_DONT_RUN_INTO;
14379 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14380 return MP_NO_ACTION;
14382 collect_count = element_info[element].collect_count_initial;
14384 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14385 return MP_NO_ACTION;
14387 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14388 player_can_move = player_can_move_or_snap;
14390 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14391 game.engine_version >= VERSION_IDENT(2,2,0,0))
14393 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14394 player->index_bit, dig_side);
14395 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14396 player->index_bit, dig_side);
14398 if (element == EL_DC_LANDMINE)
14401 if (Tile[x][y] != element) // field changed by snapping
14404 return MP_NO_ACTION;
14407 if (player->gravity && is_player && !player->is_auto_moving &&
14408 canFallDown(player) && move_direction != MV_DOWN &&
14409 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14410 return MP_NO_ACTION; // player cannot walk here due to gravity
14412 if (player_can_move &&
14413 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14415 int sound_element = SND_ELEMENT(element);
14416 int sound_action = ACTION_WALKING;
14418 if (IS_RND_GATE(element))
14420 if (!player->key[RND_GATE_NR(element)])
14421 return MP_NO_ACTION;
14423 else if (IS_RND_GATE_GRAY(element))
14425 if (!player->key[RND_GATE_GRAY_NR(element)])
14426 return MP_NO_ACTION;
14428 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14430 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14431 return MP_NO_ACTION;
14433 else if (element == EL_EXIT_OPEN ||
14434 element == EL_EM_EXIT_OPEN ||
14435 element == EL_EM_EXIT_OPENING ||
14436 element == EL_STEEL_EXIT_OPEN ||
14437 element == EL_EM_STEEL_EXIT_OPEN ||
14438 element == EL_EM_STEEL_EXIT_OPENING ||
14439 element == EL_SP_EXIT_OPEN ||
14440 element == EL_SP_EXIT_OPENING)
14442 sound_action = ACTION_PASSING; // player is passing exit
14444 else if (element == EL_EMPTY)
14446 sound_action = ACTION_MOVING; // nothing to walk on
14449 // play sound from background or player, whatever is available
14450 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14451 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14453 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14455 else if (player_can_move &&
14456 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14458 if (!ACCESS_FROM(element, opposite_direction))
14459 return MP_NO_ACTION; // field not accessible from this direction
14461 if (CAN_MOVE(element)) // only fixed elements can be passed!
14462 return MP_NO_ACTION;
14464 if (IS_EM_GATE(element))
14466 if (!player->key[EM_GATE_NR(element)])
14467 return MP_NO_ACTION;
14469 else if (IS_EM_GATE_GRAY(element))
14471 if (!player->key[EM_GATE_GRAY_NR(element)])
14472 return MP_NO_ACTION;
14474 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14476 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14477 return MP_NO_ACTION;
14479 else if (IS_EMC_GATE(element))
14481 if (!player->key[EMC_GATE_NR(element)])
14482 return MP_NO_ACTION;
14484 else if (IS_EMC_GATE_GRAY(element))
14486 if (!player->key[EMC_GATE_GRAY_NR(element)])
14487 return MP_NO_ACTION;
14489 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14491 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14492 return MP_NO_ACTION;
14494 else if (element == EL_DC_GATE_WHITE ||
14495 element == EL_DC_GATE_WHITE_GRAY ||
14496 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14498 if (player->num_white_keys == 0)
14499 return MP_NO_ACTION;
14501 player->num_white_keys--;
14503 else if (IS_SP_PORT(element))
14505 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14506 element == EL_SP_GRAVITY_PORT_RIGHT ||
14507 element == EL_SP_GRAVITY_PORT_UP ||
14508 element == EL_SP_GRAVITY_PORT_DOWN)
14509 player->gravity = !player->gravity;
14510 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14511 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14512 element == EL_SP_GRAVITY_ON_PORT_UP ||
14513 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14514 player->gravity = TRUE;
14515 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14516 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14517 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14518 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14519 player->gravity = FALSE;
14522 // automatically move to the next field with double speed
14523 player->programmed_action = move_direction;
14525 if (player->move_delay_reset_counter == 0)
14527 player->move_delay_reset_counter = 2; // two double speed steps
14529 DOUBLE_PLAYER_SPEED(player);
14532 PlayLevelSoundAction(x, y, ACTION_PASSING);
14534 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14538 if (mode != DF_SNAP)
14540 GfxElement[x][y] = GFX_ELEMENT(element);
14541 player->is_digging = TRUE;
14544 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14546 // use old behaviour for old levels (digging)
14547 if (!level.finish_dig_collect)
14549 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14550 player->index_bit, dig_side);
14552 // if digging triggered player relocation, finish digging tile
14553 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14554 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14557 if (mode == DF_SNAP)
14559 if (level.block_snap_field)
14560 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14562 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14564 // use old behaviour for old levels (snapping)
14565 if (!level.finish_dig_collect)
14566 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14567 player->index_bit, dig_side);
14570 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14574 if (is_player && mode != DF_SNAP)
14576 GfxElement[x][y] = element;
14577 player->is_collecting = TRUE;
14580 if (element == EL_SPEED_PILL)
14582 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14584 else if (element == EL_EXTRA_TIME && level.time > 0)
14586 TimeLeft += level.extra_time;
14588 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14590 DisplayGameControlValues();
14592 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14594 int shield_time = (element == EL_SHIELD_DEADLY ?
14595 level.shield_deadly_time :
14596 level.shield_normal_time);
14598 player->shield_normal_time_left += shield_time;
14599 if (element == EL_SHIELD_DEADLY)
14600 player->shield_deadly_time_left += shield_time;
14602 else if (element == EL_DYNAMITE ||
14603 element == EL_EM_DYNAMITE ||
14604 element == EL_SP_DISK_RED)
14606 if (player->inventory_size < MAX_INVENTORY_SIZE)
14607 player->inventory_element[player->inventory_size++] = element;
14609 DrawGameDoorValues();
14611 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14613 player->dynabomb_count++;
14614 player->dynabombs_left++;
14616 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14618 player->dynabomb_size++;
14620 else if (element == EL_DYNABOMB_INCREASE_POWER)
14622 player->dynabomb_xl = TRUE;
14624 else if (IS_KEY(element))
14626 player->key[KEY_NR(element)] = TRUE;
14628 DrawGameDoorValues();
14630 else if (element == EL_DC_KEY_WHITE)
14632 player->num_white_keys++;
14634 // display white keys?
14635 // DrawGameDoorValues();
14637 else if (IS_ENVELOPE(element))
14639 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14641 if (!wait_for_snapping)
14642 player->show_envelope = element;
14644 else if (element == EL_EMC_LENSES)
14646 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14648 RedrawAllInvisibleElementsForLenses();
14650 else if (element == EL_EMC_MAGNIFIER)
14652 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14654 RedrawAllInvisibleElementsForMagnifier();
14656 else if (IS_DROPPABLE(element) ||
14657 IS_THROWABLE(element)) // can be collected and dropped
14661 if (collect_count == 0)
14662 player->inventory_infinite_element = element;
14664 for (i = 0; i < collect_count; i++)
14665 if (player->inventory_size < MAX_INVENTORY_SIZE)
14666 player->inventory_element[player->inventory_size++] = element;
14668 DrawGameDoorValues();
14670 else if (collect_count > 0)
14672 game.gems_still_needed -= collect_count;
14673 if (game.gems_still_needed < 0)
14674 game.gems_still_needed = 0;
14676 game.snapshot.collected_item = TRUE;
14678 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14680 DisplayGameControlValues();
14683 RaiseScoreElement(element);
14684 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14686 // use old behaviour for old levels (collecting)
14687 if (!level.finish_dig_collect && is_player)
14689 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14690 player->index_bit, dig_side);
14692 // if collecting triggered player relocation, finish collecting tile
14693 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14694 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14697 if (mode == DF_SNAP)
14699 if (level.block_snap_field)
14700 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14702 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14704 // use old behaviour for old levels (snapping)
14705 if (!level.finish_dig_collect)
14706 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14707 player->index_bit, dig_side);
14710 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14712 if (mode == DF_SNAP && element != EL_BD_ROCK)
14713 return MP_NO_ACTION;
14715 if (CAN_FALL(element) && dy)
14716 return MP_NO_ACTION;
14718 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14719 !(element == EL_SPRING && level.use_spring_bug))
14720 return MP_NO_ACTION;
14722 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14723 ((move_direction & MV_VERTICAL &&
14724 ((element_info[element].move_pattern & MV_LEFT &&
14725 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14726 (element_info[element].move_pattern & MV_RIGHT &&
14727 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14728 (move_direction & MV_HORIZONTAL &&
14729 ((element_info[element].move_pattern & MV_UP &&
14730 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14731 (element_info[element].move_pattern & MV_DOWN &&
14732 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14733 return MP_NO_ACTION;
14735 // do not push elements already moving away faster than player
14736 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14737 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14738 return MP_NO_ACTION;
14740 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14742 if (player->push_delay_value == -1 || !player_was_pushing)
14743 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14745 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14747 if (player->push_delay_value == -1)
14748 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14750 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14752 if (!player->is_pushing)
14753 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14756 player->is_pushing = TRUE;
14757 player->is_active = TRUE;
14759 if (!(IN_LEV_FIELD(nextx, nexty) &&
14760 (IS_FREE(nextx, nexty) ||
14761 (IS_SB_ELEMENT(element) &&
14762 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14763 (IS_CUSTOM_ELEMENT(element) &&
14764 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14765 return MP_NO_ACTION;
14767 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14768 return MP_NO_ACTION;
14770 if (player->push_delay == -1) // new pushing; restart delay
14771 player->push_delay = 0;
14773 if (player->push_delay < player->push_delay_value &&
14774 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14775 element != EL_SPRING && element != EL_BALLOON)
14777 // make sure that there is no move delay before next try to push
14778 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14779 player->move_delay = 0;
14781 return MP_NO_ACTION;
14784 if (IS_CUSTOM_ELEMENT(element) &&
14785 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14787 if (!DigFieldByCE(nextx, nexty, element))
14788 return MP_NO_ACTION;
14791 if (IS_SB_ELEMENT(element))
14793 boolean sokoban_task_solved = FALSE;
14795 if (element == EL_SOKOBAN_FIELD_FULL)
14797 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14799 IncrementSokobanFieldsNeeded();
14800 IncrementSokobanObjectsNeeded();
14803 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14805 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14807 DecrementSokobanFieldsNeeded();
14808 DecrementSokobanObjectsNeeded();
14810 // sokoban object was pushed from empty field to sokoban field
14811 if (Back[x][y] == EL_EMPTY)
14812 sokoban_task_solved = TRUE;
14815 Tile[x][y] = EL_SOKOBAN_OBJECT;
14817 if (Back[x][y] == Back[nextx][nexty])
14818 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14819 else if (Back[x][y] != 0)
14820 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14823 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14826 if (sokoban_task_solved &&
14827 game.sokoban_fields_still_needed == 0 &&
14828 game.sokoban_objects_still_needed == 0 &&
14829 level.auto_exit_sokoban)
14831 game.players_still_needed = 0;
14835 PlaySound(SND_GAME_SOKOBAN_SOLVING);
14839 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14841 InitMovingField(x, y, move_direction);
14842 GfxAction[x][y] = ACTION_PUSHING;
14844 if (mode == DF_SNAP)
14845 ContinueMoving(x, y);
14847 MovPos[x][y] = (dx != 0 ? dx : dy);
14849 Pushed[x][y] = TRUE;
14850 Pushed[nextx][nexty] = TRUE;
14852 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14853 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14855 player->push_delay_value = -1; // get new value later
14857 // check for element change _after_ element has been pushed
14858 if (game.use_change_when_pushing_bug)
14860 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14861 player->index_bit, dig_side);
14862 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14863 player->index_bit, dig_side);
14866 else if (IS_SWITCHABLE(element))
14868 if (PLAYER_SWITCHING(player, x, y))
14870 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14871 player->index_bit, dig_side);
14876 player->is_switching = TRUE;
14877 player->switch_x = x;
14878 player->switch_y = y;
14880 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14882 if (element == EL_ROBOT_WHEEL)
14884 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14886 game.robot_wheel_x = x;
14887 game.robot_wheel_y = y;
14888 game.robot_wheel_active = TRUE;
14890 TEST_DrawLevelField(x, y);
14892 else if (element == EL_SP_TERMINAL)
14896 SCAN_PLAYFIELD(xx, yy)
14898 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14902 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14904 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14906 ResetGfxAnimation(xx, yy);
14907 TEST_DrawLevelField(xx, yy);
14911 else if (IS_BELT_SWITCH(element))
14913 ToggleBeltSwitch(x, y);
14915 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14916 element == EL_SWITCHGATE_SWITCH_DOWN ||
14917 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14918 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14920 ToggleSwitchgateSwitch();
14922 else if (element == EL_LIGHT_SWITCH ||
14923 element == EL_LIGHT_SWITCH_ACTIVE)
14925 ToggleLightSwitch(x, y);
14927 else if (element == EL_TIMEGATE_SWITCH ||
14928 element == EL_DC_TIMEGATE_SWITCH)
14930 ActivateTimegateSwitch(x, y);
14932 else if (element == EL_BALLOON_SWITCH_LEFT ||
14933 element == EL_BALLOON_SWITCH_RIGHT ||
14934 element == EL_BALLOON_SWITCH_UP ||
14935 element == EL_BALLOON_SWITCH_DOWN ||
14936 element == EL_BALLOON_SWITCH_NONE ||
14937 element == EL_BALLOON_SWITCH_ANY)
14939 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14940 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14941 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14942 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14943 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14946 else if (element == EL_LAMP)
14948 Tile[x][y] = EL_LAMP_ACTIVE;
14949 game.lights_still_needed--;
14951 ResetGfxAnimation(x, y);
14952 TEST_DrawLevelField(x, y);
14954 else if (element == EL_TIME_ORB_FULL)
14956 Tile[x][y] = EL_TIME_ORB_EMPTY;
14958 if (level.time > 0 || level.use_time_orb_bug)
14960 TimeLeft += level.time_orb_time;
14961 game.no_level_time_limit = FALSE;
14963 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14965 DisplayGameControlValues();
14968 ResetGfxAnimation(x, y);
14969 TEST_DrawLevelField(x, y);
14971 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14972 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14976 game.ball_active = !game.ball_active;
14978 SCAN_PLAYFIELD(xx, yy)
14980 int e = Tile[xx][yy];
14982 if (game.ball_active)
14984 if (e == EL_EMC_MAGIC_BALL)
14985 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14986 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14987 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14991 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14992 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14993 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14994 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14999 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15000 player->index_bit, dig_side);
15002 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15003 player->index_bit, dig_side);
15005 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15006 player->index_bit, dig_side);
15012 if (!PLAYER_SWITCHING(player, x, y))
15014 player->is_switching = TRUE;
15015 player->switch_x = x;
15016 player->switch_y = y;
15018 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
15019 player->index_bit, dig_side);
15020 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15021 player->index_bit, dig_side);
15023 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
15024 player->index_bit, dig_side);
15025 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15026 player->index_bit, dig_side);
15029 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
15030 player->index_bit, dig_side);
15031 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15032 player->index_bit, dig_side);
15034 return MP_NO_ACTION;
15037 player->push_delay = -1;
15039 if (is_player) // function can also be called by EL_PENGUIN
15041 if (Tile[x][y] != element) // really digged/collected something
15043 player->is_collecting = !player->is_digging;
15044 player->is_active = TRUE;
15046 player->last_removed_element = element;
15053 static boolean DigFieldByCE(int x, int y, int digging_element)
15055 int element = Tile[x][y];
15057 if (!IS_FREE(x, y))
15059 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
15060 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
15063 // no element can dig solid indestructible elements
15064 if (IS_INDESTRUCTIBLE(element) &&
15065 !IS_DIGGABLE(element) &&
15066 !IS_COLLECTIBLE(element))
15069 if (AmoebaNr[x][y] &&
15070 (element == EL_AMOEBA_FULL ||
15071 element == EL_BD_AMOEBA ||
15072 element == EL_AMOEBA_GROWING))
15074 AmoebaCnt[AmoebaNr[x][y]]--;
15075 AmoebaCnt2[AmoebaNr[x][y]]--;
15078 if (IS_MOVING(x, y))
15079 RemoveMovingField(x, y);
15083 TEST_DrawLevelField(x, y);
15086 // if digged element was about to explode, prevent the explosion
15087 ExplodeField[x][y] = EX_TYPE_NONE;
15089 PlayLevelSoundAction(x, y, action);
15092 Store[x][y] = EL_EMPTY;
15094 // this makes it possible to leave the removed element again
15095 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15096 Store[x][y] = element;
15101 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15103 int jx = player->jx, jy = player->jy;
15104 int x = jx + dx, y = jy + dy;
15105 int snap_direction = (dx == -1 ? MV_LEFT :
15106 dx == +1 ? MV_RIGHT :
15108 dy == +1 ? MV_DOWN : MV_NONE);
15109 boolean can_continue_snapping = (level.continuous_snapping &&
15110 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15112 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15115 if (!player->active || !IN_LEV_FIELD(x, y))
15123 if (player->MovPos == 0)
15124 player->is_pushing = FALSE;
15126 player->is_snapping = FALSE;
15128 if (player->MovPos == 0)
15130 player->is_moving = FALSE;
15131 player->is_digging = FALSE;
15132 player->is_collecting = FALSE;
15138 // prevent snapping with already pressed snap key when not allowed
15139 if (player->is_snapping && !can_continue_snapping)
15142 player->MovDir = snap_direction;
15144 if (player->MovPos == 0)
15146 player->is_moving = FALSE;
15147 player->is_digging = FALSE;
15148 player->is_collecting = FALSE;
15151 player->is_dropping = FALSE;
15152 player->is_dropping_pressed = FALSE;
15153 player->drop_pressed_delay = 0;
15155 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15158 player->is_snapping = TRUE;
15159 player->is_active = TRUE;
15161 if (player->MovPos == 0)
15163 player->is_moving = FALSE;
15164 player->is_digging = FALSE;
15165 player->is_collecting = FALSE;
15168 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15169 TEST_DrawLevelField(player->last_jx, player->last_jy);
15171 TEST_DrawLevelField(x, y);
15176 static boolean DropElement(struct PlayerInfo *player)
15178 int old_element, new_element;
15179 int dropx = player->jx, dropy = player->jy;
15180 int drop_direction = player->MovDir;
15181 int drop_side = drop_direction;
15182 int drop_element = get_next_dropped_element(player);
15184 /* do not drop an element on top of another element; when holding drop key
15185 pressed without moving, dropped element must move away before the next
15186 element can be dropped (this is especially important if the next element
15187 is dynamite, which can be placed on background for historical reasons) */
15188 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15191 if (IS_THROWABLE(drop_element))
15193 dropx += GET_DX_FROM_DIR(drop_direction);
15194 dropy += GET_DY_FROM_DIR(drop_direction);
15196 if (!IN_LEV_FIELD(dropx, dropy))
15200 old_element = Tile[dropx][dropy]; // old element at dropping position
15201 new_element = drop_element; // default: no change when dropping
15203 // check if player is active, not moving and ready to drop
15204 if (!player->active || player->MovPos || player->drop_delay > 0)
15207 // check if player has anything that can be dropped
15208 if (new_element == EL_UNDEFINED)
15211 // only set if player has anything that can be dropped
15212 player->is_dropping_pressed = TRUE;
15214 // check if drop key was pressed long enough for EM style dynamite
15215 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15218 // check if anything can be dropped at the current position
15219 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15222 // collected custom elements can only be dropped on empty fields
15223 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15226 if (old_element != EL_EMPTY)
15227 Back[dropx][dropy] = old_element; // store old element on this field
15229 ResetGfxAnimation(dropx, dropy);
15230 ResetRandomAnimationValue(dropx, dropy);
15232 if (player->inventory_size > 0 ||
15233 player->inventory_infinite_element != EL_UNDEFINED)
15235 if (player->inventory_size > 0)
15237 player->inventory_size--;
15239 DrawGameDoorValues();
15241 if (new_element == EL_DYNAMITE)
15242 new_element = EL_DYNAMITE_ACTIVE;
15243 else if (new_element == EL_EM_DYNAMITE)
15244 new_element = EL_EM_DYNAMITE_ACTIVE;
15245 else if (new_element == EL_SP_DISK_RED)
15246 new_element = EL_SP_DISK_RED_ACTIVE;
15249 Tile[dropx][dropy] = new_element;
15251 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15252 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15253 el2img(Tile[dropx][dropy]), 0);
15255 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15257 // needed if previous element just changed to "empty" in the last frame
15258 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15260 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15261 player->index_bit, drop_side);
15262 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15264 player->index_bit, drop_side);
15266 TestIfElementTouchesCustomElement(dropx, dropy);
15268 else // player is dropping a dyna bomb
15270 player->dynabombs_left--;
15272 Tile[dropx][dropy] = new_element;
15274 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15275 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15276 el2img(Tile[dropx][dropy]), 0);
15278 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15281 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15282 InitField_WithBug1(dropx, dropy, FALSE);
15284 new_element = Tile[dropx][dropy]; // element might have changed
15286 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15287 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15289 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15290 MovDir[dropx][dropy] = drop_direction;
15292 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15294 // do not cause impact style collision by dropping elements that can fall
15295 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15298 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15299 player->is_dropping = TRUE;
15301 player->drop_pressed_delay = 0;
15302 player->is_dropping_pressed = FALSE;
15304 player->drop_x = dropx;
15305 player->drop_y = dropy;
15310 // ----------------------------------------------------------------------------
15311 // game sound playing functions
15312 // ----------------------------------------------------------------------------
15314 static int *loop_sound_frame = NULL;
15315 static int *loop_sound_volume = NULL;
15317 void InitPlayLevelSound(void)
15319 int num_sounds = getSoundListSize();
15321 checked_free(loop_sound_frame);
15322 checked_free(loop_sound_volume);
15324 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15325 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15328 static void PlayLevelSoundExt(int x, int y, int nr, boolean is_loop_sound)
15330 int sx = SCREENX(x), sy = SCREENY(y);
15331 int volume, stereo_position;
15332 int max_distance = 8;
15333 int type = (is_loop_sound ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15335 if ((!setup.sound_simple && !is_loop_sound) ||
15336 (!setup.sound_loops && is_loop_sound))
15339 if (!IN_LEV_FIELD(x, y) ||
15340 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15341 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15344 volume = SOUND_MAX_VOLUME;
15346 if (!IN_SCR_FIELD(sx, sy))
15348 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15349 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15351 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15354 stereo_position = (SOUND_MAX_LEFT +
15355 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15356 (SCR_FIELDX + 2 * max_distance));
15360 /* This assures that quieter loop sounds do not overwrite louder ones,
15361 while restarting sound volume comparison with each new game frame. */
15363 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15366 loop_sound_volume[nr] = volume;
15367 loop_sound_frame[nr] = FrameCounter;
15370 PlaySoundExt(nr, volume, stereo_position, type);
15373 static void PlayLevelSound(int x, int y, int nr)
15375 PlayLevelSoundExt(x, y, nr, IS_LOOP_SOUND(nr));
15378 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15380 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15381 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15382 y < LEVELY(BY1) ? LEVELY(BY1) :
15383 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15387 static void PlayLevelSoundAction(int x, int y, int action)
15389 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15392 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15394 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15396 if (sound_effect != SND_UNDEFINED)
15397 PlayLevelSound(x, y, sound_effect);
15400 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15403 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15405 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15406 PlayLevelSound(x, y, sound_effect);
15409 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15411 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15413 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15414 PlayLevelSound(x, y, sound_effect);
15417 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15419 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15421 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15422 StopSound(sound_effect);
15425 static int getLevelMusicNr(void)
15427 int level_pos = level_nr - leveldir_current->first_level;
15429 if (levelset.music[level_nr] != MUS_UNDEFINED)
15430 return levelset.music[level_nr]; // from config file
15432 return MAP_NOCONF_MUSIC(level_pos); // from music dir
15435 static void FadeLevelSounds(void)
15440 static void FadeLevelMusic(void)
15442 int music_nr = getLevelMusicNr();
15443 char *curr_music = getCurrentlyPlayingMusicFilename();
15444 char *next_music = getMusicInfoEntryFilename(music_nr);
15446 if (!strEqual(curr_music, next_music))
15450 void FadeLevelSoundsAndMusic(void)
15456 static void PlayLevelMusic(void)
15458 int music_nr = getLevelMusicNr();
15459 char *curr_music = getCurrentlyPlayingMusicFilename();
15460 char *next_music = getMusicInfoEntryFilename(music_nr);
15462 if (!strEqual(curr_music, next_music))
15463 PlayMusicLoop(music_nr);
15466 static int getSoundAction_BD(int sample)
15472 case GD_S_DIRT_BALL:
15474 case GD_S_FALLING_WALL:
15475 return ACTION_IMPACT;
15477 case GD_S_NUT_CRACK:
15478 return ACTION_BREAKING;
15480 case GD_S_EXPANDING_WALL:
15481 case GD_S_WALL_REAPPEAR:
15484 case GD_S_ACID_SPREAD:
15485 return ACTION_GROWING;
15487 case GD_S_DIAMOND_COLLECT:
15488 case GD_S_SKELETON_COLLECT:
15489 case GD_S_PNEUMATIC_COLLECT:
15490 case GD_S_BOMB_COLLECT:
15491 case GD_S_CLOCK_COLLECT:
15492 case GD_S_SWEET_COLLECT:
15493 case GD_S_KEY_COLLECT:
15494 case GD_S_DIAMOND_KEY_COLLECT:
15495 return ACTION_COLLECTING;
15497 case GD_S_BOMB_PLACE:
15498 case GD_S_REPLICATOR:
15499 return ACTION_DROPPING;
15501 case GD_S_BLADDER_MOVE:
15502 return ACTION_MOVING;
15504 case GD_S_BLADDER_SPENDER:
15505 case GD_S_BLADDER_CONVERT:
15506 case GD_S_GRAVITY_CHANGE:
15507 return ACTION_CHANGING;
15509 case GD_S_BITER_EAT:
15510 return ACTION_EATING;
15512 case GD_S_DOOR_OPEN:
15514 return ACTION_OPENING;
15516 case GD_S_WALK_EARTH:
15517 return ACTION_DIGGING;
15519 case GD_S_WALK_EMPTY:
15520 return ACTION_WALKING;
15522 case GD_S_SWITCH_BITER:
15523 case GD_S_SWITCH_CREATURES:
15524 case GD_S_SWITCH_GRAVITY:
15525 case GD_S_SWITCH_EXPANDING:
15526 case GD_S_SWITCH_CONVEYOR:
15527 case GD_S_SWITCH_REPLICATOR:
15528 case GD_S_STIRRING:
15529 return ACTION_ACTIVATING;
15531 case GD_S_BOX_PUSH:
15532 return ACTION_PUSHING;
15534 case GD_S_TELEPORTER:
15535 return ACTION_PASSING;
15537 case GD_S_EXPLOSION:
15538 case GD_S_BOMB_EXPLOSION:
15539 case GD_S_GHOST_EXPLOSION:
15540 case GD_S_VOODOO_EXPLOSION:
15541 case GD_S_NITRO_EXPLOSION:
15542 return ACTION_EXPLODING;
15546 case GD_S_AMOEBA_MAGIC:
15547 case GD_S_MAGIC_WALL:
15548 case GD_S_PNEUMATIC_HAMMER:
15550 return ACTION_ACTIVE;
15552 case GD_S_DIAMOND_RANDOM:
15553 case GD_S_DIAMOND_1:
15554 case GD_S_DIAMOND_2:
15555 case GD_S_DIAMOND_3:
15556 case GD_S_DIAMOND_4:
15557 case GD_S_DIAMOND_5:
15558 case GD_S_DIAMOND_6:
15559 case GD_S_DIAMOND_7:
15560 case GD_S_DIAMOND_8:
15561 case GD_S_TIMEOUT_0:
15562 case GD_S_TIMEOUT_1:
15563 case GD_S_TIMEOUT_2:
15564 case GD_S_TIMEOUT_3:
15565 case GD_S_TIMEOUT_4:
15566 case GD_S_TIMEOUT_5:
15567 case GD_S_TIMEOUT_6:
15568 case GD_S_TIMEOUT_7:
15569 case GD_S_TIMEOUT_8:
15570 case GD_S_TIMEOUT_9:
15571 case GD_S_TIMEOUT_10:
15572 case GD_S_BONUS_LIFE:
15573 // kludge to prevent playing as loop sound
15574 return ACTION_OTHER;
15576 case GD_S_FINISHED:
15577 return ACTION_DEFAULT;
15580 return ACTION_DEFAULT;
15584 static int getSoundEffect_BD(int element_bd, int sample)
15586 int sound_action = getSoundAction_BD(sample);
15587 int sound_effect = element_info[SND_ELEMENT(element_bd)].sound[sound_action];
15591 if (sound_action != ACTION_OTHER &&
15592 sound_action != ACTION_DEFAULT)
15593 return sound_effect;
15598 case GD_S_DIAMOND_RANDOM:
15599 nr = GetSimpleRandom(8);
15600 sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr;
15603 case GD_S_DIAMOND_1:
15604 case GD_S_DIAMOND_2:
15605 case GD_S_DIAMOND_3:
15606 case GD_S_DIAMOND_4:
15607 case GD_S_DIAMOND_5:
15608 case GD_S_DIAMOND_6:
15609 case GD_S_DIAMOND_7:
15610 case GD_S_DIAMOND_8:
15611 nr = sample - GD_S_DIAMOND_1;
15612 sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr;
15615 case GD_S_TIMEOUT_0:
15616 case GD_S_TIMEOUT_1:
15617 case GD_S_TIMEOUT_2:
15618 case GD_S_TIMEOUT_3:
15619 case GD_S_TIMEOUT_4:
15620 case GD_S_TIMEOUT_5:
15621 case GD_S_TIMEOUT_6:
15622 case GD_S_TIMEOUT_7:
15623 case GD_S_TIMEOUT_8:
15624 case GD_S_TIMEOUT_9:
15625 case GD_S_TIMEOUT_10:
15626 nr = sample - GD_S_TIMEOUT_0;
15627 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME_0 + nr;
15629 if (getSoundInfoEntryFilename(sound_effect) == NULL && sample != GD_S_TIMEOUT_0)
15630 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME;
15633 case GD_S_FINISHED:
15634 sound_effect = SND_GAME_LEVELTIME_BONUS;
15637 case GD_S_BONUS_LIFE:
15638 sound_effect = SND_GAME_HEALTH_BONUS;
15642 sound_effect = SND_UNDEFINED;
15646 return sound_effect;
15649 void PlayLevelSound_BD(int xx, int yy, int element_bd, int sample)
15651 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15652 int sound_effect = getSoundEffect_BD(element, sample);
15653 int sound_action = getSoundAction_BD(sample);
15654 boolean is_loop_sound = IS_LOOP_SOUND(sound_effect);
15656 int x = xx - offset;
15657 int y = yy - offset;
15659 if (sound_action == ACTION_OTHER)
15660 is_loop_sound = FALSE;
15662 if (sound_effect != SND_UNDEFINED)
15663 PlayLevelSoundExt(x, y, sound_effect, is_loop_sound);
15666 void StopSound_BD(int element_bd, int sample)
15668 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15669 int sound_effect = getSoundEffect_BD(element, sample);
15671 if (sound_effect != SND_UNDEFINED)
15672 StopSound(sound_effect);
15675 boolean isSoundPlaying_BD(int element_bd, int sample)
15677 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15678 int sound_effect = getSoundEffect_BD(element, sample);
15680 if (sound_effect != SND_UNDEFINED)
15681 return isSoundPlaying(sound_effect);
15686 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15688 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15690 int x = xx - offset;
15691 int y = yy - offset;
15696 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15700 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15704 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15708 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15712 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15716 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15720 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15723 case SOUND_android_clone:
15724 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15727 case SOUND_android_move:
15728 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15732 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15736 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15740 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15743 case SOUND_eater_eat:
15744 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15748 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15751 case SOUND_collect:
15752 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15755 case SOUND_diamond:
15756 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15760 // !!! CHECK THIS !!!
15762 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15764 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15768 case SOUND_wonderfall:
15769 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15773 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15777 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15781 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15785 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15789 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15793 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15797 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15801 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15804 case SOUND_exit_open:
15805 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15808 case SOUND_exit_leave:
15809 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15812 case SOUND_dynamite:
15813 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15817 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15821 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15825 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15829 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15833 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15837 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15841 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15846 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15848 int element = map_element_SP_to_RND(element_sp);
15849 int action = map_action_SP_to_RND(action_sp);
15850 int offset = (setup.sp_show_border_elements ? 0 : 1);
15851 int x = xx - offset;
15852 int y = yy - offset;
15854 PlayLevelSoundElementAction(x, y, element, action);
15857 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15859 int element = map_element_MM_to_RND(element_mm);
15860 int action = map_action_MM_to_RND(action_mm);
15862 int x = xx - offset;
15863 int y = yy - offset;
15865 if (!IS_MM_ELEMENT(element))
15866 element = EL_MM_DEFAULT;
15868 PlayLevelSoundElementAction(x, y, element, action);
15871 void PlaySound_MM(int sound_mm)
15873 int sound = map_sound_MM_to_RND(sound_mm);
15875 if (sound == SND_UNDEFINED)
15881 void PlaySoundLoop_MM(int sound_mm)
15883 int sound = map_sound_MM_to_RND(sound_mm);
15885 if (sound == SND_UNDEFINED)
15888 PlaySoundLoop(sound);
15891 void StopSound_MM(int sound_mm)
15893 int sound = map_sound_MM_to_RND(sound_mm);
15895 if (sound == SND_UNDEFINED)
15901 void RaiseScore(int value)
15903 game.score += value;
15905 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15907 DisplayGameControlValues();
15910 void RaiseScoreElement(int element)
15915 case EL_BD_DIAMOND:
15916 case EL_EMERALD_YELLOW:
15917 case EL_EMERALD_RED:
15918 case EL_EMERALD_PURPLE:
15919 case EL_SP_INFOTRON:
15920 RaiseScore(level.score[SC_EMERALD]);
15923 RaiseScore(level.score[SC_DIAMOND]);
15926 RaiseScore(level.score[SC_CRYSTAL]);
15929 RaiseScore(level.score[SC_PEARL]);
15932 case EL_BD_BUTTERFLY:
15933 case EL_SP_ELECTRON:
15934 RaiseScore(level.score[SC_BUG]);
15937 case EL_BD_FIREFLY:
15938 case EL_SP_SNIKSNAK:
15939 RaiseScore(level.score[SC_SPACESHIP]);
15942 case EL_DARK_YAMYAM:
15943 RaiseScore(level.score[SC_YAMYAM]);
15946 RaiseScore(level.score[SC_ROBOT]);
15949 RaiseScore(level.score[SC_PACMAN]);
15952 RaiseScore(level.score[SC_NUT]);
15955 case EL_EM_DYNAMITE:
15956 case EL_SP_DISK_RED:
15957 case EL_DYNABOMB_INCREASE_NUMBER:
15958 case EL_DYNABOMB_INCREASE_SIZE:
15959 case EL_DYNABOMB_INCREASE_POWER:
15960 RaiseScore(level.score[SC_DYNAMITE]);
15962 case EL_SHIELD_NORMAL:
15963 case EL_SHIELD_DEADLY:
15964 RaiseScore(level.score[SC_SHIELD]);
15966 case EL_EXTRA_TIME:
15967 RaiseScore(level.extra_time_score);
15981 case EL_DC_KEY_WHITE:
15982 RaiseScore(level.score[SC_KEY]);
15985 RaiseScore(element_info[element].collect_score);
15990 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15992 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15996 // prevent short reactivation of overlay buttons while closing door
15997 SetOverlayActive(FALSE);
15998 UnmapGameButtons();
16000 // door may still be open due to skipped or envelope style request
16001 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
16004 if (network.enabled)
16006 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
16011 FadeSkipNextFadeIn();
16013 SetGameStatus(GAME_MODE_MAIN);
16018 else // continue playing the game
16020 if (tape.playing && tape.deactivate_display)
16021 TapeDeactivateDisplayOff(TRUE);
16023 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
16025 if (tape.playing && tape.deactivate_display)
16026 TapeDeactivateDisplayOn();
16030 void RequestQuitGame(boolean escape_key_pressed)
16032 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
16033 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
16034 level_editor_test_game);
16035 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
16036 quick_quit || score_info_tape_play);
16038 RequestQuitGameExt(skip_request, quick_quit,
16039 "Do you really want to quit the game?");
16042 static char *getRestartGameMessage(void)
16044 boolean play_again = hasStartedNetworkGame();
16045 static char message[MAX_OUTPUT_LINESIZE];
16046 char *game_over_text = "Game over!";
16047 char *play_again_text = " Play it again?";
16049 if (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
16050 game_mm.game_over_message != NULL)
16051 game_over_text = game_mm.game_over_message;
16053 snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text,
16054 (play_again ? play_again_text : ""));
16059 static void RequestRestartGame(void)
16061 char *message = getRestartGameMessage();
16062 boolean has_started_game = hasStartedNetworkGame();
16063 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
16064 int door_state = DOOR_CLOSE_1;
16066 if (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game)
16068 CloseDoor(door_state);
16070 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16074 // if game was invoked from level editor, also close tape recorder door
16075 if (level_editor_test_game)
16076 door_state = DOOR_CLOSE_ALL;
16078 CloseDoor(door_state);
16080 SetGameStatus(GAME_MODE_MAIN);
16086 boolean CheckRestartGame(void)
16088 static int game_over_delay = 0;
16089 int game_over_delay_value = 50;
16090 boolean game_over = checkGameFailed();
16094 game_over_delay = game_over_delay_value;
16099 if (game_over_delay > 0)
16101 if (game_over_delay == game_over_delay_value / 2)
16102 PlaySound(SND_GAME_LOSING);
16109 // do not ask to play again if request dialog is already active
16110 if (game.request_active)
16113 // do not ask to play again if request dialog already handled
16114 if (game.RestartGameRequested)
16117 // do not ask to play again if game was never actually played
16118 if (!game.GamePlayed)
16121 // do not ask to play again if this was disabled in setup menu
16122 if (!setup.ask_on_game_over)
16125 game.RestartGameRequested = TRUE;
16127 RequestRestartGame();
16132 boolean checkGameSolved(void)
16134 // set for all game engines if level was solved
16135 return game.LevelSolved_GameEnd;
16138 boolean checkGameFailed(void)
16140 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
16141 return (game_bd.game_over && !game_bd.level_solved);
16142 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16143 return (game_em.game_over && !game_em.level_solved);
16144 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16145 return (game_sp.game_over && !game_sp.level_solved);
16146 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16147 return (game_mm.game_over && !game_mm.level_solved);
16148 else // GAME_ENGINE_TYPE_RND
16149 return (game.GameOver && !game.LevelSolved);
16152 boolean checkGameEnded(void)
16154 return (checkGameSolved() || checkGameFailed());
16158 // ----------------------------------------------------------------------------
16159 // random generator functions
16160 // ----------------------------------------------------------------------------
16162 unsigned int InitEngineRandom_RND(int seed)
16164 game.num_random_calls = 0;
16166 return InitEngineRandom(seed);
16169 unsigned int RND(int max)
16173 game.num_random_calls++;
16175 return GetEngineRandom(max);
16182 // ----------------------------------------------------------------------------
16183 // game engine snapshot handling functions
16184 // ----------------------------------------------------------------------------
16186 struct EngineSnapshotInfo
16188 // runtime values for custom element collect score
16189 int collect_score[NUM_CUSTOM_ELEMENTS];
16191 // runtime values for group element choice position
16192 int choice_pos[NUM_GROUP_ELEMENTS];
16194 // runtime values for belt position animations
16195 int belt_graphic[4][NUM_BELT_PARTS];
16196 int belt_anim_mode[4][NUM_BELT_PARTS];
16199 static struct EngineSnapshotInfo engine_snapshot_rnd;
16200 static char *snapshot_level_identifier = NULL;
16201 static int snapshot_level_nr = -1;
16203 static void SaveEngineSnapshotValues_RND(void)
16205 static int belt_base_active_element[4] =
16207 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
16208 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
16209 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
16210 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
16214 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16216 int element = EL_CUSTOM_START + i;
16218 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
16221 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16223 int element = EL_GROUP_START + i;
16225 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
16228 for (i = 0; i < 4; i++)
16230 for (j = 0; j < NUM_BELT_PARTS; j++)
16232 int element = belt_base_active_element[i] + j;
16233 int graphic = el2img(element);
16234 int anim_mode = graphic_info[graphic].anim_mode;
16236 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
16237 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
16242 static void LoadEngineSnapshotValues_RND(void)
16244 unsigned int num_random_calls = game.num_random_calls;
16247 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16249 int element = EL_CUSTOM_START + i;
16251 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
16254 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16256 int element = EL_GROUP_START + i;
16258 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
16261 for (i = 0; i < 4; i++)
16263 for (j = 0; j < NUM_BELT_PARTS; j++)
16265 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
16266 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
16268 graphic_info[graphic].anim_mode = anim_mode;
16272 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16274 InitRND(tape.random_seed);
16275 for (i = 0; i < num_random_calls; i++)
16279 if (game.num_random_calls != num_random_calls)
16281 Error("number of random calls out of sync");
16282 Error("number of random calls should be %d", num_random_calls);
16283 Error("number of random calls is %d", game.num_random_calls);
16285 Fail("this should not happen -- please debug");
16289 void FreeEngineSnapshotSingle(void)
16291 FreeSnapshotSingle();
16293 setString(&snapshot_level_identifier, NULL);
16294 snapshot_level_nr = -1;
16297 void FreeEngineSnapshotList(void)
16299 FreeSnapshotList();
16302 static ListNode *SaveEngineSnapshotBuffers(void)
16304 ListNode *buffers = NULL;
16306 // copy some special values to a structure better suited for the snapshot
16308 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16309 SaveEngineSnapshotValues_RND();
16310 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16311 SaveEngineSnapshotValues_EM();
16312 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16313 SaveEngineSnapshotValues_SP(&buffers);
16314 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16315 SaveEngineSnapshotValues_MM();
16317 // save values stored in special snapshot structure
16319 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16320 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
16321 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16322 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
16323 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16324 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
16325 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16326 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
16328 // save further RND engine values
16330 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
16331 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
16332 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
16334 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
16335 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
16336 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
16337 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16338 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTimeFrames));
16339 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16341 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16342 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16343 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16345 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16347 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16348 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16350 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16351 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16352 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16353 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16354 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16355 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16356 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16357 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16358 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16359 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16360 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16361 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16362 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16363 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16364 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16365 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16366 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16367 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16369 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16370 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16372 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16373 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16374 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16376 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16377 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16379 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16380 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16381 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16382 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16383 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16384 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16386 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16387 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16390 ListNode *node = engine_snapshot_list_rnd;
16393 while (node != NULL)
16395 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16400 Debug("game:playing:SaveEngineSnapshotBuffers",
16401 "size of engine snapshot: %d bytes", num_bytes);
16407 void SaveEngineSnapshotSingle(void)
16409 ListNode *buffers = SaveEngineSnapshotBuffers();
16411 // finally save all snapshot buffers to single snapshot
16412 SaveSnapshotSingle(buffers);
16414 // save level identification information
16415 setString(&snapshot_level_identifier, leveldir_current->identifier);
16416 snapshot_level_nr = level_nr;
16419 boolean CheckSaveEngineSnapshotToList(void)
16421 boolean save_snapshot =
16422 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16423 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16424 game.snapshot.changed_action) ||
16425 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16426 game.snapshot.collected_item));
16428 game.snapshot.changed_action = FALSE;
16429 game.snapshot.collected_item = FALSE;
16430 game.snapshot.save_snapshot = save_snapshot;
16432 return save_snapshot;
16435 void SaveEngineSnapshotToList(void)
16437 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16441 ListNode *buffers = SaveEngineSnapshotBuffers();
16443 // finally save all snapshot buffers to snapshot list
16444 SaveSnapshotToList(buffers);
16447 void SaveEngineSnapshotToListInitial(void)
16449 FreeEngineSnapshotList();
16451 SaveEngineSnapshotToList();
16454 static void LoadEngineSnapshotValues(void)
16456 // restore special values from snapshot structure
16458 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16459 LoadEngineSnapshotValues_RND();
16460 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16461 LoadEngineSnapshotValues_EM();
16462 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16463 LoadEngineSnapshotValues_SP();
16464 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16465 LoadEngineSnapshotValues_MM();
16468 void LoadEngineSnapshotSingle(void)
16470 LoadSnapshotSingle();
16472 LoadEngineSnapshotValues();
16475 static void LoadEngineSnapshot_Undo(int steps)
16477 LoadSnapshotFromList_Older(steps);
16479 LoadEngineSnapshotValues();
16482 static void LoadEngineSnapshot_Redo(int steps)
16484 LoadSnapshotFromList_Newer(steps);
16486 LoadEngineSnapshotValues();
16489 boolean CheckEngineSnapshotSingle(void)
16491 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16492 snapshot_level_nr == level_nr);
16495 boolean CheckEngineSnapshotList(void)
16497 return CheckSnapshotList();
16501 // ---------- new game button stuff -------------------------------------------
16508 boolean *setup_value;
16509 boolean allowed_on_tape;
16510 boolean is_touch_button;
16512 } gamebutton_info[NUM_GAME_BUTTONS] =
16515 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16516 GAME_CTRL_ID_STOP, NULL,
16517 TRUE, FALSE, "stop game"
16520 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16521 GAME_CTRL_ID_PAUSE, NULL,
16522 TRUE, FALSE, "pause game"
16525 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16526 GAME_CTRL_ID_PLAY, NULL,
16527 TRUE, FALSE, "play game"
16530 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16531 GAME_CTRL_ID_UNDO, NULL,
16532 TRUE, FALSE, "undo step"
16535 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16536 GAME_CTRL_ID_REDO, NULL,
16537 TRUE, FALSE, "redo step"
16540 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16541 GAME_CTRL_ID_SAVE, NULL,
16542 TRUE, FALSE, "save game"
16545 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16546 GAME_CTRL_ID_PAUSE2, NULL,
16547 TRUE, FALSE, "pause game"
16550 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16551 GAME_CTRL_ID_LOAD, NULL,
16552 TRUE, FALSE, "load game"
16555 IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart,
16556 GAME_CTRL_ID_RESTART, NULL,
16557 TRUE, FALSE, "restart game"
16560 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16561 GAME_CTRL_ID_PANEL_STOP, NULL,
16562 FALSE, FALSE, "stop game"
16565 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16566 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16567 FALSE, FALSE, "pause game"
16570 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16571 GAME_CTRL_ID_PANEL_PLAY, NULL,
16572 FALSE, FALSE, "play game"
16575 IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart,
16576 GAME_CTRL_ID_PANEL_RESTART, NULL,
16577 FALSE, FALSE, "restart game"
16580 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16581 GAME_CTRL_ID_TOUCH_STOP, NULL,
16582 FALSE, TRUE, "stop game"
16585 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16586 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16587 FALSE, TRUE, "pause game"
16590 IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart,
16591 GAME_CTRL_ID_TOUCH_RESTART, NULL,
16592 FALSE, TRUE, "restart game"
16595 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16596 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16597 TRUE, FALSE, "background music on/off"
16600 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16601 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16602 TRUE, FALSE, "sound loops on/off"
16605 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16606 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16607 TRUE, FALSE, "normal sounds on/off"
16610 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16611 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16612 FALSE, FALSE, "background music on/off"
16615 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16616 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16617 FALSE, FALSE, "sound loops on/off"
16620 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16621 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16622 FALSE, FALSE, "normal sounds on/off"
16626 void CreateGameButtons(void)
16630 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16632 int graphic = gamebutton_info[i].graphic;
16633 struct GraphicInfo *gfx = &graphic_info[graphic];
16634 struct XY *pos = gamebutton_info[i].pos;
16635 struct GadgetInfo *gi;
16638 unsigned int event_mask;
16639 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16640 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16641 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16642 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16643 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16644 int gd_x = gfx->src_x;
16645 int gd_y = gfx->src_y;
16646 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16647 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16648 int gd_xa = gfx->src_x + gfx->active_xoffset;
16649 int gd_ya = gfx->src_y + gfx->active_yoffset;
16650 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16651 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16652 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16653 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16656 // do not use touch buttons if overlay touch buttons are disabled
16657 if (is_touch_button && !setup.touch.overlay_buttons)
16660 if (gfx->bitmap == NULL)
16662 game_gadget[id] = NULL;
16667 if (id == GAME_CTRL_ID_STOP ||
16668 id == GAME_CTRL_ID_PANEL_STOP ||
16669 id == GAME_CTRL_ID_TOUCH_STOP ||
16670 id == GAME_CTRL_ID_PLAY ||
16671 id == GAME_CTRL_ID_PANEL_PLAY ||
16672 id == GAME_CTRL_ID_SAVE ||
16673 id == GAME_CTRL_ID_LOAD ||
16674 id == GAME_CTRL_ID_RESTART ||
16675 id == GAME_CTRL_ID_PANEL_RESTART ||
16676 id == GAME_CTRL_ID_TOUCH_RESTART)
16678 button_type = GD_TYPE_NORMAL_BUTTON;
16680 event_mask = GD_EVENT_RELEASED;
16682 else if (id == GAME_CTRL_ID_UNDO ||
16683 id == GAME_CTRL_ID_REDO)
16685 button_type = GD_TYPE_NORMAL_BUTTON;
16687 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16691 button_type = GD_TYPE_CHECK_BUTTON;
16692 checked = (gamebutton_info[i].setup_value != NULL ?
16693 *gamebutton_info[i].setup_value : FALSE);
16694 event_mask = GD_EVENT_PRESSED;
16697 gi = CreateGadget(GDI_CUSTOM_ID, id,
16698 GDI_IMAGE_ID, graphic,
16699 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16702 GDI_WIDTH, gfx->width,
16703 GDI_HEIGHT, gfx->height,
16704 GDI_TYPE, button_type,
16705 GDI_STATE, GD_BUTTON_UNPRESSED,
16706 GDI_CHECKED, checked,
16707 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16708 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16709 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16710 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16711 GDI_DIRECT_DRAW, FALSE,
16712 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16713 GDI_EVENT_MASK, event_mask,
16714 GDI_CALLBACK_ACTION, HandleGameButtons,
16718 Fail("cannot create gadget");
16720 game_gadget[id] = gi;
16724 void FreeGameButtons(void)
16728 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16729 FreeGadget(game_gadget[i]);
16732 static void UnmapGameButtonsAtSamePosition(int id)
16736 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16738 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16739 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16740 UnmapGadget(game_gadget[i]);
16743 static void UnmapGameButtonsAtSamePosition_All(void)
16745 if (setup.show_load_save_buttons)
16747 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16748 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16749 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16751 else if (setup.show_undo_redo_buttons)
16753 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16754 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16755 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16759 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16760 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16761 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16763 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16764 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16765 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16769 void MapLoadSaveButtons(void)
16771 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16772 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16774 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16775 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16778 void MapUndoRedoButtons(void)
16780 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16781 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16783 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16784 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16787 void ModifyPauseButtons(void)
16791 GAME_CTRL_ID_PAUSE,
16792 GAME_CTRL_ID_PAUSE2,
16793 GAME_CTRL_ID_PANEL_PAUSE,
16794 GAME_CTRL_ID_TOUCH_PAUSE,
16799 // do not redraw pause button on closed door (may happen when restarting game)
16800 if (!(GetDoorState() & DOOR_OPEN_1))
16803 for (i = 0; ids[i] > -1; i++)
16804 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16807 static void MapGameButtonsExt(boolean on_tape)
16811 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16813 if ((i == GAME_CTRL_ID_UNDO ||
16814 i == GAME_CTRL_ID_REDO) &&
16815 game_status != GAME_MODE_PLAYING)
16818 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16819 MapGadget(game_gadget[i]);
16822 UnmapGameButtonsAtSamePosition_All();
16824 RedrawGameButtons();
16827 static void UnmapGameButtonsExt(boolean on_tape)
16831 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16832 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16833 UnmapGadget(game_gadget[i]);
16836 static void RedrawGameButtonsExt(boolean on_tape)
16840 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16841 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16842 RedrawGadget(game_gadget[i]);
16845 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16850 gi->checked = state;
16853 static void RedrawSoundButtonGadget(int id)
16855 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16856 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16857 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16858 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16859 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16860 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16863 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16864 RedrawGadget(game_gadget[id2]);
16867 void MapGameButtons(void)
16869 MapGameButtonsExt(FALSE);
16872 void UnmapGameButtons(void)
16874 UnmapGameButtonsExt(FALSE);
16877 void RedrawGameButtons(void)
16879 RedrawGameButtonsExt(FALSE);
16882 void MapGameButtonsOnTape(void)
16884 MapGameButtonsExt(TRUE);
16887 void UnmapGameButtonsOnTape(void)
16889 UnmapGameButtonsExt(TRUE);
16892 void RedrawGameButtonsOnTape(void)
16894 RedrawGameButtonsExt(TRUE);
16897 static void GameUndoRedoExt(void)
16899 ClearPlayerAction();
16901 tape.pausing = TRUE;
16904 UpdateAndDisplayGameControlValues();
16906 DrawCompleteVideoDisplay();
16907 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16908 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16909 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16911 ModifyPauseButtons();
16916 static void GameUndo(int steps)
16918 if (!CheckEngineSnapshotList())
16921 int tape_property_bits = tape.property_bits;
16923 LoadEngineSnapshot_Undo(steps);
16925 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16930 static void GameRedo(int steps)
16932 if (!CheckEngineSnapshotList())
16935 int tape_property_bits = tape.property_bits;
16937 LoadEngineSnapshot_Redo(steps);
16939 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16944 static void HandleGameButtonsExt(int id, int button)
16946 static boolean game_undo_executed = FALSE;
16947 int steps = BUTTON_STEPSIZE(button);
16948 boolean handle_game_buttons =
16949 (game_status == GAME_MODE_PLAYING ||
16950 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16952 if (!handle_game_buttons)
16957 case GAME_CTRL_ID_STOP:
16958 case GAME_CTRL_ID_PANEL_STOP:
16959 case GAME_CTRL_ID_TOUCH_STOP:
16964 case GAME_CTRL_ID_PAUSE:
16965 case GAME_CTRL_ID_PAUSE2:
16966 case GAME_CTRL_ID_PANEL_PAUSE:
16967 case GAME_CTRL_ID_TOUCH_PAUSE:
16968 if (network.enabled && game_status == GAME_MODE_PLAYING)
16971 SendToServer_ContinuePlaying();
16973 SendToServer_PausePlaying();
16976 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16978 game_undo_executed = FALSE;
16982 case GAME_CTRL_ID_PLAY:
16983 case GAME_CTRL_ID_PANEL_PLAY:
16984 if (game_status == GAME_MODE_MAIN)
16986 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16988 else if (tape.pausing)
16990 if (network.enabled)
16991 SendToServer_ContinuePlaying();
16993 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16997 case GAME_CTRL_ID_UNDO:
16998 // Important: When using "save snapshot when collecting an item" mode,
16999 // load last (current) snapshot for first "undo" after pressing "pause"
17000 // (else the last-but-one snapshot would be loaded, because the snapshot
17001 // pointer already points to the last snapshot when pressing "pause",
17002 // which is fine for "every step/move" mode, but not for "every collect")
17003 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
17004 !game_undo_executed)
17007 game_undo_executed = TRUE;
17012 case GAME_CTRL_ID_REDO:
17016 case GAME_CTRL_ID_SAVE:
17020 case GAME_CTRL_ID_LOAD:
17024 case GAME_CTRL_ID_RESTART:
17025 case GAME_CTRL_ID_PANEL_RESTART:
17026 case GAME_CTRL_ID_TOUCH_RESTART:
17031 case SOUND_CTRL_ID_MUSIC:
17032 case SOUND_CTRL_ID_PANEL_MUSIC:
17033 if (setup.sound_music)
17035 setup.sound_music = FALSE;
17039 else if (audio.music_available)
17041 setup.sound = setup.sound_music = TRUE;
17043 SetAudioMode(setup.sound);
17045 if (game_status == GAME_MODE_PLAYING)
17049 RedrawSoundButtonGadget(id);
17053 case SOUND_CTRL_ID_LOOPS:
17054 case SOUND_CTRL_ID_PANEL_LOOPS:
17055 if (setup.sound_loops)
17056 setup.sound_loops = FALSE;
17057 else if (audio.loops_available)
17059 setup.sound = setup.sound_loops = TRUE;
17061 SetAudioMode(setup.sound);
17064 RedrawSoundButtonGadget(id);
17068 case SOUND_CTRL_ID_SIMPLE:
17069 case SOUND_CTRL_ID_PANEL_SIMPLE:
17070 if (setup.sound_simple)
17071 setup.sound_simple = FALSE;
17072 else if (audio.sound_available)
17074 setup.sound = setup.sound_simple = TRUE;
17076 SetAudioMode(setup.sound);
17079 RedrawSoundButtonGadget(id);
17088 static void HandleGameButtons(struct GadgetInfo *gi)
17090 HandleGameButtonsExt(gi->custom_id, gi->event.button);
17093 void HandleSoundButtonKeys(Key key)
17095 if (key == setup.shortcut.sound_simple)
17096 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
17097 else if (key == setup.shortcut.sound_loops)
17098 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
17099 else if (key == setup.shortcut.sound_music)
17100 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);