1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_INIT_PLAYER 1
27 #define DEBUG_PLAYER_ACTIONS 0
30 #define USE_NEW_AMOEBA_CODE FALSE
33 #define USE_QUICKSAND_BD_ROCK_BUGFIX 0
34 #define USE_QUICKSAND_IMPACT_BUGFIX 0
35 #define USE_DELAYED_GFX_REDRAW 0
36 #define USE_NEW_PLAYER_ASSIGNMENTS 1
38 #if USE_DELAYED_GFX_REDRAW
39 #define TEST_DrawLevelField(x, y) \
40 GfxRedraw[x][y] |= GFX_REDRAW_TILE
41 #define TEST_DrawLevelFieldCrumbled(x, y) \
42 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED
43 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
44 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS
45 #define TEST_DrawTwinkleOnField(x, y) \
46 GfxRedraw[x][y] |= GFX_REDRAW_TILE_TWINKLED
48 #define TEST_DrawLevelField(x, y) \
50 #define TEST_DrawLevelFieldCrumbled(x, y) \
51 DrawLevelFieldCrumbled(x, y)
52 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
53 DrawLevelFieldCrumbledNeighbours(x, y)
54 #define TEST_DrawTwinkleOnField(x, y) \
55 DrawTwinkleOnField(x, y)
65 #define MP_NO_ACTION 0
68 #define MP_DONT_RUN_INTO (MP_MOVING | MP_ACTION)
72 #define SCROLL_GO_ON 1
74 // for Bang()/Explode()
75 #define EX_PHASE_START 0
76 #define EX_TYPE_NONE 0
77 #define EX_TYPE_NORMAL (1 << 0)
78 #define EX_TYPE_CENTER (1 << 1)
79 #define EX_TYPE_BORDER (1 << 2)
80 #define EX_TYPE_CROSS (1 << 3)
81 #define EX_TYPE_DYNA (1 << 4)
82 #define EX_TYPE_SINGLE_TILE (EX_TYPE_CENTER | EX_TYPE_BORDER)
84 #define PANEL_OFF() (game.panel.active == FALSE)
85 #define PANEL_DEACTIVATED(p) ((p)->x < 0 || (p)->y < 0 || PANEL_OFF())
86 #define PANEL_XPOS(p) (DX + ALIGNED_TEXT_XPOS(p))
87 #define PANEL_YPOS(p) (DY + ALIGNED_TEXT_YPOS(p))
89 // game panel display and control definitions
90 #define GAME_PANEL_LEVEL_NUMBER 0
91 #define GAME_PANEL_GEMS 1
92 #define GAME_PANEL_GEMS_NEEDED 2
93 #define GAME_PANEL_GEMS_COLLECTED 3
94 #define GAME_PANEL_GEMS_SCORE 4
95 #define GAME_PANEL_INVENTORY_COUNT 5
96 #define GAME_PANEL_INVENTORY_FIRST_1 6
97 #define GAME_PANEL_INVENTORY_FIRST_2 7
98 #define GAME_PANEL_INVENTORY_FIRST_3 8
99 #define GAME_PANEL_INVENTORY_FIRST_4 9
100 #define GAME_PANEL_INVENTORY_FIRST_5 10
101 #define GAME_PANEL_INVENTORY_FIRST_6 11
102 #define GAME_PANEL_INVENTORY_FIRST_7 12
103 #define GAME_PANEL_INVENTORY_FIRST_8 13
104 #define GAME_PANEL_INVENTORY_LAST_1 14
105 #define GAME_PANEL_INVENTORY_LAST_2 15
106 #define GAME_PANEL_INVENTORY_LAST_3 16
107 #define GAME_PANEL_INVENTORY_LAST_4 17
108 #define GAME_PANEL_INVENTORY_LAST_5 18
109 #define GAME_PANEL_INVENTORY_LAST_6 19
110 #define GAME_PANEL_INVENTORY_LAST_7 20
111 #define GAME_PANEL_INVENTORY_LAST_8 21
112 #define GAME_PANEL_KEY_1 22
113 #define GAME_PANEL_KEY_2 23
114 #define GAME_PANEL_KEY_3 24
115 #define GAME_PANEL_KEY_4 25
116 #define GAME_PANEL_KEY_5 26
117 #define GAME_PANEL_KEY_6 27
118 #define GAME_PANEL_KEY_7 28
119 #define GAME_PANEL_KEY_8 29
120 #define GAME_PANEL_KEY_WHITE 30
121 #define GAME_PANEL_KEY_WHITE_COUNT 31
122 #define GAME_PANEL_SCORE 32
123 #define GAME_PANEL_HIGHSCORE 33
124 #define GAME_PANEL_TIME 34
125 #define GAME_PANEL_TIME_HH 35
126 #define GAME_PANEL_TIME_MM 36
127 #define GAME_PANEL_TIME_SS 37
128 #define GAME_PANEL_TIME_ANIM 38
129 #define GAME_PANEL_HEALTH 39
130 #define GAME_PANEL_HEALTH_ANIM 40
131 #define GAME_PANEL_FRAME 41
132 #define GAME_PANEL_SHIELD_NORMAL 42
133 #define GAME_PANEL_SHIELD_NORMAL_TIME 43
134 #define GAME_PANEL_SHIELD_DEADLY 44
135 #define GAME_PANEL_SHIELD_DEADLY_TIME 45
136 #define GAME_PANEL_EXIT 46
137 #define GAME_PANEL_EMC_MAGIC_BALL 47
138 #define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 48
139 #define GAME_PANEL_LIGHT_SWITCH 49
140 #define GAME_PANEL_LIGHT_SWITCH_TIME 50
141 #define GAME_PANEL_TIMEGATE_SWITCH 51
142 #define GAME_PANEL_TIMEGATE_SWITCH_TIME 52
143 #define GAME_PANEL_SWITCHGATE_SWITCH 53
144 #define GAME_PANEL_EMC_LENSES 54
145 #define GAME_PANEL_EMC_LENSES_TIME 55
146 #define GAME_PANEL_EMC_MAGNIFIER 56
147 #define GAME_PANEL_EMC_MAGNIFIER_TIME 57
148 #define GAME_PANEL_BALLOON_SWITCH 58
149 #define GAME_PANEL_DYNABOMB_NUMBER 59
150 #define GAME_PANEL_DYNABOMB_SIZE 60
151 #define GAME_PANEL_DYNABOMB_POWER 61
152 #define GAME_PANEL_PENGUINS 62
153 #define GAME_PANEL_SOKOBAN_OBJECTS 63
154 #define GAME_PANEL_SOKOBAN_FIELDS 64
155 #define GAME_PANEL_ROBOT_WHEEL 65
156 #define GAME_PANEL_CONVEYOR_BELT_1 66
157 #define GAME_PANEL_CONVEYOR_BELT_2 67
158 #define GAME_PANEL_CONVEYOR_BELT_3 68
159 #define GAME_PANEL_CONVEYOR_BELT_4 69
160 #define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 70
161 #define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 71
162 #define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 72
163 #define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 73
164 #define GAME_PANEL_MAGIC_WALL 74
165 #define GAME_PANEL_MAGIC_WALL_TIME 75
166 #define GAME_PANEL_GRAVITY_STATE 76
167 #define GAME_PANEL_GRAPHIC_1 77
168 #define GAME_PANEL_GRAPHIC_2 78
169 #define GAME_PANEL_GRAPHIC_3 79
170 #define GAME_PANEL_GRAPHIC_4 80
171 #define GAME_PANEL_GRAPHIC_5 81
172 #define GAME_PANEL_GRAPHIC_6 82
173 #define GAME_PANEL_GRAPHIC_7 83
174 #define GAME_PANEL_GRAPHIC_8 84
175 #define GAME_PANEL_ELEMENT_1 85
176 #define GAME_PANEL_ELEMENT_2 86
177 #define GAME_PANEL_ELEMENT_3 87
178 #define GAME_PANEL_ELEMENT_4 88
179 #define GAME_PANEL_ELEMENT_5 89
180 #define GAME_PANEL_ELEMENT_6 90
181 #define GAME_PANEL_ELEMENT_7 91
182 #define GAME_PANEL_ELEMENT_8 92
183 #define GAME_PANEL_ELEMENT_COUNT_1 93
184 #define GAME_PANEL_ELEMENT_COUNT_2 94
185 #define GAME_PANEL_ELEMENT_COUNT_3 95
186 #define GAME_PANEL_ELEMENT_COUNT_4 96
187 #define GAME_PANEL_ELEMENT_COUNT_5 97
188 #define GAME_PANEL_ELEMENT_COUNT_6 98
189 #define GAME_PANEL_ELEMENT_COUNT_7 99
190 #define GAME_PANEL_ELEMENT_COUNT_8 100
191 #define GAME_PANEL_CE_SCORE_1 101
192 #define GAME_PANEL_CE_SCORE_2 102
193 #define GAME_PANEL_CE_SCORE_3 103
194 #define GAME_PANEL_CE_SCORE_4 104
195 #define GAME_PANEL_CE_SCORE_5 105
196 #define GAME_PANEL_CE_SCORE_6 106
197 #define GAME_PANEL_CE_SCORE_7 107
198 #define GAME_PANEL_CE_SCORE_8 108
199 #define GAME_PANEL_CE_SCORE_1_ELEMENT 109
200 #define GAME_PANEL_CE_SCORE_2_ELEMENT 110
201 #define GAME_PANEL_CE_SCORE_3_ELEMENT 111
202 #define GAME_PANEL_CE_SCORE_4_ELEMENT 112
203 #define GAME_PANEL_CE_SCORE_5_ELEMENT 113
204 #define GAME_PANEL_CE_SCORE_6_ELEMENT 114
205 #define GAME_PANEL_CE_SCORE_7_ELEMENT 115
206 #define GAME_PANEL_CE_SCORE_8_ELEMENT 116
207 #define GAME_PANEL_PLAYER_NAME 117
208 #define GAME_PANEL_LEVEL_NAME 118
209 #define GAME_PANEL_LEVEL_AUTHOR 119
211 #define NUM_GAME_PANEL_CONTROLS 120
213 struct GamePanelOrderInfo
219 static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
221 struct GamePanelControlInfo
225 struct TextPosInfo *pos;
228 int graphic, graphic_active;
230 int value, last_value;
231 int frame, last_frame;
236 static struct GamePanelControlInfo game_panel_controls[] =
239 GAME_PANEL_LEVEL_NUMBER,
240 &game.panel.level_number,
249 GAME_PANEL_GEMS_NEEDED,
250 &game.panel.gems_needed,
254 GAME_PANEL_GEMS_COLLECTED,
255 &game.panel.gems_collected,
259 GAME_PANEL_GEMS_SCORE,
260 &game.panel.gems_score,
264 GAME_PANEL_INVENTORY_COUNT,
265 &game.panel.inventory_count,
269 GAME_PANEL_INVENTORY_FIRST_1,
270 &game.panel.inventory_first[0],
274 GAME_PANEL_INVENTORY_FIRST_2,
275 &game.panel.inventory_first[1],
279 GAME_PANEL_INVENTORY_FIRST_3,
280 &game.panel.inventory_first[2],
284 GAME_PANEL_INVENTORY_FIRST_4,
285 &game.panel.inventory_first[3],
289 GAME_PANEL_INVENTORY_FIRST_5,
290 &game.panel.inventory_first[4],
294 GAME_PANEL_INVENTORY_FIRST_6,
295 &game.panel.inventory_first[5],
299 GAME_PANEL_INVENTORY_FIRST_7,
300 &game.panel.inventory_first[6],
304 GAME_PANEL_INVENTORY_FIRST_8,
305 &game.panel.inventory_first[7],
309 GAME_PANEL_INVENTORY_LAST_1,
310 &game.panel.inventory_last[0],
314 GAME_PANEL_INVENTORY_LAST_2,
315 &game.panel.inventory_last[1],
319 GAME_PANEL_INVENTORY_LAST_3,
320 &game.panel.inventory_last[2],
324 GAME_PANEL_INVENTORY_LAST_4,
325 &game.panel.inventory_last[3],
329 GAME_PANEL_INVENTORY_LAST_5,
330 &game.panel.inventory_last[4],
334 GAME_PANEL_INVENTORY_LAST_6,
335 &game.panel.inventory_last[5],
339 GAME_PANEL_INVENTORY_LAST_7,
340 &game.panel.inventory_last[6],
344 GAME_PANEL_INVENTORY_LAST_8,
345 &game.panel.inventory_last[7],
389 GAME_PANEL_KEY_WHITE,
390 &game.panel.key_white,
394 GAME_PANEL_KEY_WHITE_COUNT,
395 &game.panel.key_white_count,
404 GAME_PANEL_HIGHSCORE,
405 &game.panel.highscore,
429 GAME_PANEL_TIME_ANIM,
430 &game.panel.time_anim,
433 IMG_GFX_GAME_PANEL_TIME_ANIM,
434 IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
442 GAME_PANEL_HEALTH_ANIM,
443 &game.panel.health_anim,
446 IMG_GFX_GAME_PANEL_HEALTH_ANIM,
447 IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
455 GAME_PANEL_SHIELD_NORMAL,
456 &game.panel.shield_normal,
460 GAME_PANEL_SHIELD_NORMAL_TIME,
461 &game.panel.shield_normal_time,
465 GAME_PANEL_SHIELD_DEADLY,
466 &game.panel.shield_deadly,
470 GAME_PANEL_SHIELD_DEADLY_TIME,
471 &game.panel.shield_deadly_time,
480 GAME_PANEL_EMC_MAGIC_BALL,
481 &game.panel.emc_magic_ball,
485 GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
486 &game.panel.emc_magic_ball_switch,
490 GAME_PANEL_LIGHT_SWITCH,
491 &game.panel.light_switch,
495 GAME_PANEL_LIGHT_SWITCH_TIME,
496 &game.panel.light_switch_time,
500 GAME_PANEL_TIMEGATE_SWITCH,
501 &game.panel.timegate_switch,
505 GAME_PANEL_TIMEGATE_SWITCH_TIME,
506 &game.panel.timegate_switch_time,
510 GAME_PANEL_SWITCHGATE_SWITCH,
511 &game.panel.switchgate_switch,
515 GAME_PANEL_EMC_LENSES,
516 &game.panel.emc_lenses,
520 GAME_PANEL_EMC_LENSES_TIME,
521 &game.panel.emc_lenses_time,
525 GAME_PANEL_EMC_MAGNIFIER,
526 &game.panel.emc_magnifier,
530 GAME_PANEL_EMC_MAGNIFIER_TIME,
531 &game.panel.emc_magnifier_time,
535 GAME_PANEL_BALLOON_SWITCH,
536 &game.panel.balloon_switch,
540 GAME_PANEL_DYNABOMB_NUMBER,
541 &game.panel.dynabomb_number,
545 GAME_PANEL_DYNABOMB_SIZE,
546 &game.panel.dynabomb_size,
550 GAME_PANEL_DYNABOMB_POWER,
551 &game.panel.dynabomb_power,
556 &game.panel.penguins,
560 GAME_PANEL_SOKOBAN_OBJECTS,
561 &game.panel.sokoban_objects,
565 GAME_PANEL_SOKOBAN_FIELDS,
566 &game.panel.sokoban_fields,
570 GAME_PANEL_ROBOT_WHEEL,
571 &game.panel.robot_wheel,
575 GAME_PANEL_CONVEYOR_BELT_1,
576 &game.panel.conveyor_belt[0],
580 GAME_PANEL_CONVEYOR_BELT_2,
581 &game.panel.conveyor_belt[1],
585 GAME_PANEL_CONVEYOR_BELT_3,
586 &game.panel.conveyor_belt[2],
590 GAME_PANEL_CONVEYOR_BELT_4,
591 &game.panel.conveyor_belt[3],
595 GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
596 &game.panel.conveyor_belt_switch[0],
600 GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
601 &game.panel.conveyor_belt_switch[1],
605 GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
606 &game.panel.conveyor_belt_switch[2],
610 GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
611 &game.panel.conveyor_belt_switch[3],
615 GAME_PANEL_MAGIC_WALL,
616 &game.panel.magic_wall,
620 GAME_PANEL_MAGIC_WALL_TIME,
621 &game.panel.magic_wall_time,
625 GAME_PANEL_GRAVITY_STATE,
626 &game.panel.gravity_state,
630 GAME_PANEL_GRAPHIC_1,
631 &game.panel.graphic[0],
635 GAME_PANEL_GRAPHIC_2,
636 &game.panel.graphic[1],
640 GAME_PANEL_GRAPHIC_3,
641 &game.panel.graphic[2],
645 GAME_PANEL_GRAPHIC_4,
646 &game.panel.graphic[3],
650 GAME_PANEL_GRAPHIC_5,
651 &game.panel.graphic[4],
655 GAME_PANEL_GRAPHIC_6,
656 &game.panel.graphic[5],
660 GAME_PANEL_GRAPHIC_7,
661 &game.panel.graphic[6],
665 GAME_PANEL_GRAPHIC_8,
666 &game.panel.graphic[7],
670 GAME_PANEL_ELEMENT_1,
671 &game.panel.element[0],
675 GAME_PANEL_ELEMENT_2,
676 &game.panel.element[1],
680 GAME_PANEL_ELEMENT_3,
681 &game.panel.element[2],
685 GAME_PANEL_ELEMENT_4,
686 &game.panel.element[3],
690 GAME_PANEL_ELEMENT_5,
691 &game.panel.element[4],
695 GAME_PANEL_ELEMENT_6,
696 &game.panel.element[5],
700 GAME_PANEL_ELEMENT_7,
701 &game.panel.element[6],
705 GAME_PANEL_ELEMENT_8,
706 &game.panel.element[7],
710 GAME_PANEL_ELEMENT_COUNT_1,
711 &game.panel.element_count[0],
715 GAME_PANEL_ELEMENT_COUNT_2,
716 &game.panel.element_count[1],
720 GAME_PANEL_ELEMENT_COUNT_3,
721 &game.panel.element_count[2],
725 GAME_PANEL_ELEMENT_COUNT_4,
726 &game.panel.element_count[3],
730 GAME_PANEL_ELEMENT_COUNT_5,
731 &game.panel.element_count[4],
735 GAME_PANEL_ELEMENT_COUNT_6,
736 &game.panel.element_count[5],
740 GAME_PANEL_ELEMENT_COUNT_7,
741 &game.panel.element_count[6],
745 GAME_PANEL_ELEMENT_COUNT_8,
746 &game.panel.element_count[7],
750 GAME_PANEL_CE_SCORE_1,
751 &game.panel.ce_score[0],
755 GAME_PANEL_CE_SCORE_2,
756 &game.panel.ce_score[1],
760 GAME_PANEL_CE_SCORE_3,
761 &game.panel.ce_score[2],
765 GAME_PANEL_CE_SCORE_4,
766 &game.panel.ce_score[3],
770 GAME_PANEL_CE_SCORE_5,
771 &game.panel.ce_score[4],
775 GAME_PANEL_CE_SCORE_6,
776 &game.panel.ce_score[5],
780 GAME_PANEL_CE_SCORE_7,
781 &game.panel.ce_score[6],
785 GAME_PANEL_CE_SCORE_8,
786 &game.panel.ce_score[7],
790 GAME_PANEL_CE_SCORE_1_ELEMENT,
791 &game.panel.ce_score_element[0],
795 GAME_PANEL_CE_SCORE_2_ELEMENT,
796 &game.panel.ce_score_element[1],
800 GAME_PANEL_CE_SCORE_3_ELEMENT,
801 &game.panel.ce_score_element[2],
805 GAME_PANEL_CE_SCORE_4_ELEMENT,
806 &game.panel.ce_score_element[3],
810 GAME_PANEL_CE_SCORE_5_ELEMENT,
811 &game.panel.ce_score_element[4],
815 GAME_PANEL_CE_SCORE_6_ELEMENT,
816 &game.panel.ce_score_element[5],
820 GAME_PANEL_CE_SCORE_7_ELEMENT,
821 &game.panel.ce_score_element[6],
825 GAME_PANEL_CE_SCORE_8_ELEMENT,
826 &game.panel.ce_score_element[7],
830 GAME_PANEL_PLAYER_NAME,
831 &game.panel.player_name,
835 GAME_PANEL_LEVEL_NAME,
836 &game.panel.level_name,
840 GAME_PANEL_LEVEL_AUTHOR,
841 &game.panel.level_author,
852 // values for delayed check of falling and moving elements and for collision
853 #define CHECK_DELAY_MOVING 3
854 #define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
855 #define CHECK_DELAY_COLLISION 2
856 #define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
858 // values for initial player move delay (initial delay counter value)
859 #define INITIAL_MOVE_DELAY_OFF -1
860 #define INITIAL_MOVE_DELAY_ON 0
862 // values for player movement speed (which is in fact a delay value)
863 #define MOVE_DELAY_MIN_SPEED 32
864 #define MOVE_DELAY_NORMAL_SPEED 8
865 #define MOVE_DELAY_HIGH_SPEED 4
866 #define MOVE_DELAY_MAX_SPEED 1
868 #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
869 #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
871 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
872 #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
874 // values for scroll positions
875 #define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
876 (x) > SBX_Right + MIDPOSX ? SBX_Right :\
878 #define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
879 (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
882 // values for other actions
883 #define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
884 #define MOVE_STEPSIZE_MIN (1)
885 #define MOVE_STEPSIZE_MAX (TILEX)
887 #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
888 #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
890 #define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
892 #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
893 RND(element_info[e].push_delay_random))
894 #define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
895 RND(element_info[e].drop_delay_random))
896 #define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
897 RND(element_info[e].move_delay_random))
898 #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
899 (element_info[e].move_delay_random))
900 #define GET_NEW_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
901 RND(element_info[e].step_delay_random))
902 #define GET_MAX_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
903 (element_info[e].step_delay_random))
904 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
905 RND(element_info[e].ce_value_random_initial))
906 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
907 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
908 RND((c)->delay_random * (c)->delay_frames))
909 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
910 RND((c)->delay_random))
913 #define GET_VALID_RUNTIME_ELEMENT(e) \
914 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
916 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
917 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
918 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
919 (be) + (e) - EL_SELF)
921 #define GET_PLAYER_FROM_BITS(p) \
922 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
924 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
925 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
926 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
927 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
928 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
929 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
930 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
931 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
932 RESOLVED_REFERENCE_ELEMENT(be, e) : \
935 #define CAN_GROW_INTO(e) \
936 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
938 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
939 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
942 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
943 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
944 (CAN_MOVE_INTO_ACID(e) && \
945 Tile[x][y] == EL_ACID) || \
948 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
949 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
950 (CAN_MOVE_INTO_ACID(e) && \
951 Tile[x][y] == EL_ACID) || \
954 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
955 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
957 (CAN_MOVE_INTO_ACID(e) && \
958 Tile[x][y] == EL_ACID) || \
959 (DONT_COLLIDE_WITH(e) && \
961 !PLAYER_ENEMY_PROTECTED(x, y))))
963 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
964 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
966 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
967 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
969 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
970 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
972 #define ANDROID_CAN_CLONE_FIELD(x, y) \
973 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
974 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
976 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
977 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
979 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
980 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
982 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
983 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
985 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
986 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
988 #define PIG_CAN_ENTER_FIELD(e, x, y) \
989 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
991 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
992 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
993 Tile[x][y] == EL_EM_EXIT_OPEN || \
994 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
995 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
996 IS_FOOD_PENGUIN(Tile[x][y])))
997 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
998 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
1000 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
1001 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
1003 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
1004 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
1006 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
1007 (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
1008 Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
1010 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
1012 #define CE_ENTER_FIELD_COND(e, x, y) \
1013 (!IS_PLAYER(x, y) && \
1014 IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
1016 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
1017 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
1019 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
1020 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1022 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1023 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1024 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1025 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1027 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1029 // game button identifiers
1030 #define GAME_CTRL_ID_STOP 0
1031 #define GAME_CTRL_ID_PAUSE 1
1032 #define GAME_CTRL_ID_PLAY 2
1033 #define GAME_CTRL_ID_UNDO 3
1034 #define GAME_CTRL_ID_REDO 4
1035 #define GAME_CTRL_ID_SAVE 5
1036 #define GAME_CTRL_ID_PAUSE2 6
1037 #define GAME_CTRL_ID_LOAD 7
1038 #define GAME_CTRL_ID_RESTART 8
1039 #define GAME_CTRL_ID_PANEL_STOP 9
1040 #define GAME_CTRL_ID_PANEL_PAUSE 10
1041 #define GAME_CTRL_ID_PANEL_PLAY 11
1042 #define GAME_CTRL_ID_PANEL_RESTART 12
1043 #define GAME_CTRL_ID_TOUCH_STOP 13
1044 #define GAME_CTRL_ID_TOUCH_PAUSE 14
1045 #define GAME_CTRL_ID_TOUCH_RESTART 15
1046 #define SOUND_CTRL_ID_MUSIC 16
1047 #define SOUND_CTRL_ID_LOOPS 17
1048 #define SOUND_CTRL_ID_SIMPLE 18
1049 #define SOUND_CTRL_ID_PANEL_MUSIC 19
1050 #define SOUND_CTRL_ID_PANEL_LOOPS 20
1051 #define SOUND_CTRL_ID_PANEL_SIMPLE 21
1053 #define NUM_GAME_BUTTONS 22
1056 // forward declaration for internal use
1058 static void CreateField(int, int, int);
1060 static void ResetGfxAnimation(int, int);
1062 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1063 static void AdvanceFrameAndPlayerCounters(int);
1065 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1066 static boolean MovePlayer(struct PlayerInfo *, int, int);
1067 static void ScrollPlayer(struct PlayerInfo *, int);
1068 static void ScrollScreen(struct PlayerInfo *, int);
1070 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1071 static boolean DigFieldByCE(int, int, int);
1072 static boolean SnapField(struct PlayerInfo *, int, int);
1073 static boolean DropElement(struct PlayerInfo *);
1075 static void InitBeltMovement(void);
1076 static void CloseAllOpenTimegates(void);
1077 static void CheckGravityMovement(struct PlayerInfo *);
1078 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1079 static void KillPlayerUnlessEnemyProtected(int, int);
1080 static void KillPlayerUnlessExplosionProtected(int, int);
1082 static void CheckNextToConditions(int, int);
1083 static void TestIfPlayerNextToCustomElement(int, int);
1084 static void TestIfPlayerTouchesCustomElement(int, int);
1085 static void TestIfElementNextToCustomElement(int, int);
1086 static void TestIfElementTouchesCustomElement(int, int);
1087 static void TestIfElementHitsCustomElement(int, int, int);
1089 static void HandleElementChange(int, int, int);
1090 static void ExecuteCustomElementAction(int, int, int, int);
1091 static boolean ChangeElement(int, int, int, int);
1093 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int, int, int);
1094 #define CheckTriggeredElementChange(x, y, e, ev) \
1095 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, -1)
1096 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1097 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1098 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1099 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1100 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1101 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1102 #define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
1103 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1105 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1106 #define CheckElementChange(x, y, e, te, ev) \
1107 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1108 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1109 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1110 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1111 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1112 #define CheckElementChangeByMouse(x, y, e, ev, s) \
1113 CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
1115 static void PlayLevelSound(int, int, int);
1116 static void PlayLevelSoundNearest(int, int, int);
1117 static void PlayLevelSoundAction(int, int, int);
1118 static void PlayLevelSoundElementAction(int, int, int, int);
1119 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1120 static void PlayLevelSoundActionIfLoop(int, int, int);
1121 static void StopLevelSoundActionIfLoop(int, int, int);
1122 static void PlayLevelMusic(void);
1123 static void FadeLevelSoundsAndMusic(void);
1125 static void HandleGameButtons(struct GadgetInfo *);
1127 int AmoebaNeighbourNr(int, int);
1128 void AmoebaToDiamond(int, int);
1129 void ContinueMoving(int, int);
1130 void Bang(int, int);
1131 void InitMovDir(int, int);
1132 void InitAmoebaNr(int, int);
1133 void NewHighScore(int, boolean);
1135 void TestIfGoodThingHitsBadThing(int, int, int);
1136 void TestIfBadThingHitsGoodThing(int, int, int);
1137 void TestIfPlayerTouchesBadThing(int, int);
1138 void TestIfPlayerRunsIntoBadThing(int, int, int);
1139 void TestIfBadThingTouchesPlayer(int, int);
1140 void TestIfBadThingRunsIntoPlayer(int, int, int);
1141 void TestIfFriendTouchesBadThing(int, int);
1142 void TestIfBadThingTouchesFriend(int, int);
1143 void TestIfBadThingTouchesOtherBadThing(int, int);
1144 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1146 void KillPlayer(struct PlayerInfo *);
1147 void BuryPlayer(struct PlayerInfo *);
1148 void RemovePlayer(struct PlayerInfo *);
1149 void ExitPlayer(struct PlayerInfo *);
1151 static int getInvisibleActiveFromInvisibleElement(int);
1152 static int getInvisibleFromInvisibleActiveElement(int);
1154 static void TestFieldAfterSnapping(int, int, int, int, int);
1156 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1158 // for detection of endless loops, caused by custom element programming
1159 // (using maximal playfield width x 10 is just a rough approximation)
1160 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1162 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1164 if (recursion_loop_detected) \
1167 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1169 recursion_loop_detected = TRUE; \
1170 recursion_loop_element = (e); \
1173 recursion_loop_depth++; \
1176 #define RECURSION_LOOP_DETECTION_END() \
1178 recursion_loop_depth--; \
1181 static int recursion_loop_depth;
1182 static boolean recursion_loop_detected;
1183 static boolean recursion_loop_element;
1185 static int map_player_action[MAX_PLAYERS];
1188 // ----------------------------------------------------------------------------
1189 // definition of elements that automatically change to other elements after
1190 // a specified time, eventually calling a function when changing
1191 // ----------------------------------------------------------------------------
1193 // forward declaration for changer functions
1194 static void InitBuggyBase(int, int);
1195 static void WarnBuggyBase(int, int);
1197 static void InitTrap(int, int);
1198 static void ActivateTrap(int, int);
1199 static void ChangeActiveTrap(int, int);
1201 static void InitRobotWheel(int, int);
1202 static void RunRobotWheel(int, int);
1203 static void StopRobotWheel(int, int);
1205 static void InitTimegateWheel(int, int);
1206 static void RunTimegateWheel(int, int);
1208 static void InitMagicBallDelay(int, int);
1209 static void ActivateMagicBall(int, int);
1211 struct ChangingElementInfo
1216 void (*pre_change_function)(int x, int y);
1217 void (*change_function)(int x, int y);
1218 void (*post_change_function)(int x, int y);
1221 static struct ChangingElementInfo change_delay_list[] =
1256 EL_STEEL_EXIT_OPENING,
1264 EL_STEEL_EXIT_CLOSING,
1265 EL_STEEL_EXIT_CLOSED,
1288 EL_EM_STEEL_EXIT_OPENING,
1289 EL_EM_STEEL_EXIT_OPEN,
1296 EL_EM_STEEL_EXIT_CLOSING,
1320 EL_SWITCHGATE_OPENING,
1328 EL_SWITCHGATE_CLOSING,
1329 EL_SWITCHGATE_CLOSED,
1336 EL_TIMEGATE_OPENING,
1344 EL_TIMEGATE_CLOSING,
1353 EL_ACID_SPLASH_LEFT,
1361 EL_ACID_SPLASH_RIGHT,
1370 EL_SP_BUGGY_BASE_ACTIVATING,
1377 EL_SP_BUGGY_BASE_ACTIVATING,
1378 EL_SP_BUGGY_BASE_ACTIVE,
1385 EL_SP_BUGGY_BASE_ACTIVE,
1409 EL_ROBOT_WHEEL_ACTIVE,
1417 EL_TIMEGATE_SWITCH_ACTIVE,
1425 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1426 EL_DC_TIMEGATE_SWITCH,
1433 EL_EMC_MAGIC_BALL_ACTIVE,
1434 EL_EMC_MAGIC_BALL_ACTIVE,
1441 EL_EMC_SPRING_BUMPER_ACTIVE,
1442 EL_EMC_SPRING_BUMPER,
1449 EL_DIAGONAL_SHRINKING,
1457 EL_DIAGONAL_GROWING,
1478 int push_delay_fixed, push_delay_random;
1482 { EL_SPRING, 0, 0 },
1483 { EL_BALLOON, 0, 0 },
1485 { EL_SOKOBAN_OBJECT, 2, 0 },
1486 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1487 { EL_SATELLITE, 2, 0 },
1488 { EL_SP_DISK_YELLOW, 2, 0 },
1490 { EL_UNDEFINED, 0, 0 },
1498 move_stepsize_list[] =
1500 { EL_AMOEBA_DROP, 2 },
1501 { EL_AMOEBA_DROPPING, 2 },
1502 { EL_QUICKSAND_FILLING, 1 },
1503 { EL_QUICKSAND_EMPTYING, 1 },
1504 { EL_QUICKSAND_FAST_FILLING, 2 },
1505 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1506 { EL_MAGIC_WALL_FILLING, 2 },
1507 { EL_MAGIC_WALL_EMPTYING, 2 },
1508 { EL_BD_MAGIC_WALL_FILLING, 2 },
1509 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1510 { EL_DC_MAGIC_WALL_FILLING, 2 },
1511 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1513 { EL_UNDEFINED, 0 },
1521 collect_count_list[] =
1524 { EL_BD_DIAMOND, 1 },
1525 { EL_EMERALD_YELLOW, 1 },
1526 { EL_EMERALD_RED, 1 },
1527 { EL_EMERALD_PURPLE, 1 },
1529 { EL_SP_INFOTRON, 1 },
1533 { EL_UNDEFINED, 0 },
1541 access_direction_list[] =
1543 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1544 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1545 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1546 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1547 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1548 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1549 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1550 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1551 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1552 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1553 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1555 { EL_SP_PORT_LEFT, MV_RIGHT },
1556 { EL_SP_PORT_RIGHT, MV_LEFT },
1557 { EL_SP_PORT_UP, MV_DOWN },
1558 { EL_SP_PORT_DOWN, MV_UP },
1559 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1560 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1561 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1562 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1563 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1564 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1565 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1566 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1567 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1568 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1569 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1570 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1571 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1572 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1573 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1575 { EL_UNDEFINED, MV_NONE }
1578 static struct XY xy_topdown[] =
1586 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1588 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1589 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1590 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1591 IS_JUST_CHANGING(x, y))
1593 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1595 // static variables for playfield scan mode (scanning forward or backward)
1596 static int playfield_scan_start_x = 0;
1597 static int playfield_scan_start_y = 0;
1598 static int playfield_scan_delta_x = 1;
1599 static int playfield_scan_delta_y = 1;
1601 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1602 (y) >= 0 && (y) <= lev_fieldy - 1; \
1603 (y) += playfield_scan_delta_y) \
1604 for ((x) = playfield_scan_start_x; \
1605 (x) >= 0 && (x) <= lev_fieldx - 1; \
1606 (x) += playfield_scan_delta_x)
1609 void DEBUG_SetMaximumDynamite(void)
1613 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1614 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1615 local_player->inventory_element[local_player->inventory_size++] =
1620 static void InitPlayfieldScanModeVars(void)
1622 if (game.use_reverse_scan_direction)
1624 playfield_scan_start_x = lev_fieldx - 1;
1625 playfield_scan_start_y = lev_fieldy - 1;
1627 playfield_scan_delta_x = -1;
1628 playfield_scan_delta_y = -1;
1632 playfield_scan_start_x = 0;
1633 playfield_scan_start_y = 0;
1635 playfield_scan_delta_x = 1;
1636 playfield_scan_delta_y = 1;
1640 static void InitPlayfieldScanMode(int mode)
1642 game.use_reverse_scan_direction =
1643 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1645 InitPlayfieldScanModeVars();
1648 static int get_move_delay_from_stepsize(int move_stepsize)
1651 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1653 // make sure that stepsize value is always a power of 2
1654 move_stepsize = (1 << log_2(move_stepsize));
1656 return TILEX / move_stepsize;
1659 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1662 int player_nr = player->index_nr;
1663 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1664 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1666 // do no immediately change move delay -- the player might just be moving
1667 player->move_delay_value_next = move_delay;
1669 // information if player can move must be set separately
1670 player->cannot_move = cannot_move;
1674 player->move_delay = game.initial_move_delay[player_nr];
1675 player->move_delay_value = game.initial_move_delay_value[player_nr];
1677 player->move_delay_value_next = -1;
1679 player->move_delay_reset_counter = 0;
1683 void GetPlayerConfig(void)
1685 GameFrameDelay = setup.game_frame_delay;
1687 if (!audio.sound_available)
1688 setup.sound_simple = FALSE;
1690 if (!audio.loops_available)
1691 setup.sound_loops = FALSE;
1693 if (!audio.music_available)
1694 setup.sound_music = FALSE;
1696 if (!video.fullscreen_available)
1697 setup.fullscreen = FALSE;
1699 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1701 SetAudioMode(setup.sound);
1704 int GetElementFromGroupElement(int element)
1706 if (IS_GROUP_ELEMENT(element))
1708 struct ElementGroupInfo *group = element_info[element].group;
1709 int last_anim_random_frame = gfx.anim_random_frame;
1712 if (group->choice_mode == ANIM_RANDOM)
1713 gfx.anim_random_frame = RND(group->num_elements_resolved);
1715 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1716 group->choice_mode, 0,
1719 if (group->choice_mode == ANIM_RANDOM)
1720 gfx.anim_random_frame = last_anim_random_frame;
1722 group->choice_pos++;
1724 element = group->element_resolved[element_pos];
1730 static void IncrementSokobanFieldsNeeded(void)
1732 if (level.sb_fields_needed)
1733 game.sokoban_fields_still_needed++;
1736 static void IncrementSokobanObjectsNeeded(void)
1738 if (level.sb_objects_needed)
1739 game.sokoban_objects_still_needed++;
1742 static void DecrementSokobanFieldsNeeded(void)
1744 if (game.sokoban_fields_still_needed > 0)
1745 game.sokoban_fields_still_needed--;
1748 static void DecrementSokobanObjectsNeeded(void)
1750 if (game.sokoban_objects_still_needed > 0)
1751 game.sokoban_objects_still_needed--;
1754 static void InitPlayerField(int x, int y, int element, boolean init_game)
1756 if (element == EL_SP_MURPHY)
1760 if (stored_player[0].present)
1762 Tile[x][y] = EL_SP_MURPHY_CLONE;
1768 stored_player[0].initial_element = element;
1769 stored_player[0].use_murphy = TRUE;
1771 if (!level.use_artwork_element[0])
1772 stored_player[0].artwork_element = EL_SP_MURPHY;
1775 Tile[x][y] = EL_PLAYER_1;
1781 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1782 int jx = player->jx, jy = player->jy;
1784 player->present = TRUE;
1786 player->block_last_field = (element == EL_SP_MURPHY ?
1787 level.sp_block_last_field :
1788 level.block_last_field);
1790 // ---------- initialize player's last field block delay ------------------
1792 // always start with reliable default value (no adjustment needed)
1793 player->block_delay_adjustment = 0;
1795 // special case 1: in Supaplex, Murphy blocks last field one more frame
1796 if (player->block_last_field && element == EL_SP_MURPHY)
1797 player->block_delay_adjustment = 1;
1799 // special case 2: in game engines before 3.1.1, blocking was different
1800 if (game.use_block_last_field_bug)
1801 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1803 if (!network.enabled || player->connected_network)
1805 player->active = TRUE;
1807 // remove potentially duplicate players
1808 if (IN_LEV_FIELD(jx, jy) && StorePlayer[jx][jy] == Tile[x][y])
1809 StorePlayer[jx][jy] = 0;
1811 StorePlayer[x][y] = Tile[x][y];
1813 #if DEBUG_INIT_PLAYER
1814 Debug("game:init:player", "- player element %d activated",
1815 player->element_nr);
1816 Debug("game:init:player", " (local player is %d and currently %s)",
1817 local_player->element_nr,
1818 local_player->active ? "active" : "not active");
1822 Tile[x][y] = EL_EMPTY;
1824 player->jx = player->last_jx = x;
1825 player->jy = player->last_jy = y;
1828 // always check if player was just killed and should be reanimated
1830 int player_nr = GET_PLAYER_NR(element);
1831 struct PlayerInfo *player = &stored_player[player_nr];
1833 if (player->active && player->killed)
1834 player->reanimated = TRUE; // if player was just killed, reanimate him
1838 static void InitFieldForEngine_RND(int x, int y)
1840 int element = Tile[x][y];
1842 // convert BD engine elements to corresponding R'n'D engine elements
1843 element = (element == EL_BDX_EMPTY ? EL_EMPTY :
1844 element == EL_BDX_PLAYER ? EL_PLAYER_1 :
1845 element == EL_BDX_INBOX ? EL_PLAYER_1 :
1846 element == EL_BDX_SAND_1 ? EL_SAND :
1847 element == EL_BDX_STEELWALL ? EL_STEELWALL :
1848 element == EL_BDX_EXIT_CLOSED ? EL_EXIT_CLOSED :
1849 element == EL_BDX_EXIT_OPEN ? EL_EXIT_OPEN :
1852 Tile[x][y] = element;
1855 static void InitFieldForEngine(int x, int y)
1857 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
1858 InitFieldForEngine_RND(x, y);
1861 static void InitField(int x, int y, boolean init_game)
1863 int element = Tile[x][y];
1872 InitPlayerField(x, y, element, init_game);
1875 case EL_SOKOBAN_FIELD_PLAYER:
1876 element = Tile[x][y] = EL_PLAYER_1;
1877 InitField(x, y, init_game);
1879 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1880 InitField(x, y, init_game);
1883 case EL_SOKOBAN_FIELD_EMPTY:
1884 IncrementSokobanFieldsNeeded();
1887 case EL_SOKOBAN_OBJECT:
1888 IncrementSokobanObjectsNeeded();
1892 if (x < lev_fieldx - 1 && Tile[x + 1][y] == EL_ACID)
1893 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1894 else if (x > 0 && Tile[x - 1][y] == EL_ACID)
1895 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1896 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPLEFT)
1897 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1898 else if (y > 0 && Tile[x][y - 1] == EL_ACID)
1899 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1900 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPRIGHT)
1901 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1910 case EL_SPACESHIP_RIGHT:
1911 case EL_SPACESHIP_UP:
1912 case EL_SPACESHIP_LEFT:
1913 case EL_SPACESHIP_DOWN:
1914 case EL_BD_BUTTERFLY:
1915 case EL_BD_BUTTERFLY_RIGHT:
1916 case EL_BD_BUTTERFLY_UP:
1917 case EL_BD_BUTTERFLY_LEFT:
1918 case EL_BD_BUTTERFLY_DOWN:
1920 case EL_BD_FIREFLY_RIGHT:
1921 case EL_BD_FIREFLY_UP:
1922 case EL_BD_FIREFLY_LEFT:
1923 case EL_BD_FIREFLY_DOWN:
1924 case EL_PACMAN_RIGHT:
1926 case EL_PACMAN_LEFT:
1927 case EL_PACMAN_DOWN:
1929 case EL_YAMYAM_LEFT:
1930 case EL_YAMYAM_RIGHT:
1932 case EL_YAMYAM_DOWN:
1933 case EL_DARK_YAMYAM:
1936 case EL_SP_SNIKSNAK:
1937 case EL_SP_ELECTRON:
1943 case EL_SPRING_LEFT:
1944 case EL_SPRING_RIGHT:
1948 case EL_AMOEBA_FULL:
1953 case EL_AMOEBA_DROP:
1954 if (y == lev_fieldy - 1)
1956 Tile[x][y] = EL_AMOEBA_GROWING;
1957 Store[x][y] = EL_AMOEBA_WET;
1961 case EL_DYNAMITE_ACTIVE:
1962 case EL_SP_DISK_RED_ACTIVE:
1963 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1964 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1965 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1966 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1967 MovDelay[x][y] = 96;
1970 case EL_EM_DYNAMITE_ACTIVE:
1971 MovDelay[x][y] = 32;
1975 game.lights_still_needed++;
1979 game.friends_still_needed++;
1984 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1987 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1988 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1989 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1990 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1991 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1992 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1993 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1994 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1995 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1996 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1997 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1998 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
2001 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
2002 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
2003 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
2005 if (game.belt_dir_nr[belt_nr] == 3) // initial value
2007 game.belt_dir[belt_nr] = belt_dir;
2008 game.belt_dir_nr[belt_nr] = belt_dir_nr;
2010 else // more than one switch -- set it like the first switch
2012 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
2017 case EL_LIGHT_SWITCH_ACTIVE:
2019 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
2022 case EL_INVISIBLE_STEELWALL:
2023 case EL_INVISIBLE_WALL:
2024 case EL_INVISIBLE_SAND:
2025 if (game.light_time_left > 0 ||
2026 game.lenses_time_left > 0)
2027 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
2030 case EL_EMC_MAGIC_BALL:
2031 if (game.ball_active)
2032 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
2035 case EL_EMC_MAGIC_BALL_SWITCH:
2036 if (game.ball_active)
2037 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
2040 case EL_TRIGGER_PLAYER:
2041 case EL_TRIGGER_ELEMENT:
2042 case EL_TRIGGER_CE_VALUE:
2043 case EL_TRIGGER_CE_SCORE:
2045 case EL_ANY_ELEMENT:
2046 case EL_CURRENT_CE_VALUE:
2047 case EL_CURRENT_CE_SCORE:
2064 // reference elements should not be used on the playfield
2065 Tile[x][y] = EL_EMPTY;
2069 if (IS_CUSTOM_ELEMENT(element))
2071 if (CAN_MOVE(element))
2074 if (!element_info[element].use_last_ce_value || init_game)
2075 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2077 else if (IS_GROUP_ELEMENT(element))
2079 Tile[x][y] = GetElementFromGroupElement(element);
2081 InitField(x, y, init_game);
2083 else if (IS_EMPTY_ELEMENT(element))
2085 GfxElementEmpty[x][y] = element;
2086 Tile[x][y] = EL_EMPTY;
2088 if (element_info[element].use_gfx_element)
2089 game.use_masked_elements = TRUE;
2096 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2099 static void InitField_WithBug1(int x, int y, boolean init_game)
2101 InitField(x, y, init_game);
2103 // not needed to call InitMovDir() -- already done by InitField()!
2104 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2105 CAN_MOVE(Tile[x][y]))
2109 static void InitField_WithBug2(int x, int y, boolean init_game)
2111 int old_element = Tile[x][y];
2113 InitField(x, y, init_game);
2115 // not needed to call InitMovDir() -- already done by InitField()!
2116 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2117 CAN_MOVE(old_element) &&
2118 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2121 /* this case is in fact a combination of not less than three bugs:
2122 first, it calls InitMovDir() for elements that can move, although this is
2123 already done by InitField(); then, it checks the element that was at this
2124 field _before_ the call to InitField() (which can change it); lastly, it
2125 was not called for "mole with direction" elements, which were treated as
2126 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2130 static int get_key_element_from_nr(int key_nr)
2132 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2133 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2134 EL_EM_KEY_1 : EL_KEY_1);
2136 return key_base_element + key_nr;
2139 static int get_next_dropped_element(struct PlayerInfo *player)
2141 return (player->inventory_size > 0 ?
2142 player->inventory_element[player->inventory_size - 1] :
2143 player->inventory_infinite_element != EL_UNDEFINED ?
2144 player->inventory_infinite_element :
2145 player->dynabombs_left > 0 ?
2146 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2150 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2152 // pos >= 0: get element from bottom of the stack;
2153 // pos < 0: get element from top of the stack
2157 int min_inventory_size = -pos;
2158 int inventory_pos = player->inventory_size - min_inventory_size;
2159 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2161 return (player->inventory_size >= min_inventory_size ?
2162 player->inventory_element[inventory_pos] :
2163 player->inventory_infinite_element != EL_UNDEFINED ?
2164 player->inventory_infinite_element :
2165 player->dynabombs_left >= min_dynabombs_left ?
2166 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2171 int min_dynabombs_left = pos + 1;
2172 int min_inventory_size = pos + 1 - player->dynabombs_left;
2173 int inventory_pos = pos - player->dynabombs_left;
2175 return (player->inventory_infinite_element != EL_UNDEFINED ?
2176 player->inventory_infinite_element :
2177 player->dynabombs_left >= min_dynabombs_left ?
2178 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2179 player->inventory_size >= min_inventory_size ?
2180 player->inventory_element[inventory_pos] :
2185 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2187 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2188 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2191 if (gpo1->sort_priority != gpo2->sort_priority)
2192 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2194 compare_result = gpo1->nr - gpo2->nr;
2196 return compare_result;
2199 int getPlayerInventorySize(int player_nr)
2201 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2202 return game_em.ply[player_nr]->dynamite;
2203 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2204 return game_sp.red_disk_count;
2206 return stored_player[player_nr].inventory_size;
2209 static void InitGameControlValues(void)
2213 for (i = 0; game_panel_controls[i].nr != -1; i++)
2215 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2216 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2217 struct TextPosInfo *pos = gpc->pos;
2219 int type = gpc->type;
2223 Error("'game_panel_controls' structure corrupted at %d", i);
2225 Fail("this should not happen -- please debug");
2228 // force update of game controls after initialization
2229 gpc->value = gpc->last_value = -1;
2230 gpc->frame = gpc->last_frame = -1;
2231 gpc->gfx_frame = -1;
2233 // determine panel value width for later calculation of alignment
2234 if (type == TYPE_INTEGER || type == TYPE_STRING)
2236 pos->width = pos->size * getFontWidth(pos->font);
2237 pos->height = getFontHeight(pos->font);
2239 else if (type == TYPE_ELEMENT)
2241 pos->width = pos->size;
2242 pos->height = pos->size;
2245 // fill structure for game panel draw order
2247 gpo->sort_priority = pos->sort_priority;
2250 // sort game panel controls according to sort_priority and control number
2251 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2252 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2255 static void UpdatePlayfieldElementCount(void)
2257 boolean use_element_count = FALSE;
2260 // first check if it is needed at all to calculate playfield element count
2261 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2262 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2263 use_element_count = TRUE;
2265 if (!use_element_count)
2268 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2269 element_info[i].element_count = 0;
2271 SCAN_PLAYFIELD(x, y)
2273 element_info[Tile[x][y]].element_count++;
2276 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2277 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2278 if (IS_IN_GROUP(j, i))
2279 element_info[EL_GROUP_START + i].element_count +=
2280 element_info[j].element_count;
2283 static void UpdateGameControlValues(void)
2286 int time = (game.LevelSolved ?
2287 game.LevelSolved_CountingTime :
2288 level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2290 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2292 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2293 game_sp.time_played :
2294 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2295 game_mm.energy_left :
2296 game.no_level_time_limit ? TimePlayed : TimeLeft);
2297 int score = (game.LevelSolved ?
2298 game.LevelSolved_CountingScore :
2299 level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2301 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2302 game_em.lev->score :
2303 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2305 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2308 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2309 game_bd.gems_still_needed :
2310 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2311 game_em.lev->gems_needed :
2312 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2313 game_sp.infotrons_still_needed :
2314 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2315 game_mm.kettles_still_needed :
2316 game.gems_still_needed);
2317 int gems_needed = level.gems_needed;
2318 int gems_collected = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2319 game_bd.game->cave->diamonds_collected :
2320 gems_needed - gems);
2321 int gems_score = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2322 game_bd.game->cave->diamond_value :
2323 level.score[SC_EMERALD]);
2324 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2325 game_bd.gems_still_needed > 0 :
2326 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2327 game_em.lev->gems_needed > 0 :
2328 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2329 game_sp.infotrons_still_needed > 0 :
2330 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2331 game_mm.kettles_still_needed > 0 ||
2332 game_mm.lights_still_needed > 0 :
2333 game.gems_still_needed > 0 ||
2334 game.sokoban_fields_still_needed > 0 ||
2335 game.sokoban_objects_still_needed > 0 ||
2336 game.lights_still_needed > 0);
2337 int health = (game.LevelSolved ?
2338 game.LevelSolved_CountingHealth :
2339 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2340 MM_HEALTH(game_mm.laser_overload_value) :
2342 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2344 UpdatePlayfieldElementCount();
2346 // update game panel control values
2348 // used instead of "level_nr" (for network games)
2349 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2350 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2351 game_panel_controls[GAME_PANEL_GEMS_NEEDED].value = gems_needed;
2352 game_panel_controls[GAME_PANEL_GEMS_COLLECTED].value = gems_collected;
2353 game_panel_controls[GAME_PANEL_GEMS_SCORE].value = gems_score;
2355 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2356 for (i = 0; i < MAX_NUM_KEYS; i++)
2357 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2358 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2359 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2361 if (game.centered_player_nr == -1)
2363 for (i = 0; i < MAX_PLAYERS; i++)
2365 // only one player in Supaplex game engine
2366 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2369 for (k = 0; k < MAX_NUM_KEYS; k++)
2371 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2373 if (game_em.ply[i]->keys & (1 << k))
2374 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2375 get_key_element_from_nr(k);
2377 else if (stored_player[i].key[k])
2378 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2379 get_key_element_from_nr(k);
2382 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2383 getPlayerInventorySize(i);
2385 if (stored_player[i].num_white_keys > 0)
2386 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2389 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2390 stored_player[i].num_white_keys;
2395 int player_nr = game.centered_player_nr;
2397 for (k = 0; k < MAX_NUM_KEYS; k++)
2399 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2401 if (game_em.ply[player_nr]->keys & (1 << k))
2402 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2403 get_key_element_from_nr(k);
2405 else if (stored_player[player_nr].key[k])
2406 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2407 get_key_element_from_nr(k);
2410 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2411 getPlayerInventorySize(player_nr);
2413 if (stored_player[player_nr].num_white_keys > 0)
2414 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2416 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2417 stored_player[player_nr].num_white_keys;
2420 // re-arrange keys on game panel, if needed or if defined by style settings
2421 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2423 int nr = GAME_PANEL_KEY_1 + i;
2424 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2425 struct TextPosInfo *pos = gpc->pos;
2427 // skip check if key is not in the player's inventory
2428 if (gpc->value == EL_EMPTY)
2431 // check if keys should be arranged on panel from left to right
2432 if (pos->style == STYLE_LEFTMOST_POSITION)
2434 // check previous key positions (left from current key)
2435 for (k = 0; k < i; k++)
2437 int nr_new = GAME_PANEL_KEY_1 + k;
2439 if (game_panel_controls[nr_new].value == EL_EMPTY)
2441 game_panel_controls[nr_new].value = gpc->value;
2442 gpc->value = EL_EMPTY;
2449 // check if "undefined" keys can be placed at some other position
2450 if (pos->x == -1 && pos->y == -1)
2452 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2454 // 1st try: display key at the same position as normal or EM keys
2455 if (game_panel_controls[nr_new].value == EL_EMPTY)
2457 game_panel_controls[nr_new].value = gpc->value;
2461 // 2nd try: display key at the next free position in the key panel
2462 for (k = 0; k < STD_NUM_KEYS; k++)
2464 nr_new = GAME_PANEL_KEY_1 + k;
2466 if (game_panel_controls[nr_new].value == EL_EMPTY)
2468 game_panel_controls[nr_new].value = gpc->value;
2477 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2479 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2480 get_inventory_element_from_pos(local_player, i);
2481 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2482 get_inventory_element_from_pos(local_player, -i - 1);
2485 game_panel_controls[GAME_PANEL_SCORE].value = score;
2486 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2488 game_panel_controls[GAME_PANEL_TIME].value = time;
2490 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2491 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2492 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2494 if (level.time == 0)
2495 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2497 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2499 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2500 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2502 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2504 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2505 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2507 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2508 local_player->shield_normal_time_left;
2509 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2510 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2512 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2513 local_player->shield_deadly_time_left;
2515 game_panel_controls[GAME_PANEL_EXIT].value =
2516 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2518 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2519 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2520 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2521 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2522 EL_EMC_MAGIC_BALL_SWITCH);
2524 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2525 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2526 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2527 game.light_time_left;
2529 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2530 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2531 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2532 game.timegate_time_left;
2534 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2535 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2537 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2538 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2539 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2540 game.lenses_time_left;
2542 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2543 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2544 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2545 game.magnify_time_left;
2547 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2548 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2549 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2550 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2551 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2552 EL_BALLOON_SWITCH_NONE);
2554 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2555 local_player->dynabomb_count;
2556 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2557 local_player->dynabomb_size;
2558 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2559 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2561 game_panel_controls[GAME_PANEL_PENGUINS].value =
2562 game.friends_still_needed;
2564 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2565 game.sokoban_objects_still_needed;
2566 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2567 game.sokoban_fields_still_needed;
2569 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2570 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2572 for (i = 0; i < NUM_BELTS; i++)
2574 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2575 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2576 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2577 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2578 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2581 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2582 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2583 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2584 game.magic_wall_time_left;
2586 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2587 local_player->gravity;
2589 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2590 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2592 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2593 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2594 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2595 game.panel.element[i].id : EL_UNDEFINED);
2597 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2598 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2599 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2600 element_info[game.panel.element_count[i].id].element_count : 0);
2602 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2603 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2604 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2605 element_info[game.panel.ce_score[i].id].collect_score : 0);
2607 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2608 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2609 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2610 element_info[game.panel.ce_score_element[i].id].collect_score :
2613 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2614 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2615 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2617 // update game panel control frames
2619 for (i = 0; game_panel_controls[i].nr != -1; i++)
2621 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2623 if (gpc->type == TYPE_ELEMENT)
2625 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2627 int last_anim_random_frame = gfx.anim_random_frame;
2628 int element = gpc->value;
2629 int graphic = el2panelimg(element);
2630 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2632 graphic_info[graphic].anim_global_anim_sync ?
2633 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2635 if (gpc->value != gpc->last_value)
2638 gpc->gfx_random = init_gfx_random;
2644 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2645 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2646 gpc->gfx_random = init_gfx_random;
2649 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2650 gfx.anim_random_frame = gpc->gfx_random;
2652 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2653 gpc->gfx_frame = element_info[element].collect_score;
2655 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2657 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2658 gfx.anim_random_frame = last_anim_random_frame;
2661 else if (gpc->type == TYPE_GRAPHIC)
2663 if (gpc->graphic != IMG_UNDEFINED)
2665 int last_anim_random_frame = gfx.anim_random_frame;
2666 int graphic = gpc->graphic;
2667 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2669 graphic_info[graphic].anim_global_anim_sync ?
2670 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2672 if (gpc->value != gpc->last_value)
2675 gpc->gfx_random = init_gfx_random;
2681 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2682 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2683 gpc->gfx_random = init_gfx_random;
2686 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2687 gfx.anim_random_frame = gpc->gfx_random;
2689 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2691 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2692 gfx.anim_random_frame = last_anim_random_frame;
2698 static void DisplayGameControlValues(void)
2700 boolean redraw_panel = FALSE;
2703 for (i = 0; game_panel_controls[i].nr != -1; i++)
2705 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2707 if (PANEL_DEACTIVATED(gpc->pos))
2710 if (gpc->value == gpc->last_value &&
2711 gpc->frame == gpc->last_frame)
2714 redraw_panel = TRUE;
2720 // copy default game door content to main double buffer
2722 // !!! CHECK AGAIN !!!
2723 SetPanelBackground();
2724 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2725 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2727 // redraw game control buttons
2728 RedrawGameButtons();
2730 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2732 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2734 int nr = game_panel_order[i].nr;
2735 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2736 struct TextPosInfo *pos = gpc->pos;
2737 int type = gpc->type;
2738 int value = gpc->value;
2739 int frame = gpc->frame;
2740 int size = pos->size;
2741 int font = pos->font;
2742 boolean draw_masked = pos->draw_masked;
2743 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2745 if (PANEL_DEACTIVATED(pos))
2748 if (pos->class == get_hash_from_string("extra_panel_items") &&
2749 !setup.prefer_extra_panel_items)
2752 gpc->last_value = value;
2753 gpc->last_frame = frame;
2755 if (type == TYPE_INTEGER)
2757 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2758 nr == GAME_PANEL_INVENTORY_COUNT ||
2759 nr == GAME_PANEL_SCORE ||
2760 nr == GAME_PANEL_HIGHSCORE ||
2761 nr == GAME_PANEL_TIME)
2763 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2765 if (use_dynamic_size) // use dynamic number of digits
2767 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 :
2768 nr == GAME_PANEL_INVENTORY_COUNT ||
2769 nr == GAME_PANEL_TIME ? 1000 : 100000);
2770 int size_add = (nr == GAME_PANEL_LEVEL_NUMBER ||
2771 nr == GAME_PANEL_INVENTORY_COUNT ||
2772 nr == GAME_PANEL_TIME ? 1 : 2);
2773 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 :
2774 nr == GAME_PANEL_INVENTORY_COUNT ||
2775 nr == GAME_PANEL_TIME ? 3 : 5);
2776 int size2 = size1 + size_add;
2777 int font1 = pos->font;
2778 int font2 = pos->font_alt;
2780 size = (value < value_change ? size1 : size2);
2781 font = (value < value_change ? font1 : font2);
2785 // correct text size if "digits" is zero or less
2787 size = strlen(int2str(value, size));
2789 // dynamically correct text alignment
2790 pos->width = size * getFontWidth(font);
2792 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2793 int2str(value, size), font, mask_mode);
2795 else if (type == TYPE_ELEMENT)
2797 int element, graphic;
2801 int dst_x = PANEL_XPOS(pos);
2802 int dst_y = PANEL_YPOS(pos);
2804 if (value != EL_UNDEFINED && value != EL_EMPTY)
2807 graphic = el2panelimg(value);
2810 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2811 element, EL_NAME(element), size);
2814 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2817 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2820 width = graphic_info[graphic].width * size / TILESIZE;
2821 height = graphic_info[graphic].height * size / TILESIZE;
2824 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2827 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2831 else if (type == TYPE_GRAPHIC)
2833 int graphic = gpc->graphic;
2834 int graphic_active = gpc->graphic_active;
2838 int dst_x = PANEL_XPOS(pos);
2839 int dst_y = PANEL_YPOS(pos);
2840 boolean skip = (pos->class == get_hash_from_string("mm_engine_only") &&
2841 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2843 if (graphic != IMG_UNDEFINED && !skip)
2845 if (pos->style == STYLE_REVERSE)
2846 value = 100 - value;
2848 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2850 if (pos->direction & MV_HORIZONTAL)
2852 width = graphic_info[graphic_active].width * value / 100;
2853 height = graphic_info[graphic_active].height;
2855 if (pos->direction == MV_LEFT)
2857 src_x += graphic_info[graphic_active].width - width;
2858 dst_x += graphic_info[graphic_active].width - width;
2863 width = graphic_info[graphic_active].width;
2864 height = graphic_info[graphic_active].height * value / 100;
2866 if (pos->direction == MV_UP)
2868 src_y += graphic_info[graphic_active].height - height;
2869 dst_y += graphic_info[graphic_active].height - height;
2874 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2877 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2880 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2882 if (pos->direction & MV_HORIZONTAL)
2884 if (pos->direction == MV_RIGHT)
2891 dst_x = PANEL_XPOS(pos);
2894 width = graphic_info[graphic].width - width;
2898 if (pos->direction == MV_DOWN)
2905 dst_y = PANEL_YPOS(pos);
2908 height = graphic_info[graphic].height - height;
2912 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2915 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2919 else if (type == TYPE_STRING)
2921 boolean active = (value != 0);
2922 char *state_normal = "off";
2923 char *state_active = "on";
2924 char *state = (active ? state_active : state_normal);
2925 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2926 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2927 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2928 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2930 if (nr == GAME_PANEL_GRAVITY_STATE)
2932 int font1 = pos->font; // (used for normal state)
2933 int font2 = pos->font_alt; // (used for active state)
2935 font = (active ? font2 : font1);
2944 // don't truncate output if "chars" is zero or less
2947 // dynamically correct text alignment
2948 pos->width = size * getFontWidth(font);
2951 s_cut = getStringCopyN(s, size);
2953 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2954 s_cut, font, mask_mode);
2960 redraw_mask |= REDRAW_DOOR_1;
2963 SetGameStatus(GAME_MODE_PLAYING);
2966 void UpdateAndDisplayGameControlValues(void)
2968 if (tape.deactivate_display)
2971 UpdateGameControlValues();
2972 DisplayGameControlValues();
2975 void UpdateGameDoorValues(void)
2977 UpdateGameControlValues();
2980 void DrawGameDoorValues(void)
2982 DisplayGameControlValues();
2986 // ============================================================================
2988 // ----------------------------------------------------------------------------
2989 // initialize game engine due to level / tape version number
2990 // ============================================================================
2992 static void InitGameEngine(void)
2994 int i, j, k, l, x, y;
2996 // set game engine from tape file when re-playing, else from level file
2997 game.engine_version = (tape.playing ? tape.engine_version :
2998 level.game_version);
3000 // set single or multi-player game mode (needed for re-playing tapes)
3001 game.team_mode = setup.team_mode;
3005 int num_players = 0;
3007 for (i = 0; i < MAX_PLAYERS; i++)
3008 if (tape.player_participates[i])
3011 // multi-player tapes contain input data for more than one player
3012 game.team_mode = (num_players > 1);
3016 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
3017 level.game_version);
3018 Debug("game:init:level", " tape.file_version == %06d",
3020 Debug("game:init:level", " tape.game_version == %06d",
3022 Debug("game:init:level", " tape.engine_version == %06d",
3023 tape.engine_version);
3024 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
3025 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
3028 // --------------------------------------------------------------------------
3029 // set flags for bugs and changes according to active game engine version
3030 // --------------------------------------------------------------------------
3034 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
3036 Bug was introduced in version:
3039 Bug was fixed in version:
3043 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
3044 but the property "can fall" was missing, which caused some levels to be
3045 unsolvable. This was fixed in version 4.2.0.0.
3047 Affected levels/tapes:
3048 An example for a tape that was fixed by this bugfix is tape 029 from the
3049 level set "rnd_sam_bateman".
3050 The wrong behaviour will still be used for all levels or tapes that were
3051 created/recorded with it. An example for this is tape 023 from the level
3052 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
3055 boolean use_amoeba_dropping_cannot_fall_bug =
3056 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
3057 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
3059 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3060 tape.game_version < VERSION_IDENT(4,2,0,0)));
3063 Summary of bugfix/change:
3064 Fixed move speed of elements entering or leaving magic wall.
3066 Fixed/changed in version:
3070 Before 2.0.1, move speed of elements entering or leaving magic wall was
3071 twice as fast as it is now.
3072 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
3074 Affected levels/tapes:
3075 The first condition is generally needed for all levels/tapes before version
3076 2.0.1, which might use the old behaviour before it was changed; known tapes
3077 that are affected: Tape 014 from the level set "rnd_conor_mancone".
3078 The second condition is an exception from the above case and is needed for
3079 the special case of tapes recorded with game (not engine!) version 2.0.1 or
3080 above, but before it was known that this change would break tapes like the
3081 above and was fixed in 4.2.0.0, so that the changed behaviour was active
3082 although the engine version while recording maybe was before 2.0.1. There
3083 are a lot of tapes that are affected by this exception, like tape 006 from
3084 the level set "rnd_conor_mancone".
3087 boolean use_old_move_stepsize_for_magic_wall =
3088 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
3090 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3091 tape.game_version < VERSION_IDENT(4,2,0,0)));
3094 Summary of bugfix/change:
3095 Fixed handling for custom elements that change when pushed by the player.
3097 Fixed/changed in version:
3101 Before 3.1.0, custom elements that "change when pushing" changed directly
3102 after the player started pushing them (until then handled in "DigField()").
3103 Since 3.1.0, these custom elements are not changed until the "pushing"
3104 move of the element is finished (now handled in "ContinueMoving()").
3106 Affected levels/tapes:
3107 The first condition is generally needed for all levels/tapes before version
3108 3.1.0, which might use the old behaviour before it was changed; known tapes
3109 that are affected are some tapes from the level set "Walpurgis Gardens" by
3111 The second condition is an exception from the above case and is needed for
3112 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3113 above (including some development versions of 3.1.0), but before it was
3114 known that this change would break tapes like the above and was fixed in
3115 3.1.1, so that the changed behaviour was active although the engine version
3116 while recording maybe was before 3.1.0. There is at least one tape that is
3117 affected by this exception, which is the tape for the one-level set "Bug
3118 Machine" by Juergen Bonhagen.
3121 game.use_change_when_pushing_bug =
3122 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3124 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3125 tape.game_version < VERSION_IDENT(3,1,1,0)));
3128 Summary of bugfix/change:
3129 Fixed handling for blocking the field the player leaves when moving.
3131 Fixed/changed in version:
3135 Before 3.1.1, when "block last field when moving" was enabled, the field
3136 the player is leaving when moving was blocked for the time of the move,
3137 and was directly unblocked afterwards. This resulted in the last field
3138 being blocked for exactly one less than the number of frames of one player
3139 move. Additionally, even when blocking was disabled, the last field was
3140 blocked for exactly one frame.
3141 Since 3.1.1, due to changes in player movement handling, the last field
3142 is not blocked at all when blocking is disabled. When blocking is enabled,
3143 the last field is blocked for exactly the number of frames of one player
3144 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3145 last field is blocked for exactly one more than the number of frames of
3148 Affected levels/tapes:
3149 (!!! yet to be determined -- probably many !!!)
3152 game.use_block_last_field_bug =
3153 (game.engine_version < VERSION_IDENT(3,1,1,0));
3155 /* various special flags and settings for native Emerald Mine game engine */
3157 game_em.use_single_button =
3158 (game.engine_version > VERSION_IDENT(4,0,0,2));
3160 game_em.use_push_delay =
3161 (game.engine_version > VERSION_IDENT(4,3,7,1));
3163 game_em.use_snap_key_bug =
3164 (game.engine_version < VERSION_IDENT(4,0,1,0));
3166 game_em.use_random_bug =
3167 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3169 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3171 game_em.use_old_explosions = use_old_em_engine;
3172 game_em.use_old_android = use_old_em_engine;
3173 game_em.use_old_push_elements = use_old_em_engine;
3174 game_em.use_old_push_into_acid = use_old_em_engine;
3176 game_em.use_wrap_around = !use_old_em_engine;
3178 // --------------------------------------------------------------------------
3180 // set maximal allowed number of custom element changes per game frame
3181 game.max_num_changes_per_frame = 1;
3183 // default scan direction: scan playfield from top/left to bottom/right
3184 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3186 // dynamically adjust element properties according to game engine version
3187 InitElementPropertiesEngine(game.engine_version);
3189 // ---------- initialize special element properties -------------------------
3191 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3192 if (use_amoeba_dropping_cannot_fall_bug)
3193 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3195 // ---------- initialize player's initial move delay ------------------------
3197 // dynamically adjust player properties according to level information
3198 for (i = 0; i < MAX_PLAYERS; i++)
3199 game.initial_move_delay_value[i] =
3200 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3202 // dynamically adjust player properties according to game engine version
3203 for (i = 0; i < MAX_PLAYERS; i++)
3204 game.initial_move_delay[i] =
3205 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3206 game.initial_move_delay_value[i] : 0);
3208 // ---------- initialize player's initial push delay ------------------------
3210 // dynamically adjust player properties according to game engine version
3211 game.initial_push_delay_value =
3212 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3214 // ---------- initialize changing elements ----------------------------------
3216 // initialize changing elements information
3217 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3219 struct ElementInfo *ei = &element_info[i];
3221 // this pointer might have been changed in the level editor
3222 ei->change = &ei->change_page[0];
3224 if (!IS_CUSTOM_ELEMENT(i))
3226 ei->change->target_element = EL_EMPTY_SPACE;
3227 ei->change->delay_fixed = 0;
3228 ei->change->delay_random = 0;
3229 ei->change->delay_frames = 1;
3232 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3234 ei->has_change_event[j] = FALSE;
3236 ei->event_page_nr[j] = 0;
3237 ei->event_page[j] = &ei->change_page[0];
3241 // add changing elements from pre-defined list
3242 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3244 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3245 struct ElementInfo *ei = &element_info[ch_delay->element];
3247 ei->change->target_element = ch_delay->target_element;
3248 ei->change->delay_fixed = ch_delay->change_delay;
3250 ei->change->pre_change_function = ch_delay->pre_change_function;
3251 ei->change->change_function = ch_delay->change_function;
3252 ei->change->post_change_function = ch_delay->post_change_function;
3254 ei->change->can_change = TRUE;
3255 ei->change->can_change_or_has_action = TRUE;
3257 ei->has_change_event[CE_DELAY] = TRUE;
3259 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3260 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3263 // ---------- initialize if element can trigger global animations -----------
3265 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3267 struct ElementInfo *ei = &element_info[i];
3269 ei->has_anim_event = FALSE;
3272 InitGlobalAnimEventsForCustomElements();
3274 // ---------- initialize internal run-time variables ------------------------
3276 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3278 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3280 for (j = 0; j < ei->num_change_pages; j++)
3282 ei->change_page[j].can_change_or_has_action =
3283 (ei->change_page[j].can_change |
3284 ei->change_page[j].has_action);
3288 // add change events from custom element configuration
3289 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3291 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3293 for (j = 0; j < ei->num_change_pages; j++)
3295 if (!ei->change_page[j].can_change_or_has_action)
3298 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3300 // only add event page for the first page found with this event
3301 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3303 ei->has_change_event[k] = TRUE;
3305 ei->event_page_nr[k] = j;
3306 ei->event_page[k] = &ei->change_page[j];
3312 // ---------- initialize reference elements in change conditions ------------
3314 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3316 int element = EL_CUSTOM_START + i;
3317 struct ElementInfo *ei = &element_info[element];
3319 for (j = 0; j < ei->num_change_pages; j++)
3321 int trigger_element = ei->change_page[j].initial_trigger_element;
3323 if (trigger_element >= EL_PREV_CE_8 &&
3324 trigger_element <= EL_NEXT_CE_8)
3325 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3327 ei->change_page[j].trigger_element = trigger_element;
3331 // ---------- initialize run-time trigger player and element ----------------
3333 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3335 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3337 for (j = 0; j < ei->num_change_pages; j++)
3339 struct ElementChangeInfo *change = &ei->change_page[j];
3341 change->actual_trigger_element = EL_EMPTY;
3342 change->actual_trigger_player = EL_EMPTY;
3343 change->actual_trigger_player_bits = CH_PLAYER_NONE;
3344 change->actual_trigger_side = CH_SIDE_NONE;
3345 change->actual_trigger_ce_value = 0;
3346 change->actual_trigger_ce_score = 0;
3347 change->actual_trigger_x = -1;
3348 change->actual_trigger_y = -1;
3352 // ---------- initialize trigger events -------------------------------------
3354 // initialize trigger events information
3355 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3356 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3357 trigger_events[i][j] = FALSE;
3359 // add trigger events from element change event properties
3360 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3362 struct ElementInfo *ei = &element_info[i];
3364 for (j = 0; j < ei->num_change_pages; j++)
3366 struct ElementChangeInfo *change = &ei->change_page[j];
3368 if (!change->can_change_or_has_action)
3371 if (change->has_event[CE_BY_OTHER_ACTION])
3373 int trigger_element = change->trigger_element;
3375 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3377 if (change->has_event[k])
3379 if (IS_GROUP_ELEMENT(trigger_element))
3381 struct ElementGroupInfo *group =
3382 element_info[trigger_element].group;
3384 for (l = 0; l < group->num_elements_resolved; l++)
3385 trigger_events[group->element_resolved[l]][k] = TRUE;
3387 else if (trigger_element == EL_ANY_ELEMENT)
3388 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3389 trigger_events[l][k] = TRUE;
3391 trigger_events[trigger_element][k] = TRUE;
3398 // ---------- initialize push delay -----------------------------------------
3400 // initialize push delay values to default
3401 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3403 if (!IS_CUSTOM_ELEMENT(i))
3405 // set default push delay values (corrected since version 3.0.7-1)
3406 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3408 element_info[i].push_delay_fixed = 2;
3409 element_info[i].push_delay_random = 8;
3413 element_info[i].push_delay_fixed = 8;
3414 element_info[i].push_delay_random = 8;
3419 // set push delay value for certain elements from pre-defined list
3420 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3422 int e = push_delay_list[i].element;
3424 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3425 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3428 // set push delay value for Supaplex elements for newer engine versions
3429 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3431 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3433 if (IS_SP_ELEMENT(i))
3435 // set SP push delay to just enough to push under a falling zonk
3436 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3438 element_info[i].push_delay_fixed = delay;
3439 element_info[i].push_delay_random = 0;
3444 // ---------- initialize move stepsize --------------------------------------
3446 // initialize move stepsize values to default
3447 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3448 if (!IS_CUSTOM_ELEMENT(i))
3449 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3451 // set move stepsize value for certain elements from pre-defined list
3452 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3454 int e = move_stepsize_list[i].element;
3456 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3458 // set move stepsize value for certain elements for older engine versions
3459 if (use_old_move_stepsize_for_magic_wall)
3461 if (e == EL_MAGIC_WALL_FILLING ||
3462 e == EL_MAGIC_WALL_EMPTYING ||
3463 e == EL_BD_MAGIC_WALL_FILLING ||
3464 e == EL_BD_MAGIC_WALL_EMPTYING)
3465 element_info[e].move_stepsize *= 2;
3469 // ---------- initialize collect score --------------------------------------
3471 // initialize collect score values for custom elements from initial value
3472 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3473 if (IS_CUSTOM_ELEMENT(i))
3474 element_info[i].collect_score = element_info[i].collect_score_initial;
3476 // ---------- initialize collect count --------------------------------------
3478 // initialize collect count values for non-custom elements
3479 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3480 if (!IS_CUSTOM_ELEMENT(i))
3481 element_info[i].collect_count_initial = 0;
3483 // add collect count values for all elements from pre-defined list
3484 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3485 element_info[collect_count_list[i].element].collect_count_initial =
3486 collect_count_list[i].count;
3488 // ---------- initialize access direction -----------------------------------
3490 // initialize access direction values to default (access from every side)
3491 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3492 if (!IS_CUSTOM_ELEMENT(i))
3493 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3495 // set access direction value for certain elements from pre-defined list
3496 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3497 element_info[access_direction_list[i].element].access_direction =
3498 access_direction_list[i].direction;
3500 // ---------- initialize explosion content ----------------------------------
3501 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3503 if (IS_CUSTOM_ELEMENT(i))
3506 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3508 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3510 element_info[i].content.e[x][y] =
3511 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3512 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3513 i == EL_PLAYER_3 ? EL_EMERALD :
3514 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3515 i == EL_MOLE ? EL_EMERALD_RED :
3516 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3517 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3518 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3519 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3520 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3521 i == EL_WALL_EMERALD ? EL_EMERALD :
3522 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3523 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3524 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3525 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3526 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3527 i == EL_WALL_PEARL ? EL_PEARL :
3528 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3533 // ---------- initialize recursion detection --------------------------------
3534 recursion_loop_depth = 0;
3535 recursion_loop_detected = FALSE;
3536 recursion_loop_element = EL_UNDEFINED;
3538 // ---------- initialize graphics engine ------------------------------------
3539 game.scroll_delay_value =
3540 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3541 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3542 !setup.forced_scroll_delay ? 0 :
3543 setup.scroll_delay ? setup.scroll_delay_value : 0);
3544 if (game.forced_scroll_delay_value == -1)
3545 game.scroll_delay_value =
3546 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3548 // ---------- initialize game engine snapshots ------------------------------
3549 for (i = 0; i < MAX_PLAYERS; i++)
3550 game.snapshot.last_action[i] = 0;
3551 game.snapshot.changed_action = FALSE;
3552 game.snapshot.collected_item = FALSE;
3553 game.snapshot.mode =
3554 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3555 SNAPSHOT_MODE_EVERY_STEP :
3556 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3557 SNAPSHOT_MODE_EVERY_MOVE :
3558 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3559 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3560 game.snapshot.save_snapshot = FALSE;
3562 // ---------- initialize level time for Supaplex engine ---------------------
3563 // Supaplex levels with time limit currently unsupported -- should be added
3564 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3567 // ---------- initialize flags for handling game actions --------------------
3569 // set flags for game actions to default values
3570 game.use_key_actions = TRUE;
3571 game.use_mouse_actions = FALSE;
3573 // when using Mirror Magic game engine, handle mouse events only
3574 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3576 game.use_key_actions = FALSE;
3577 game.use_mouse_actions = TRUE;
3580 // check for custom elements with mouse click events
3581 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3583 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3585 int element = EL_CUSTOM_START + i;
3587 if (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3588 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3589 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3590 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3591 game.use_mouse_actions = TRUE;
3596 static int get_num_special_action(int element, int action_first,
3599 int num_special_action = 0;
3602 for (i = action_first; i <= action_last; i++)
3604 boolean found = FALSE;
3606 for (j = 0; j < NUM_DIRECTIONS; j++)
3607 if (el_act_dir2img(element, i, j) !=
3608 el_act_dir2img(element, ACTION_DEFAULT, j))
3612 num_special_action++;
3617 return num_special_action;
3621 // ============================================================================
3623 // ----------------------------------------------------------------------------
3624 // initialize and start new game
3625 // ============================================================================
3627 #if DEBUG_INIT_PLAYER
3628 static void DebugPrintPlayerStatus(char *message)
3635 Debug("game:init:player", "%s:", message);
3637 for (i = 0; i < MAX_PLAYERS; i++)
3639 struct PlayerInfo *player = &stored_player[i];
3641 Debug("game:init:player",
3642 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3646 player->connected_locally,
3647 player->connected_network,
3649 (local_player == player ? " (local player)" : ""));
3656 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3657 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3658 int fade_mask = REDRAW_FIELD;
3659 boolean restarting = (game_status == GAME_MODE_PLAYING);
3660 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3661 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3662 int initial_move_dir = MV_DOWN;
3665 // required here to update video display before fading (FIX THIS)
3666 DrawMaskedBorder(REDRAW_DOOR_2);
3668 if (!game.restart_level)
3669 CloseDoor(DOOR_CLOSE_1);
3673 // force fading out global animations displayed during game play
3674 SetGameStatus(GAME_MODE_PSEUDO_RESTARTING);
3678 SetGameStatus(GAME_MODE_PLAYING);
3680 // do not cover screen before fading out when starting from main menu
3681 game_bd.cover_screen = FALSE;
3684 if (level_editor_test_game)
3685 FadeSkipNextFadeOut();
3687 FadeSetEnterScreen();
3690 fade_mask = REDRAW_ALL;
3692 FadeLevelSoundsAndMusic();
3694 ExpireSoundLoops(TRUE);
3700 // force restarting global animations displayed during game play
3701 RestartGlobalAnimsByStatus(GAME_MODE_PSEUDO_RESTARTING);
3703 // this is required for "transforming" fade modes like cross-fading
3704 // (else global animations will be stopped, but not restarted here)
3705 SetAnimStatusBeforeFading(GAME_MODE_PSEUDO_RESTARTING);
3707 SetGameStatus(GAME_MODE_PLAYING);
3710 if (level_editor_test_game)
3711 FadeSkipNextFadeIn();
3713 // needed if different viewport properties defined for playing
3714 ChangeViewportPropertiesIfNeeded();
3718 DrawCompleteVideoDisplay();
3720 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3723 InitGameControlValues();
3727 // initialize tape actions from game when recording tape
3728 tape.use_key_actions = game.use_key_actions;
3729 tape.use_mouse_actions = game.use_mouse_actions;
3731 // initialize visible playfield size when recording tape (for team mode)
3732 tape.scr_fieldx = SCR_FIELDX;
3733 tape.scr_fieldy = SCR_FIELDY;
3736 // don't play tapes over network
3737 network_playing = (network.enabled && !tape.playing);
3739 for (i = 0; i < MAX_PLAYERS; i++)
3741 struct PlayerInfo *player = &stored_player[i];
3743 player->index_nr = i;
3744 player->index_bit = (1 << i);
3745 player->element_nr = EL_PLAYER_1 + i;
3747 player->present = FALSE;
3748 player->active = FALSE;
3749 player->mapped = FALSE;
3751 player->killed = FALSE;
3752 player->reanimated = FALSE;
3753 player->buried = FALSE;
3756 player->effective_action = 0;
3757 player->programmed_action = 0;
3758 player->snap_action = 0;
3760 player->mouse_action.lx = 0;
3761 player->mouse_action.ly = 0;
3762 player->mouse_action.button = 0;
3763 player->mouse_action.button_hint = 0;
3765 player->effective_mouse_action.lx = 0;
3766 player->effective_mouse_action.ly = 0;
3767 player->effective_mouse_action.button = 0;
3768 player->effective_mouse_action.button_hint = 0;
3770 for (j = 0; j < MAX_NUM_KEYS; j++)
3771 player->key[j] = FALSE;
3773 player->num_white_keys = 0;
3775 player->dynabomb_count = 0;
3776 player->dynabomb_size = 1;
3777 player->dynabombs_left = 0;
3778 player->dynabomb_xl = FALSE;
3780 player->MovDir = initial_move_dir;
3783 player->GfxDir = initial_move_dir;
3784 player->GfxAction = ACTION_DEFAULT;
3786 player->StepFrame = 0;
3788 player->initial_element = player->element_nr;
3789 player->artwork_element =
3790 (level.use_artwork_element[i] ? level.artwork_element[i] :
3791 player->element_nr);
3792 player->use_murphy = FALSE;
3794 player->block_last_field = FALSE; // initialized in InitPlayerField()
3795 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3797 player->gravity = level.initial_player_gravity[i];
3799 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3801 player->actual_frame_counter.count = 0;
3802 player->actual_frame_counter.value = 1;
3804 player->step_counter = 0;
3806 player->last_move_dir = initial_move_dir;
3808 player->is_active = FALSE;
3810 player->is_waiting = FALSE;
3811 player->is_moving = FALSE;
3812 player->is_auto_moving = FALSE;
3813 player->is_digging = FALSE;
3814 player->is_snapping = FALSE;
3815 player->is_collecting = FALSE;
3816 player->is_pushing = FALSE;
3817 player->is_switching = FALSE;
3818 player->is_dropping = FALSE;
3819 player->is_dropping_pressed = FALSE;
3821 player->is_bored = FALSE;
3822 player->is_sleeping = FALSE;
3824 player->was_waiting = TRUE;
3825 player->was_moving = FALSE;
3826 player->was_snapping = FALSE;
3827 player->was_dropping = FALSE;
3829 player->force_dropping = FALSE;
3831 player->frame_counter_bored = -1;
3832 player->frame_counter_sleeping = -1;
3834 player->anim_delay_counter = 0;
3835 player->post_delay_counter = 0;
3837 player->dir_waiting = initial_move_dir;
3838 player->action_waiting = ACTION_DEFAULT;
3839 player->last_action_waiting = ACTION_DEFAULT;
3840 player->special_action_bored = ACTION_DEFAULT;
3841 player->special_action_sleeping = ACTION_DEFAULT;
3843 player->switch_x = -1;
3844 player->switch_y = -1;
3846 player->drop_x = -1;
3847 player->drop_y = -1;
3849 player->show_envelope = 0;
3851 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3853 player->push_delay = -1; // initialized when pushing starts
3854 player->push_delay_value = game.initial_push_delay_value;
3856 player->drop_delay = 0;
3857 player->drop_pressed_delay = 0;
3859 player->last_jx = -1;
3860 player->last_jy = -1;
3864 player->shield_normal_time_left = 0;
3865 player->shield_deadly_time_left = 0;
3867 player->last_removed_element = EL_UNDEFINED;
3869 player->inventory_infinite_element = EL_UNDEFINED;
3870 player->inventory_size = 0;
3872 if (level.use_initial_inventory[i])
3874 for (j = 0; j < level.initial_inventory_size[i]; j++)
3876 int element = level.initial_inventory_content[i][j];
3877 int collect_count = element_info[element].collect_count_initial;
3880 if (!IS_CUSTOM_ELEMENT(element))
3883 if (collect_count == 0)
3884 player->inventory_infinite_element = element;
3886 for (k = 0; k < collect_count; k++)
3887 if (player->inventory_size < MAX_INVENTORY_SIZE)
3888 player->inventory_element[player->inventory_size++] = element;
3892 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3893 SnapField(player, 0, 0);
3895 map_player_action[i] = i;
3898 network_player_action_received = FALSE;
3900 // initial null action
3901 if (network_playing)
3902 SendToServer_MovePlayer(MV_NONE);
3907 TimeLeft = level.time;
3912 ScreenMovDir = MV_NONE;
3916 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3918 game.robot_wheel_x = -1;
3919 game.robot_wheel_y = -1;
3924 game.all_players_gone = FALSE;
3926 game.LevelSolved = FALSE;
3927 game.GameOver = FALSE;
3929 game.GamePlayed = !tape.playing;
3931 game.LevelSolved_GameWon = FALSE;
3932 game.LevelSolved_GameEnd = FALSE;
3933 game.LevelSolved_SaveTape = FALSE;
3934 game.LevelSolved_SaveScore = FALSE;
3936 game.LevelSolved_CountingTime = 0;
3937 game.LevelSolved_CountingScore = 0;
3938 game.LevelSolved_CountingHealth = 0;
3940 game.RestartGameRequested = FALSE;
3942 game.panel.active = TRUE;
3944 game.no_level_time_limit = (level.time == 0);
3945 game.time_limit = (leveldir_current->time_limit && setup.time_limit);
3947 game.yamyam_content_nr = 0;
3948 game.robot_wheel_active = FALSE;
3949 game.magic_wall_active = FALSE;
3950 game.magic_wall_time_left = 0;
3951 game.light_time_left = 0;
3952 game.timegate_time_left = 0;
3953 game.switchgate_pos = 0;
3954 game.wind_direction = level.wind_direction_initial;
3956 game.time_final = 0;
3957 game.score_time_final = 0;
3960 game.score_final = 0;
3962 game.health = MAX_HEALTH;
3963 game.health_final = MAX_HEALTH;
3965 game.gems_still_needed = level.gems_needed;
3966 game.sokoban_fields_still_needed = 0;
3967 game.sokoban_objects_still_needed = 0;
3968 game.lights_still_needed = 0;
3969 game.players_still_needed = 0;
3970 game.friends_still_needed = 0;
3972 game.lenses_time_left = 0;
3973 game.magnify_time_left = 0;
3975 game.ball_active = level.ball_active_initial;
3976 game.ball_content_nr = 0;
3978 game.explosions_delayed = TRUE;
3980 // special case: set custom artwork setting to initial value
3981 game.use_masked_elements = game.use_masked_elements_initial;
3983 for (i = 0; i < NUM_BELTS; i++)
3985 game.belt_dir[i] = MV_NONE;
3986 game.belt_dir_nr[i] = 3; // not moving, next moving left
3989 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3990 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3992 #if DEBUG_INIT_PLAYER
3993 DebugPrintPlayerStatus("Player status at level initialization");
3996 SCAN_PLAYFIELD(x, y)
3998 Tile[x][y] = Last[x][y] = level.field[x][y];
3999 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
4000 ChangeDelay[x][y] = 0;
4001 ChangePage[x][y] = -1;
4002 CustomValue[x][y] = 0; // initialized in InitField()
4003 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
4005 WasJustMoving[x][y] = 0;
4006 WasJustFalling[x][y] = 0;
4007 CheckCollision[x][y] = 0;
4008 CheckImpact[x][y] = 0;
4010 Pushed[x][y] = FALSE;
4012 ChangeCount[x][y] = 0;
4013 ChangeEvent[x][y] = -1;
4015 ExplodePhase[x][y] = 0;
4016 ExplodeDelay[x][y] = 0;
4017 ExplodeField[x][y] = EX_TYPE_NONE;
4019 RunnerVisit[x][y] = 0;
4020 PlayerVisit[x][y] = 0;
4023 GfxRandom[x][y] = INIT_GFX_RANDOM();
4024 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
4025 GfxElement[x][y] = EL_UNDEFINED;
4026 GfxElementEmpty[x][y] = EL_EMPTY;
4027 GfxAction[x][y] = ACTION_DEFAULT;
4028 GfxDir[x][y] = MV_NONE;
4029 GfxRedraw[x][y] = GFX_REDRAW_NONE;
4032 SCAN_PLAYFIELD(x, y)
4034 InitFieldForEngine(x, y);
4036 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
4038 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
4041 InitField(x, y, TRUE);
4043 ResetGfxAnimation(x, y);
4048 // required if level does not contain any "empty space" element
4049 if (element_info[EL_EMPTY].use_gfx_element)
4050 game.use_masked_elements = TRUE;
4052 for (i = 0; i < MAX_PLAYERS; i++)
4054 struct PlayerInfo *player = &stored_player[i];
4056 // set number of special actions for bored and sleeping animation
4057 player->num_special_action_bored =
4058 get_num_special_action(player->artwork_element,
4059 ACTION_BORING_1, ACTION_BORING_LAST);
4060 player->num_special_action_sleeping =
4061 get_num_special_action(player->artwork_element,
4062 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
4065 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
4066 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
4068 // initialize type of slippery elements
4069 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4071 if (!IS_CUSTOM_ELEMENT(i))
4073 // default: elements slip down either to the left or right randomly
4074 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
4076 // SP style elements prefer to slip down on the left side
4077 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
4078 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4080 // BD style elements prefer to slip down on the left side
4081 if (game.emulation == EMU_BOULDERDASH)
4082 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4086 // initialize explosion and ignition delay
4087 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4089 if (!IS_CUSTOM_ELEMENT(i))
4092 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
4093 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
4094 game.emulation == EMU_SUPAPLEX ? 3 : 2);
4095 int last_phase = (num_phase + 1) * delay;
4096 int half_phase = (num_phase / 2) * delay;
4098 element_info[i].explosion_delay = last_phase - 1;
4099 element_info[i].ignition_delay = half_phase;
4101 if (i == EL_BLACK_ORB)
4102 element_info[i].ignition_delay = 1;
4106 // correct non-moving belts to start moving left
4107 for (i = 0; i < NUM_BELTS; i++)
4108 if (game.belt_dir[i] == MV_NONE)
4109 game.belt_dir_nr[i] = 3; // not moving, next moving left
4111 #if USE_NEW_PLAYER_ASSIGNMENTS
4112 // use preferred player also in local single-player mode
4113 if (!network.enabled && !game.team_mode)
4115 int new_index_nr = setup.network_player_nr;
4117 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
4119 for (i = 0; i < MAX_PLAYERS; i++)
4120 stored_player[i].connected_locally = FALSE;
4122 stored_player[new_index_nr].connected_locally = TRUE;
4126 for (i = 0; i < MAX_PLAYERS; i++)
4128 stored_player[i].connected = FALSE;
4130 // in network game mode, the local player might not be the first player
4131 if (stored_player[i].connected_locally)
4132 local_player = &stored_player[i];
4135 if (!network.enabled)
4136 local_player->connected = TRUE;
4140 for (i = 0; i < MAX_PLAYERS; i++)
4141 stored_player[i].connected = tape.player_participates[i];
4143 else if (network.enabled)
4145 // add team mode players connected over the network (needed for correct
4146 // assignment of player figures from level to locally playing players)
4148 for (i = 0; i < MAX_PLAYERS; i++)
4149 if (stored_player[i].connected_network)
4150 stored_player[i].connected = TRUE;
4152 else if (game.team_mode)
4154 // try to guess locally connected team mode players (needed for correct
4155 // assignment of player figures from level to locally playing players)
4157 for (i = 0; i < MAX_PLAYERS; i++)
4158 if (setup.input[i].use_joystick ||
4159 setup.input[i].key.left != KSYM_UNDEFINED)
4160 stored_player[i].connected = TRUE;
4163 #if DEBUG_INIT_PLAYER
4164 DebugPrintPlayerStatus("Player status after level initialization");
4167 #if DEBUG_INIT_PLAYER
4168 Debug("game:init:player", "Reassigning players ...");
4171 // check if any connected player was not found in playfield
4172 for (i = 0; i < MAX_PLAYERS; i++)
4174 struct PlayerInfo *player = &stored_player[i];
4176 if (player->connected && !player->present)
4178 struct PlayerInfo *field_player = NULL;
4180 #if DEBUG_INIT_PLAYER
4181 Debug("game:init:player",
4182 "- looking for field player for player %d ...", i + 1);
4185 // assign first free player found that is present in the playfield
4187 // first try: look for unmapped playfield player that is not connected
4188 for (j = 0; j < MAX_PLAYERS; j++)
4189 if (field_player == NULL &&
4190 stored_player[j].present &&
4191 !stored_player[j].mapped &&
4192 !stored_player[j].connected)
4193 field_player = &stored_player[j];
4195 // second try: look for *any* unmapped playfield player
4196 for (j = 0; j < MAX_PLAYERS; j++)
4197 if (field_player == NULL &&
4198 stored_player[j].present &&
4199 !stored_player[j].mapped)
4200 field_player = &stored_player[j];
4202 if (field_player != NULL)
4204 int jx = field_player->jx, jy = field_player->jy;
4206 #if DEBUG_INIT_PLAYER
4207 Debug("game:init:player", "- found player %d",
4208 field_player->index_nr + 1);
4211 player->present = FALSE;
4212 player->active = FALSE;
4214 field_player->present = TRUE;
4215 field_player->active = TRUE;
4218 player->initial_element = field_player->initial_element;
4219 player->artwork_element = field_player->artwork_element;
4221 player->block_last_field = field_player->block_last_field;
4222 player->block_delay_adjustment = field_player->block_delay_adjustment;
4225 StorePlayer[jx][jy] = field_player->element_nr;
4227 field_player->jx = field_player->last_jx = jx;
4228 field_player->jy = field_player->last_jy = jy;
4230 if (local_player == player)
4231 local_player = field_player;
4233 map_player_action[field_player->index_nr] = i;
4235 field_player->mapped = TRUE;
4237 #if DEBUG_INIT_PLAYER
4238 Debug("game:init:player", "- map_player_action[%d] == %d",
4239 field_player->index_nr + 1, i + 1);
4244 if (player->connected && player->present)
4245 player->mapped = TRUE;
4248 #if DEBUG_INIT_PLAYER
4249 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4254 // check if any connected player was not found in playfield
4255 for (i = 0; i < MAX_PLAYERS; i++)
4257 struct PlayerInfo *player = &stored_player[i];
4259 if (player->connected && !player->present)
4261 for (j = 0; j < MAX_PLAYERS; j++)
4263 struct PlayerInfo *field_player = &stored_player[j];
4264 int jx = field_player->jx, jy = field_player->jy;
4266 // assign first free player found that is present in the playfield
4267 if (field_player->present && !field_player->connected)
4269 player->present = TRUE;
4270 player->active = TRUE;
4272 field_player->present = FALSE;
4273 field_player->active = FALSE;
4275 player->initial_element = field_player->initial_element;
4276 player->artwork_element = field_player->artwork_element;
4278 player->block_last_field = field_player->block_last_field;
4279 player->block_delay_adjustment = field_player->block_delay_adjustment;
4281 StorePlayer[jx][jy] = player->element_nr;
4283 player->jx = player->last_jx = jx;
4284 player->jy = player->last_jy = jy;
4294 Debug("game:init:player", "local_player->present == %d",
4295 local_player->present);
4298 // set focus to local player for network games, else to all players
4299 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4300 game.centered_player_nr_next = game.centered_player_nr;
4301 game.set_centered_player = FALSE;
4302 game.set_centered_player_wrap = FALSE;
4304 if (network_playing && tape.recording)
4306 // store client dependent player focus when recording network games
4307 tape.centered_player_nr_next = game.centered_player_nr_next;
4308 tape.set_centered_player = TRUE;
4313 // when playing a tape, eliminate all players who do not participate
4315 #if USE_NEW_PLAYER_ASSIGNMENTS
4317 if (!game.team_mode)
4319 for (i = 0; i < MAX_PLAYERS; i++)
4321 if (stored_player[i].active &&
4322 !tape.player_participates[map_player_action[i]])
4324 struct PlayerInfo *player = &stored_player[i];
4325 int jx = player->jx, jy = player->jy;
4327 #if DEBUG_INIT_PLAYER
4328 Debug("game:init:player", "Removing player %d at (%d, %d)",
4332 player->active = FALSE;
4333 StorePlayer[jx][jy] = 0;
4334 Tile[jx][jy] = EL_EMPTY;
4341 for (i = 0; i < MAX_PLAYERS; i++)
4343 if (stored_player[i].active &&
4344 !tape.player_participates[i])
4346 struct PlayerInfo *player = &stored_player[i];
4347 int jx = player->jx, jy = player->jy;
4349 player->active = FALSE;
4350 StorePlayer[jx][jy] = 0;
4351 Tile[jx][jy] = EL_EMPTY;
4356 else if (!network.enabled && !game.team_mode) // && !tape.playing
4358 // when in single player mode, eliminate all but the local player
4360 for (i = 0; i < MAX_PLAYERS; i++)
4362 struct PlayerInfo *player = &stored_player[i];
4364 if (player->active && player != local_player)
4366 int jx = player->jx, jy = player->jy;
4368 player->active = FALSE;
4369 player->present = FALSE;
4371 StorePlayer[jx][jy] = 0;
4372 Tile[jx][jy] = EL_EMPTY;
4377 for (i = 0; i < MAX_PLAYERS; i++)
4378 if (stored_player[i].active)
4379 game.players_still_needed++;
4381 if (level.solved_by_one_player)
4382 game.players_still_needed = 1;
4384 // when recording the game, store which players take part in the game
4387 #if USE_NEW_PLAYER_ASSIGNMENTS
4388 for (i = 0; i < MAX_PLAYERS; i++)
4389 if (stored_player[i].connected)
4390 tape.player_participates[i] = TRUE;
4392 for (i = 0; i < MAX_PLAYERS; i++)
4393 if (stored_player[i].active)
4394 tape.player_participates[i] = TRUE;
4398 #if DEBUG_INIT_PLAYER
4399 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4402 if (BorderElement == EL_EMPTY)
4405 SBX_Right = lev_fieldx - SCR_FIELDX;
4407 SBY_Lower = lev_fieldy - SCR_FIELDY;
4412 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4414 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4417 if (full_lev_fieldx <= SCR_FIELDX)
4418 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4419 if (full_lev_fieldy <= SCR_FIELDY)
4420 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4422 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4424 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4427 // if local player not found, look for custom element that might create
4428 // the player (make some assumptions about the right custom element)
4429 if (!local_player->present)
4431 int start_x = 0, start_y = 0;
4432 int found_rating = 0;
4433 int found_element = EL_UNDEFINED;
4434 int player_nr = local_player->index_nr;
4436 SCAN_PLAYFIELD(x, y)
4438 int element = Tile[x][y];
4443 if (level.use_start_element[player_nr] &&
4444 level.start_element[player_nr] == element &&
4451 found_element = element;
4454 if (!IS_CUSTOM_ELEMENT(element))
4457 if (CAN_CHANGE(element))
4459 for (i = 0; i < element_info[element].num_change_pages; i++)
4461 // check for player created from custom element as single target
4462 content = element_info[element].change_page[i].target_element;
4463 is_player = IS_PLAYER_ELEMENT(content);
4465 if (is_player && (found_rating < 3 ||
4466 (found_rating == 3 && element < found_element)))
4472 found_element = element;
4477 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4479 // check for player created from custom element as explosion content
4480 content = element_info[element].content.e[xx][yy];
4481 is_player = IS_PLAYER_ELEMENT(content);
4483 if (is_player && (found_rating < 2 ||
4484 (found_rating == 2 && element < found_element)))
4486 start_x = x + xx - 1;
4487 start_y = y + yy - 1;
4490 found_element = element;
4493 if (!CAN_CHANGE(element))
4496 for (i = 0; i < element_info[element].num_change_pages; i++)
4498 // check for player created from custom element as extended target
4500 element_info[element].change_page[i].target_content.e[xx][yy];
4502 is_player = IS_PLAYER_ELEMENT(content);
4504 if (is_player && (found_rating < 1 ||
4505 (found_rating == 1 && element < found_element)))
4507 start_x = x + xx - 1;
4508 start_y = y + yy - 1;
4511 found_element = element;
4517 scroll_x = SCROLL_POSITION_X(start_x);
4518 scroll_y = SCROLL_POSITION_Y(start_y);
4522 scroll_x = SCROLL_POSITION_X(local_player->jx);
4523 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4526 if (game.forced_scroll_x != ARG_UNDEFINED_VALUE)
4527 scroll_x = game.forced_scroll_x;
4528 if (game.forced_scroll_y != ARG_UNDEFINED_VALUE)
4529 scroll_y = game.forced_scroll_y;
4531 // !!! FIX THIS (START) !!!
4532 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
4534 InitGameEngine_BD();
4536 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4538 InitGameEngine_EM();
4540 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4542 InitGameEngine_SP();
4544 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4546 InitGameEngine_MM();
4550 DrawLevel(REDRAW_FIELD);
4553 // after drawing the level, correct some elements
4554 if (game.timegate_time_left == 0)
4555 CloseAllOpenTimegates();
4558 // blit playfield from scroll buffer to normal back buffer for fading in
4559 BlitScreenToBitmap(backbuffer);
4560 // !!! FIX THIS (END) !!!
4562 DrawMaskedBorder(fade_mask);
4567 // full screen redraw is required at this point in the following cases:
4568 // - special editor door undrawn when game was started from level editor
4569 // - drawing area (playfield) was changed and has to be removed completely
4570 redraw_mask = REDRAW_ALL;
4574 if (!game.restart_level)
4576 // copy default game door content to main double buffer
4578 // !!! CHECK AGAIN !!!
4579 SetPanelBackground();
4580 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4581 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4584 SetPanelBackground();
4585 SetDrawBackgroundMask(REDRAW_DOOR_1);
4587 UpdateAndDisplayGameControlValues();
4589 if (!game.restart_level)
4595 CreateGameButtons();
4600 // copy actual game door content to door double buffer for OpenDoor()
4601 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4603 OpenDoor(DOOR_OPEN_ALL);
4605 KeyboardAutoRepeatOffUnlessAutoplay();
4607 #if DEBUG_INIT_PLAYER
4608 DebugPrintPlayerStatus("Player status (final)");
4617 if (!game.restart_level && !tape.playing)
4619 LevelStats_incPlayed(level_nr);
4621 SaveLevelSetup_SeriesInfo();
4624 game.restart_level = FALSE;
4625 game.request_active = FALSE;
4626 game.envelope_active = FALSE;
4627 game.any_door_active = FALSE;
4629 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4630 InitGameActions_MM();
4632 SaveEngineSnapshotToListInitial();
4634 if (!game.restart_level)
4636 PlaySound(SND_GAME_STARTING);
4638 if (setup.sound_music)
4642 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4645 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4646 int actual_player_x, int actual_player_y)
4648 // this is used for non-R'n'D game engines to update certain engine values
4650 // needed to determine if sounds are played within the visible screen area
4651 scroll_x = actual_scroll_x;
4652 scroll_y = actual_scroll_y;
4654 // needed to get player position for "follow finger" playing input method
4655 local_player->jx = actual_player_x;
4656 local_player->jy = actual_player_y;
4659 void InitMovDir(int x, int y)
4661 int i, element = Tile[x][y];
4662 static int xy[4][2] =
4669 static int direction[3][4] =
4671 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4672 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4673 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4682 Tile[x][y] = EL_BUG;
4683 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4686 case EL_SPACESHIP_RIGHT:
4687 case EL_SPACESHIP_UP:
4688 case EL_SPACESHIP_LEFT:
4689 case EL_SPACESHIP_DOWN:
4690 Tile[x][y] = EL_SPACESHIP;
4691 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4694 case EL_BD_BUTTERFLY_RIGHT:
4695 case EL_BD_BUTTERFLY_UP:
4696 case EL_BD_BUTTERFLY_LEFT:
4697 case EL_BD_BUTTERFLY_DOWN:
4698 Tile[x][y] = EL_BD_BUTTERFLY;
4699 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4702 case EL_BD_FIREFLY_RIGHT:
4703 case EL_BD_FIREFLY_UP:
4704 case EL_BD_FIREFLY_LEFT:
4705 case EL_BD_FIREFLY_DOWN:
4706 Tile[x][y] = EL_BD_FIREFLY;
4707 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4710 case EL_PACMAN_RIGHT:
4712 case EL_PACMAN_LEFT:
4713 case EL_PACMAN_DOWN:
4714 Tile[x][y] = EL_PACMAN;
4715 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4718 case EL_YAMYAM_LEFT:
4719 case EL_YAMYAM_RIGHT:
4721 case EL_YAMYAM_DOWN:
4722 Tile[x][y] = EL_YAMYAM;
4723 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4726 case EL_SP_SNIKSNAK:
4727 MovDir[x][y] = MV_UP;
4730 case EL_SP_ELECTRON:
4731 MovDir[x][y] = MV_LEFT;
4738 Tile[x][y] = EL_MOLE;
4739 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4742 case EL_SPRING_LEFT:
4743 case EL_SPRING_RIGHT:
4744 Tile[x][y] = EL_SPRING;
4745 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4749 if (IS_CUSTOM_ELEMENT(element))
4751 struct ElementInfo *ei = &element_info[element];
4752 int move_direction_initial = ei->move_direction_initial;
4753 int move_pattern = ei->move_pattern;
4755 if (move_direction_initial == MV_START_PREVIOUS)
4757 if (MovDir[x][y] != MV_NONE)
4760 move_direction_initial = MV_START_AUTOMATIC;
4763 if (move_direction_initial == MV_START_RANDOM)
4764 MovDir[x][y] = 1 << RND(4);
4765 else if (move_direction_initial & MV_ANY_DIRECTION)
4766 MovDir[x][y] = move_direction_initial;
4767 else if (move_pattern == MV_ALL_DIRECTIONS ||
4768 move_pattern == MV_TURNING_LEFT ||
4769 move_pattern == MV_TURNING_RIGHT ||
4770 move_pattern == MV_TURNING_LEFT_RIGHT ||
4771 move_pattern == MV_TURNING_RIGHT_LEFT ||
4772 move_pattern == MV_TURNING_RANDOM)
4773 MovDir[x][y] = 1 << RND(4);
4774 else if (move_pattern == MV_HORIZONTAL)
4775 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4776 else if (move_pattern == MV_VERTICAL)
4777 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4778 else if (move_pattern & MV_ANY_DIRECTION)
4779 MovDir[x][y] = element_info[element].move_pattern;
4780 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4781 move_pattern == MV_ALONG_RIGHT_SIDE)
4783 // use random direction as default start direction
4784 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4785 MovDir[x][y] = 1 << RND(4);
4787 for (i = 0; i < NUM_DIRECTIONS; i++)
4789 int x1 = x + xy[i][0];
4790 int y1 = y + xy[i][1];
4792 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4794 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4795 MovDir[x][y] = direction[0][i];
4797 MovDir[x][y] = direction[1][i];
4806 MovDir[x][y] = 1 << RND(4);
4808 if (element != EL_BUG &&
4809 element != EL_SPACESHIP &&
4810 element != EL_BD_BUTTERFLY &&
4811 element != EL_BD_FIREFLY)
4814 for (i = 0; i < NUM_DIRECTIONS; i++)
4816 int x1 = x + xy[i][0];
4817 int y1 = y + xy[i][1];
4819 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4821 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4823 MovDir[x][y] = direction[0][i];
4826 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4827 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4829 MovDir[x][y] = direction[1][i];
4838 GfxDir[x][y] = MovDir[x][y];
4841 void InitAmoebaNr(int x, int y)
4844 int group_nr = AmoebaNeighbourNr(x, y);
4848 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4850 if (AmoebaCnt[i] == 0)
4858 AmoebaNr[x][y] = group_nr;
4859 AmoebaCnt[group_nr]++;
4860 AmoebaCnt2[group_nr]++;
4863 static void LevelSolved_SetFinalGameValues(void)
4865 game.time_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.time_left :
4866 game.no_level_time_limit ? TimePlayed : TimeLeft);
4867 game.score_time_final = (level.use_step_counter ? TimePlayed :
4868 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4870 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.score :
4871 level.game_engine_type == GAME_ENGINE_TYPE_EM ? game_em.lev->score :
4872 level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.score :
4875 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4876 MM_HEALTH(game_mm.laser_overload_value) :
4879 game.LevelSolved_CountingTime = game.time_final;
4880 game.LevelSolved_CountingScore = game.score_final;
4881 game.LevelSolved_CountingHealth = game.health_final;
4884 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4886 game.LevelSolved_CountingTime = time;
4887 game.LevelSolved_CountingScore = score;
4888 game.LevelSolved_CountingHealth = health;
4890 game_panel_controls[GAME_PANEL_TIME].value = time;
4891 game_panel_controls[GAME_PANEL_SCORE].value = score;
4892 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4894 DisplayGameControlValues();
4897 static void LevelSolved(void)
4899 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4900 game.players_still_needed > 0)
4903 game.LevelSolved = TRUE;
4904 game.GameOver = TRUE;
4908 // needed here to display correct panel values while player walks into exit
4909 LevelSolved_SetFinalGameValues();
4912 static boolean AdvanceToNextLevel(void)
4914 if (setup.increment_levels &&
4915 level_nr < leveldir_current->last_level &&
4918 level_nr++; // advance to next level
4919 TapeErase(); // start with empty tape
4921 if (setup.auto_play_next_level)
4923 scores.continue_playing = TRUE;
4924 scores.next_level_nr = level_nr;
4926 LoadLevel(level_nr);
4928 SaveLevelSetup_SeriesInfo();
4939 static int time_count_steps;
4940 static int time, time_final;
4941 static float score, score_final; // needed for time score < 10 for 10 seconds
4942 static int health, health_final;
4943 static int game_over_delay_1 = 0;
4944 static int game_over_delay_2 = 0;
4945 static int game_over_delay_3 = 0;
4946 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4947 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4949 if (!game.LevelSolved_GameWon)
4953 // do not start end game actions before the player stops moving (to exit)
4954 if (local_player->active && local_player->MovPos)
4957 // calculate final game values after player finished walking into exit
4958 LevelSolved_SetFinalGameValues();
4960 game.LevelSolved_GameWon = TRUE;
4961 game.LevelSolved_SaveTape = tape.recording;
4962 game.LevelSolved_SaveScore = !tape.playing;
4966 LevelStats_incSolved(level_nr);
4968 SaveLevelSetup_SeriesInfo();
4971 if (tape.auto_play) // tape might already be stopped here
4972 tape.auto_play_level_solved = TRUE;
4976 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4977 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4978 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4980 time = time_final = game.time_final;
4981 score = score_final = game.score_final;
4982 health = health_final = game.health_final;
4984 // update game panel values before (delayed) counting of score (if any)
4985 LevelSolved_DisplayFinalGameValues(time, score, health);
4987 // if level has time score defined, calculate new final game values
4990 int time_final_max = 999;
4991 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4992 int time_frames = 0;
4993 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4994 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4999 time_frames = time_frames_left;
5001 else if (game.no_level_time_limit && TimePlayed < time_final_max)
5003 time_final = time_final_max;
5004 time_frames = time_frames_final_max - time_frames_played;
5007 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
5009 time_count_steps = MAX(1, ABS(time_final - time) / 100);
5011 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
5013 // keep previous values (final values already processed here)
5015 score_final = score;
5017 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
5020 score_final += health * time_score;
5023 game.score_final = score_final;
5024 game.health_final = health_final;
5027 // if not counting score after game, immediately update game panel values
5028 if (level_editor_test_game || !setup.count_score_after_game ||
5029 level.game_engine_type == GAME_ENGINE_TYPE_BD)
5032 score = score_final;
5034 LevelSolved_DisplayFinalGameValues(time, score, health);
5037 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
5039 // check if last player has left the level
5040 if (game.exit_x >= 0 &&
5043 int x = game.exit_x;
5044 int y = game.exit_y;
5045 int element = Tile[x][y];
5047 // close exit door after last player
5048 if ((game.all_players_gone &&
5049 (element == EL_EXIT_OPEN ||
5050 element == EL_SP_EXIT_OPEN ||
5051 element == EL_STEEL_EXIT_OPEN)) ||
5052 element == EL_EM_EXIT_OPEN ||
5053 element == EL_EM_STEEL_EXIT_OPEN)
5057 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
5058 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
5059 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
5060 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
5061 EL_EM_STEEL_EXIT_CLOSING);
5063 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
5066 // player disappears
5067 DrawLevelField(x, y);
5070 for (i = 0; i < MAX_PLAYERS; i++)
5072 struct PlayerInfo *player = &stored_player[i];
5074 if (player->present)
5076 RemovePlayer(player);
5078 // player disappears
5079 DrawLevelField(player->jx, player->jy);
5084 PlaySound(SND_GAME_WINNING);
5087 if (setup.count_score_after_game)
5089 if (time != time_final)
5091 if (game_over_delay_1 > 0)
5093 game_over_delay_1--;
5098 int time_to_go = ABS(time_final - time);
5099 int time_count_dir = (time < time_final ? +1 : -1);
5101 if (time_to_go < time_count_steps)
5102 time_count_steps = 1;
5104 time += time_count_steps * time_count_dir;
5105 score += time_count_steps * time_score;
5107 // set final score to correct rounding differences after counting score
5108 if (time == time_final)
5109 score = score_final;
5111 LevelSolved_DisplayFinalGameValues(time, score, health);
5113 if (time == time_final)
5114 StopSound(SND_GAME_LEVELTIME_BONUS);
5115 else if (setup.sound_loops)
5116 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5118 PlaySound(SND_GAME_LEVELTIME_BONUS);
5123 if (health != health_final)
5125 if (game_over_delay_2 > 0)
5127 game_over_delay_2--;
5132 int health_count_dir = (health < health_final ? +1 : -1);
5134 health += health_count_dir;
5135 score += time_score;
5137 LevelSolved_DisplayFinalGameValues(time, score, health);
5139 if (health == health_final)
5140 StopSound(SND_GAME_LEVELTIME_BONUS);
5141 else if (setup.sound_loops)
5142 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5144 PlaySound(SND_GAME_LEVELTIME_BONUS);
5150 game.panel.active = FALSE;
5152 if (game_over_delay_3 > 0)
5154 game_over_delay_3--;
5164 // used instead of "level_nr" (needed for network games)
5165 int last_level_nr = levelset.level_nr;
5166 boolean tape_saved = FALSE;
5167 boolean game_over = checkGameFailed();
5169 // Important note: This function is not only called after "GameWon()", but also after
5170 // "game over" (if automatically asking for restarting the game is disabled in setup)
5172 // do not handle game end if game over and automatically asking for game restart
5173 if (game_over && setup.ask_on_game_over)
5175 // (this is a special case: player pressed "return" key or fire button shortly before
5176 // automatically asking to restart the game, so skip asking and restart right away)
5178 CloseDoor(DOOR_CLOSE_1);
5180 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5185 // do not handle game end if request dialog is already active
5186 if (checkRequestActive())
5189 if (game.LevelSolved)
5190 game.LevelSolved_GameEnd = TRUE;
5192 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5194 // make sure that request dialog to save tape does not open door again
5195 if (!global.use_envelope_request)
5196 CloseDoor(DOOR_CLOSE_1);
5199 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5201 // set unique basename for score tape (also saved in high score table)
5202 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5205 // if no tape is to be saved, close both doors simultaneously
5206 CloseDoor(DOOR_CLOSE_ALL);
5208 if (level_editor_test_game || score_info_tape_play)
5210 SetGameStatus(GAME_MODE_MAIN);
5217 if (!game.GamePlayed || (!game.LevelSolved_SaveScore && !level.bd_intermission))
5219 SetGameStatus(GAME_MODE_MAIN);
5226 if (level_nr == leveldir_current->handicap_level)
5228 leveldir_current->handicap_level++;
5230 SaveLevelSetup_SeriesInfo();
5233 // save score and score tape before potentially erasing tape below
5234 if (game.LevelSolved_SaveScore)
5235 NewHighScore(last_level_nr, tape_saved);
5237 // increment and load next level (if possible and not configured otherwise)
5238 AdvanceToNextLevel();
5240 if (game.LevelSolved_SaveScore && scores.last_added >= 0 && setup.show_scores_after_game)
5242 SetGameStatus(GAME_MODE_SCORES);
5244 DrawHallOfFame(last_level_nr);
5246 else if (scores.continue_playing)
5248 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5252 SetGameStatus(GAME_MODE_MAIN);
5258 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5259 boolean one_score_entry_per_name)
5263 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5266 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5268 struct ScoreEntry *entry = &list->entry[i];
5269 boolean score_is_better = (new_entry->score > entry->score);
5270 boolean score_is_equal = (new_entry->score == entry->score);
5271 boolean time_is_better = (new_entry->time < entry->time);
5272 boolean time_is_equal = (new_entry->time == entry->time);
5273 boolean better_by_score = (score_is_better ||
5274 (score_is_equal && time_is_better));
5275 boolean better_by_time = (time_is_better ||
5276 (time_is_equal && score_is_better));
5277 boolean is_better = (level.rate_time_over_score ? better_by_time :
5279 boolean entry_is_empty = (entry->score == 0 &&
5282 // prevent adding server score entries if also existing in local score file
5283 // (special case: historic score entries have an empty tape basename entry)
5284 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5285 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5287 // add fields from server score entry not stored in local score entry
5288 // (currently, this means setting platform, version and country fields;
5289 // in rare cases, this may also correct an invalid score value, as
5290 // historic scores might have been truncated to 16-bit values locally)
5291 *entry = *new_entry;
5296 if (is_better || entry_is_empty)
5298 // player has made it to the hall of fame
5300 if (i < MAX_SCORE_ENTRIES - 1)
5302 int m = MAX_SCORE_ENTRIES - 1;
5305 if (one_score_entry_per_name)
5307 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5308 if (strEqual(list->entry[l].name, new_entry->name))
5311 if (m == i) // player's new highscore overwrites his old one
5315 for (l = m; l > i; l--)
5316 list->entry[l] = list->entry[l - 1];
5321 *entry = *new_entry;
5325 else if (one_score_entry_per_name &&
5326 strEqual(entry->name, new_entry->name))
5328 // player already in high score list with better score or time
5334 // special case: new score is beyond the last high score list position
5335 return MAX_SCORE_ENTRIES;
5338 void NewHighScore(int level_nr, boolean tape_saved)
5340 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5341 boolean one_per_name = FALSE;
5343 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5344 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5346 new_entry.score = game.score_final;
5347 new_entry.time = game.score_time_final;
5349 LoadScore(level_nr);
5351 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5353 if (scores.last_added >= MAX_SCORE_ENTRIES)
5355 scores.last_added = MAX_SCORE_ENTRIES - 1;
5356 scores.force_last_added = TRUE;
5358 scores.entry[scores.last_added] = new_entry;
5360 // store last added local score entry (before merging server scores)
5361 scores.last_added_local = scores.last_added;
5366 if (scores.last_added < 0)
5369 SaveScore(level_nr);
5371 // store last added local score entry (before merging server scores)
5372 scores.last_added_local = scores.last_added;
5374 if (!game.LevelSolved_SaveTape)
5377 SaveScoreTape(level_nr);
5379 if (setup.ask_for_using_api_server)
5381 setup.use_api_server =
5382 Request("Upload your score and tape to the high score server?", REQ_ASK);
5384 if (!setup.use_api_server)
5385 Request("Not using high score server! Use setup menu to enable again!",
5388 runtime.use_api_server = setup.use_api_server;
5390 // after asking for using API server once, do not ask again
5391 setup.ask_for_using_api_server = FALSE;
5393 SaveSetup_ServerSetup();
5396 SaveServerScore(level_nr, tape_saved);
5399 void MergeServerScore(void)
5401 struct ScoreEntry last_added_entry;
5402 boolean one_per_name = FALSE;
5405 if (scores.last_added >= 0)
5406 last_added_entry = scores.entry[scores.last_added];
5408 for (i = 0; i < server_scores.num_entries; i++)
5410 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5412 if (pos >= 0 && pos <= scores.last_added)
5413 scores.last_added++;
5416 if (scores.last_added >= MAX_SCORE_ENTRIES)
5418 scores.last_added = MAX_SCORE_ENTRIES - 1;
5419 scores.force_last_added = TRUE;
5421 scores.entry[scores.last_added] = last_added_entry;
5425 static int getElementMoveStepsizeExt(int x, int y, int direction)
5427 int element = Tile[x][y];
5428 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5429 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5430 int horiz_move = (dx != 0);
5431 int sign = (horiz_move ? dx : dy);
5432 int step = sign * element_info[element].move_stepsize;
5434 // special values for move stepsize for spring and things on conveyor belt
5437 if (CAN_FALL(element) &&
5438 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5439 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5440 else if (element == EL_SPRING)
5441 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5447 static int getElementMoveStepsize(int x, int y)
5449 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5452 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5454 if (player->GfxAction != action || player->GfxDir != dir)
5456 player->GfxAction = action;
5457 player->GfxDir = dir;
5459 player->StepFrame = 0;
5463 static void ResetGfxFrame(int x, int y)
5465 // profiling showed that "autotest" spends 10~20% of its time in this function
5466 if (DrawingDeactivatedField())
5469 int element = Tile[x][y];
5470 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5472 if (graphic_info[graphic].anim_global_sync)
5473 GfxFrame[x][y] = FrameCounter;
5474 else if (graphic_info[graphic].anim_global_anim_sync)
5475 GfxFrame[x][y] = getGlobalAnimSyncFrame();
5476 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5477 GfxFrame[x][y] = CustomValue[x][y];
5478 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5479 GfxFrame[x][y] = element_info[element].collect_score;
5480 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5481 GfxFrame[x][y] = ChangeDelay[x][y];
5484 static void ResetGfxAnimation(int x, int y)
5486 GfxAction[x][y] = ACTION_DEFAULT;
5487 GfxDir[x][y] = MovDir[x][y];
5490 ResetGfxFrame(x, y);
5493 static void ResetRandomAnimationValue(int x, int y)
5495 GfxRandom[x][y] = INIT_GFX_RANDOM();
5498 static void InitMovingField(int x, int y, int direction)
5500 int element = Tile[x][y];
5501 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5502 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5505 boolean is_moving_before, is_moving_after;
5507 // check if element was/is moving or being moved before/after mode change
5508 is_moving_before = (WasJustMoving[x][y] != 0);
5509 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5511 // reset animation only for moving elements which change direction of moving
5512 // or which just started or stopped moving
5513 // (else CEs with property "can move" / "not moving" are reset each frame)
5514 if (is_moving_before != is_moving_after ||
5515 direction != MovDir[x][y])
5516 ResetGfxAnimation(x, y);
5518 MovDir[x][y] = direction;
5519 GfxDir[x][y] = direction;
5521 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5522 direction == MV_DOWN && CAN_FALL(element) ?
5523 ACTION_FALLING : ACTION_MOVING);
5525 // this is needed for CEs with property "can move" / "not moving"
5527 if (is_moving_after)
5529 if (Tile[newx][newy] == EL_EMPTY)
5530 Tile[newx][newy] = EL_BLOCKED;
5532 MovDir[newx][newy] = MovDir[x][y];
5534 CustomValue[newx][newy] = CustomValue[x][y];
5536 GfxFrame[newx][newy] = GfxFrame[x][y];
5537 GfxRandom[newx][newy] = GfxRandom[x][y];
5538 GfxAction[newx][newy] = GfxAction[x][y];
5539 GfxDir[newx][newy] = GfxDir[x][y];
5543 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5545 int direction = MovDir[x][y];
5546 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5547 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5553 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5555 int direction = MovDir[x][y];
5556 int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0);
5557 int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0);
5559 *comes_from_x = oldx;
5560 *comes_from_y = oldy;
5563 static int MovingOrBlocked2Element(int x, int y)
5565 int element = Tile[x][y];
5567 if (element == EL_BLOCKED)
5571 Blocked2Moving(x, y, &oldx, &oldy);
5573 return Tile[oldx][oldy];
5579 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5581 // like MovingOrBlocked2Element(), but if element is moving
5582 // and (x, y) is the field the moving element is just leaving,
5583 // return EL_BLOCKED instead of the element value
5584 int element = Tile[x][y];
5586 if (IS_MOVING(x, y))
5588 if (element == EL_BLOCKED)
5592 Blocked2Moving(x, y, &oldx, &oldy);
5593 return Tile[oldx][oldy];
5602 static void RemoveField(int x, int y)
5604 Tile[x][y] = EL_EMPTY;
5610 CustomValue[x][y] = 0;
5613 ChangeDelay[x][y] = 0;
5614 ChangePage[x][y] = -1;
5615 Pushed[x][y] = FALSE;
5617 GfxElement[x][y] = EL_UNDEFINED;
5618 GfxAction[x][y] = ACTION_DEFAULT;
5619 GfxDir[x][y] = MV_NONE;
5622 static void RemoveMovingField(int x, int y)
5624 int oldx = x, oldy = y, newx = x, newy = y;
5625 int element = Tile[x][y];
5626 int next_element = EL_UNDEFINED;
5628 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5631 if (IS_MOVING(x, y))
5633 Moving2Blocked(x, y, &newx, &newy);
5635 if (Tile[newx][newy] != EL_BLOCKED)
5637 // element is moving, but target field is not free (blocked), but
5638 // already occupied by something different (example: acid pool);
5639 // in this case, only remove the moving field, but not the target
5641 RemoveField(oldx, oldy);
5643 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5645 TEST_DrawLevelField(oldx, oldy);
5650 else if (element == EL_BLOCKED)
5652 Blocked2Moving(x, y, &oldx, &oldy);
5653 if (!IS_MOVING(oldx, oldy))
5657 if (element == EL_BLOCKED &&
5658 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5659 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5660 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5661 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5662 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5663 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5664 next_element = get_next_element(Tile[oldx][oldy]);
5666 RemoveField(oldx, oldy);
5667 RemoveField(newx, newy);
5669 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5671 if (next_element != EL_UNDEFINED)
5672 Tile[oldx][oldy] = next_element;
5674 TEST_DrawLevelField(oldx, oldy);
5675 TEST_DrawLevelField(newx, newy);
5678 void DrawDynamite(int x, int y)
5680 int sx = SCREENX(x), sy = SCREENY(y);
5681 int graphic = el2img(Tile[x][y]);
5684 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5687 if (IS_WALKABLE_INSIDE(Back[x][y]))
5691 DrawLevelElement(x, y, Back[x][y]);
5692 else if (Store[x][y])
5693 DrawLevelElement(x, y, Store[x][y]);
5694 else if (game.use_masked_elements)
5695 DrawLevelElement(x, y, EL_EMPTY);
5697 frame = getGraphicAnimationFrameXY(graphic, x, y);
5699 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5700 DrawGraphicThruMask(sx, sy, graphic, frame);
5702 DrawGraphic(sx, sy, graphic, frame);
5705 static void CheckDynamite(int x, int y)
5707 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5711 if (MovDelay[x][y] != 0)
5714 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5720 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5725 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5727 boolean num_checked_players = 0;
5730 for (i = 0; i < MAX_PLAYERS; i++)
5732 if (stored_player[i].active)
5734 int sx = stored_player[i].jx;
5735 int sy = stored_player[i].jy;
5737 if (num_checked_players == 0)
5744 *sx1 = MIN(*sx1, sx);
5745 *sy1 = MIN(*sy1, sy);
5746 *sx2 = MAX(*sx2, sx);
5747 *sy2 = MAX(*sy2, sy);
5750 num_checked_players++;
5755 static boolean checkIfAllPlayersFitToScreen_RND(void)
5757 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5759 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5761 return (sx2 - sx1 < SCR_FIELDX &&
5762 sy2 - sy1 < SCR_FIELDY);
5765 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5767 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5769 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5771 *sx = (sx1 + sx2) / 2;
5772 *sy = (sy1 + sy2) / 2;
5775 static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
5776 boolean center_screen, boolean quick_relocation)
5778 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5779 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5780 boolean no_delay = (tape.warp_forward);
5781 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5782 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5783 int new_scroll_x, new_scroll_y;
5785 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5787 // case 1: quick relocation inside visible screen (without scrolling)
5794 if (!level.shifted_relocation || center_screen)
5796 // relocation _with_ centering of screen
5798 new_scroll_x = SCROLL_POSITION_X(x);
5799 new_scroll_y = SCROLL_POSITION_Y(y);
5803 // relocation _without_ centering of screen
5805 // apply distance between old and new player position to scroll position
5806 int shifted_scroll_x = scroll_x + (x - old_x);
5807 int shifted_scroll_y = scroll_y + (y - old_y);
5809 // make sure that shifted scroll position does not scroll beyond screen
5810 new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
5811 new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
5813 // special case for teleporting from one end of the playfield to the other
5814 // (this kludge prevents the destination area to be shifted by half a tile
5815 // against the source destination for even screen width or screen height;
5816 // probably most useful when used with high "game.forced_scroll_delay_value"
5817 // in combination with "game.forced_scroll_x" and "game.forced_scroll_y")
5818 if (quick_relocation)
5820 if (EVEN(SCR_FIELDX))
5822 // relocate (teleport) between left and right border (half or full)
5823 if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1)
5824 new_scroll_x = SBX_Right;
5825 else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right)
5826 new_scroll_x = SBX_Right - 1;
5827 else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1)
5828 new_scroll_x = SBX_Left;
5829 else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left)
5830 new_scroll_x = SBX_Left + 1;
5833 if (EVEN(SCR_FIELDY))
5835 // relocate (teleport) between top and bottom border (half or full)
5836 if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1)
5837 new_scroll_y = SBY_Lower;
5838 else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower)
5839 new_scroll_y = SBY_Lower - 1;
5840 else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1)
5841 new_scroll_y = SBY_Upper;
5842 else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper)
5843 new_scroll_y = SBY_Upper + 1;
5848 if (quick_relocation)
5850 // case 2: quick relocation (redraw without visible scrolling)
5852 scroll_x = new_scroll_x;
5853 scroll_y = new_scroll_y;
5860 // case 3: visible relocation (with scrolling to new position)
5862 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5864 SetVideoFrameDelay(wait_delay_value);
5866 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5868 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5869 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5871 if (dx == 0 && dy == 0) // no scrolling needed at all
5877 // set values for horizontal/vertical screen scrolling (half tile size)
5878 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5879 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5880 int pos_x = dx * TILEX / 2;
5881 int pos_y = dy * TILEY / 2;
5882 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5883 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5885 ScrollLevel(dx, dy);
5888 // scroll in two steps of half tile size to make things smoother
5889 BlitScreenToBitmapExt_RND(window, fx, fy);
5891 // scroll second step to align at full tile size
5892 BlitScreenToBitmap(window);
5898 SetVideoFrameDelay(frame_delay_value_old);
5901 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5903 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5904 int player_nr = GET_PLAYER_NR(el_player);
5905 struct PlayerInfo *player = &stored_player[player_nr];
5906 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5907 boolean no_delay = (tape.warp_forward);
5908 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5909 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5910 int old_jx = player->jx;
5911 int old_jy = player->jy;
5912 int old_element = Tile[old_jx][old_jy];
5913 int element = Tile[jx][jy];
5914 boolean player_relocated = (old_jx != jx || old_jy != jy);
5916 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5917 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5918 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5919 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5920 int leave_side_horiz = move_dir_horiz;
5921 int leave_side_vert = move_dir_vert;
5922 int enter_side = enter_side_horiz | enter_side_vert;
5923 int leave_side = leave_side_horiz | leave_side_vert;
5925 if (player->buried) // do not reanimate dead player
5928 if (!player_relocated) // no need to relocate the player
5931 if (IS_PLAYER(jx, jy)) // player already placed at new position
5933 RemoveField(jx, jy); // temporarily remove newly placed player
5934 DrawLevelField(jx, jy);
5937 if (player->present)
5939 while (player->MovPos)
5941 ScrollPlayer(player, SCROLL_GO_ON);
5942 ScrollScreen(NULL, SCROLL_GO_ON);
5944 AdvanceFrameAndPlayerCounters(player->index_nr);
5948 BackToFront_WithFrameDelay(wait_delay_value);
5951 DrawPlayer(player); // needed here only to cleanup last field
5952 DrawLevelField(player->jx, player->jy); // remove player graphic
5954 player->is_moving = FALSE;
5957 if (IS_CUSTOM_ELEMENT(old_element))
5958 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5960 player->index_bit, leave_side);
5962 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5964 player->index_bit, leave_side);
5966 Tile[jx][jy] = el_player;
5967 InitPlayerField(jx, jy, el_player, TRUE);
5969 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5970 possible that the relocation target field did not contain a player element,
5971 but a walkable element, to which the new player was relocated -- in this
5972 case, restore that (already initialized!) element on the player field */
5973 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5975 Tile[jx][jy] = element; // restore previously existing element
5978 // only visually relocate centered player
5979 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy,
5980 FALSE, level.instant_relocation);
5982 TestIfPlayerTouchesBadThing(jx, jy);
5983 TestIfPlayerTouchesCustomElement(jx, jy);
5985 if (IS_CUSTOM_ELEMENT(element))
5986 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5987 player->index_bit, enter_side);
5989 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5990 player->index_bit, enter_side);
5992 if (player->is_switching)
5994 /* ensure that relocation while still switching an element does not cause
5995 a new element to be treated as also switched directly after relocation
5996 (this is important for teleporter switches that teleport the player to
5997 a place where another teleporter switch is in the same direction, which
5998 would then incorrectly be treated as immediately switched before the
5999 direction key that caused the switch was released) */
6001 player->switch_x += jx - old_jx;
6002 player->switch_y += jy - old_jy;
6006 static void Explode(int ex, int ey, int phase, int mode)
6012 if (game.explosions_delayed)
6014 ExplodeField[ex][ey] = mode;
6018 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
6020 int center_element = Tile[ex][ey];
6021 int ce_value = CustomValue[ex][ey];
6022 int ce_score = element_info[center_element].collect_score;
6023 int artwork_element, explosion_element; // set these values later
6025 // remove things displayed in background while burning dynamite
6026 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
6029 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
6031 // put moving element to center field (and let it explode there)
6032 center_element = MovingOrBlocked2Element(ex, ey);
6033 RemoveMovingField(ex, ey);
6034 Tile[ex][ey] = center_element;
6037 // now "center_element" is finally determined -- set related values now
6038 artwork_element = center_element; // for custom player artwork
6039 explosion_element = center_element; // for custom player artwork
6041 if (IS_PLAYER(ex, ey))
6043 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
6045 artwork_element = stored_player[player_nr].artwork_element;
6047 if (level.use_explosion_element[player_nr])
6049 explosion_element = level.explosion_element[player_nr];
6050 artwork_element = explosion_element;
6054 if (mode == EX_TYPE_NORMAL ||
6055 mode == EX_TYPE_CENTER ||
6056 mode == EX_TYPE_CROSS)
6057 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
6059 last_phase = element_info[explosion_element].explosion_delay + 1;
6061 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
6063 int xx = x - ex + 1;
6064 int yy = y - ey + 1;
6067 if (!IN_LEV_FIELD(x, y) ||
6068 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
6069 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
6072 element = Tile[x][y];
6074 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
6076 element = MovingOrBlocked2Element(x, y);
6078 if (!IS_EXPLOSION_PROOF(element))
6079 RemoveMovingField(x, y);
6082 // indestructible elements can only explode in center (but not flames)
6083 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
6084 mode == EX_TYPE_BORDER)) ||
6085 element == EL_FLAMES)
6088 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
6089 behaviour, for example when touching a yamyam that explodes to rocks
6090 with active deadly shield, a rock is created under the player !!! */
6091 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
6093 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
6094 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
6095 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
6097 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
6100 if (IS_ACTIVE_BOMB(element))
6102 // re-activate things under the bomb like gate or penguin
6103 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
6110 // save walkable background elements while explosion on same tile
6111 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
6112 (x != ex || y != ey || mode == EX_TYPE_BORDER))
6113 Back[x][y] = element;
6115 // ignite explodable elements reached by other explosion
6116 if (element == EL_EXPLOSION)
6117 element = Store2[x][y];
6119 if (AmoebaNr[x][y] &&
6120 (element == EL_AMOEBA_FULL ||
6121 element == EL_BD_AMOEBA ||
6122 element == EL_AMOEBA_GROWING))
6124 AmoebaCnt[AmoebaNr[x][y]]--;
6125 AmoebaCnt2[AmoebaNr[x][y]]--;
6130 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
6132 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
6134 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
6136 if (PLAYERINFO(ex, ey)->use_murphy)
6137 Store[x][y] = EL_EMPTY;
6140 // !!! check this case -- currently needed for rnd_rado_negundo_v,
6141 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
6142 else if (IS_PLAYER_ELEMENT(center_element))
6143 Store[x][y] = EL_EMPTY;
6144 else if (center_element == EL_YAMYAM)
6145 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
6146 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
6147 Store[x][y] = element_info[center_element].content.e[xx][yy];
6149 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
6150 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
6151 // otherwise) -- FIX THIS !!!
6152 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
6153 Store[x][y] = element_info[element].content.e[1][1];
6155 else if (!CAN_EXPLODE(element))
6156 Store[x][y] = element_info[element].content.e[1][1];
6159 Store[x][y] = EL_EMPTY;
6161 if (IS_CUSTOM_ELEMENT(center_element))
6162 Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value :
6163 Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score :
6164 Store[x][y] >= EL_PREV_CE_8 &&
6165 Store[x][y] <= EL_NEXT_CE_8 ?
6166 RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) :
6169 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
6170 center_element == EL_AMOEBA_TO_DIAMOND)
6171 Store2[x][y] = element;
6173 Tile[x][y] = EL_EXPLOSION;
6174 GfxElement[x][y] = artwork_element;
6176 ExplodePhase[x][y] = 1;
6177 ExplodeDelay[x][y] = last_phase;
6182 if (center_element == EL_YAMYAM)
6183 game.yamyam_content_nr =
6184 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
6196 GfxFrame[x][y] = 0; // restart explosion animation
6198 last_phase = ExplodeDelay[x][y];
6200 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
6202 // this can happen if the player leaves an explosion just in time
6203 if (GfxElement[x][y] == EL_UNDEFINED)
6204 GfxElement[x][y] = EL_EMPTY;
6206 border_element = Store2[x][y];
6207 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6208 border_element = StorePlayer[x][y];
6210 if (phase == element_info[border_element].ignition_delay ||
6211 phase == last_phase)
6213 boolean border_explosion = FALSE;
6215 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6216 !PLAYER_EXPLOSION_PROTECTED(x, y))
6218 KillPlayerUnlessExplosionProtected(x, y);
6219 border_explosion = TRUE;
6221 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6223 Tile[x][y] = Store2[x][y];
6226 border_explosion = TRUE;
6228 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6230 AmoebaToDiamond(x, y);
6232 border_explosion = TRUE;
6235 // if an element just explodes due to another explosion (chain-reaction),
6236 // do not immediately end the new explosion when it was the last frame of
6237 // the explosion (as it would be done in the following "if"-statement!)
6238 if (border_explosion && phase == last_phase)
6242 // this can happen if the player was just killed by an explosion
6243 if (GfxElement[x][y] == EL_UNDEFINED)
6244 GfxElement[x][y] = EL_EMPTY;
6246 if (phase == last_phase)
6250 element = Tile[x][y] = Store[x][y];
6251 Store[x][y] = Store2[x][y] = 0;
6252 GfxElement[x][y] = EL_UNDEFINED;
6254 // player can escape from explosions and might therefore be still alive
6255 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6256 element <= EL_PLAYER_IS_EXPLODING_4)
6258 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6259 int explosion_element = EL_PLAYER_1 + player_nr;
6260 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6261 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6263 if (level.use_explosion_element[player_nr])
6264 explosion_element = level.explosion_element[player_nr];
6266 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6267 element_info[explosion_element].content.e[xx][yy]);
6270 // restore probably existing indestructible background element
6271 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6272 element = Tile[x][y] = Back[x][y];
6275 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6276 GfxDir[x][y] = MV_NONE;
6277 ChangeDelay[x][y] = 0;
6278 ChangePage[x][y] = -1;
6280 CustomValue[x][y] = 0;
6282 InitField_WithBug2(x, y, FALSE);
6284 TEST_DrawLevelField(x, y);
6286 TestIfElementTouchesCustomElement(x, y);
6288 if (GFX_CRUMBLED(element))
6289 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6291 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6292 StorePlayer[x][y] = 0;
6294 if (IS_PLAYER_ELEMENT(element))
6295 RelocatePlayer(x, y, element);
6297 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6299 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6300 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6303 TEST_DrawLevelFieldCrumbled(x, y);
6305 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6307 DrawLevelElement(x, y, Back[x][y]);
6308 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6310 else if (IS_WALKABLE_UNDER(Back[x][y]))
6312 DrawLevelGraphic(x, y, graphic, frame);
6313 DrawLevelElementThruMask(x, y, Back[x][y]);
6315 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6316 DrawLevelGraphic(x, y, graphic, frame);
6320 static void DynaExplode(int ex, int ey)
6323 int dynabomb_element = Tile[ex][ey];
6324 int dynabomb_size = 1;
6325 boolean dynabomb_xl = FALSE;
6326 struct PlayerInfo *player;
6327 struct XY *xy = xy_topdown;
6329 if (IS_ACTIVE_BOMB(dynabomb_element))
6331 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6332 dynabomb_size = player->dynabomb_size;
6333 dynabomb_xl = player->dynabomb_xl;
6334 player->dynabombs_left++;
6337 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6339 for (i = 0; i < NUM_DIRECTIONS; i++)
6341 for (j = 1; j <= dynabomb_size; j++)
6343 int x = ex + j * xy[i].x;
6344 int y = ey + j * xy[i].y;
6347 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6350 element = Tile[x][y];
6352 // do not restart explosions of fields with active bombs
6353 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6356 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6358 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6359 !IS_DIGGABLE(element) && !dynabomb_xl)
6365 void Bang(int x, int y)
6367 int element = MovingOrBlocked2Element(x, y);
6368 int explosion_type = EX_TYPE_NORMAL;
6370 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6372 struct PlayerInfo *player = PLAYERINFO(x, y);
6374 element = Tile[x][y] = player->initial_element;
6376 if (level.use_explosion_element[player->index_nr])
6378 int explosion_element = level.explosion_element[player->index_nr];
6380 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6381 explosion_type = EX_TYPE_CROSS;
6382 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6383 explosion_type = EX_TYPE_CENTER;
6391 case EL_BD_BUTTERFLY:
6394 case EL_DARK_YAMYAM:
6398 RaiseScoreElement(element);
6401 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6402 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6403 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6404 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6405 case EL_DYNABOMB_INCREASE_NUMBER:
6406 case EL_DYNABOMB_INCREASE_SIZE:
6407 case EL_DYNABOMB_INCREASE_POWER:
6408 explosion_type = EX_TYPE_DYNA;
6411 case EL_DC_LANDMINE:
6412 explosion_type = EX_TYPE_CENTER;
6417 case EL_LAMP_ACTIVE:
6418 case EL_AMOEBA_TO_DIAMOND:
6419 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6420 explosion_type = EX_TYPE_CENTER;
6424 if (element_info[element].explosion_type == EXPLODES_CROSS)
6425 explosion_type = EX_TYPE_CROSS;
6426 else if (element_info[element].explosion_type == EXPLODES_1X1)
6427 explosion_type = EX_TYPE_CENTER;
6431 if (explosion_type == EX_TYPE_DYNA)
6434 Explode(x, y, EX_PHASE_START, explosion_type);
6436 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6439 static void SplashAcid(int x, int y)
6441 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6442 (!IN_LEV_FIELD(x - 1, y - 2) ||
6443 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6444 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6446 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6447 (!IN_LEV_FIELD(x + 1, y - 2) ||
6448 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6449 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6451 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6454 static void InitBeltMovement(void)
6456 static int belt_base_element[4] =
6458 EL_CONVEYOR_BELT_1_LEFT,
6459 EL_CONVEYOR_BELT_2_LEFT,
6460 EL_CONVEYOR_BELT_3_LEFT,
6461 EL_CONVEYOR_BELT_4_LEFT
6463 static int belt_base_active_element[4] =
6465 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6466 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6467 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6468 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6473 // set frame order for belt animation graphic according to belt direction
6474 for (i = 0; i < NUM_BELTS; i++)
6478 for (j = 0; j < NUM_BELT_PARTS; j++)
6480 int element = belt_base_active_element[belt_nr] + j;
6481 int graphic_1 = el2img(element);
6482 int graphic_2 = el2panelimg(element);
6484 if (game.belt_dir[i] == MV_LEFT)
6486 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6487 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6491 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6492 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6497 SCAN_PLAYFIELD(x, y)
6499 int element = Tile[x][y];
6501 for (i = 0; i < NUM_BELTS; i++)
6503 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6505 int e_belt_nr = getBeltNrFromBeltElement(element);
6508 if (e_belt_nr == belt_nr)
6510 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6512 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6519 static void ToggleBeltSwitch(int x, int y)
6521 static int belt_base_element[4] =
6523 EL_CONVEYOR_BELT_1_LEFT,
6524 EL_CONVEYOR_BELT_2_LEFT,
6525 EL_CONVEYOR_BELT_3_LEFT,
6526 EL_CONVEYOR_BELT_4_LEFT
6528 static int belt_base_active_element[4] =
6530 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6531 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6532 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6533 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6535 static int belt_base_switch_element[4] =
6537 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6538 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6539 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6540 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6542 static int belt_move_dir[4] =
6550 int element = Tile[x][y];
6551 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6552 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6553 int belt_dir = belt_move_dir[belt_dir_nr];
6556 if (!IS_BELT_SWITCH(element))
6559 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6560 game.belt_dir[belt_nr] = belt_dir;
6562 if (belt_dir_nr == 3)
6565 // set frame order for belt animation graphic according to belt direction
6566 for (i = 0; i < NUM_BELT_PARTS; i++)
6568 int element = belt_base_active_element[belt_nr] + i;
6569 int graphic_1 = el2img(element);
6570 int graphic_2 = el2panelimg(element);
6572 if (belt_dir == MV_LEFT)
6574 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6575 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6579 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6580 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6584 SCAN_PLAYFIELD(xx, yy)
6586 int element = Tile[xx][yy];
6588 if (IS_BELT_SWITCH(element))
6590 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6592 if (e_belt_nr == belt_nr)
6594 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6595 TEST_DrawLevelField(xx, yy);
6598 else if (IS_BELT(element) && belt_dir != MV_NONE)
6600 int e_belt_nr = getBeltNrFromBeltElement(element);
6602 if (e_belt_nr == belt_nr)
6604 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6606 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6607 TEST_DrawLevelField(xx, yy);
6610 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6612 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6614 if (e_belt_nr == belt_nr)
6616 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6618 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6619 TEST_DrawLevelField(xx, yy);
6625 static void ToggleSwitchgateSwitch(void)
6629 game.switchgate_pos = !game.switchgate_pos;
6631 SCAN_PLAYFIELD(xx, yy)
6633 int element = Tile[xx][yy];
6635 if (element == EL_SWITCHGATE_SWITCH_UP)
6637 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6638 TEST_DrawLevelField(xx, yy);
6640 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6642 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6643 TEST_DrawLevelField(xx, yy);
6645 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6647 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6648 TEST_DrawLevelField(xx, yy);
6650 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6652 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6653 TEST_DrawLevelField(xx, yy);
6655 else if (element == EL_SWITCHGATE_OPEN ||
6656 element == EL_SWITCHGATE_OPENING)
6658 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6660 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6662 else if (element == EL_SWITCHGATE_CLOSED ||
6663 element == EL_SWITCHGATE_CLOSING)
6665 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6667 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6672 static int getInvisibleActiveFromInvisibleElement(int element)
6674 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6675 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6676 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6680 static int getInvisibleFromInvisibleActiveElement(int element)
6682 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6683 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6684 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6688 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6692 SCAN_PLAYFIELD(x, y)
6694 int element = Tile[x][y];
6696 if (element == EL_LIGHT_SWITCH &&
6697 game.light_time_left > 0)
6699 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6700 TEST_DrawLevelField(x, y);
6702 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6703 game.light_time_left == 0)
6705 Tile[x][y] = EL_LIGHT_SWITCH;
6706 TEST_DrawLevelField(x, y);
6708 else if (element == EL_EMC_DRIPPER &&
6709 game.light_time_left > 0)
6711 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6712 TEST_DrawLevelField(x, y);
6714 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6715 game.light_time_left == 0)
6717 Tile[x][y] = EL_EMC_DRIPPER;
6718 TEST_DrawLevelField(x, y);
6720 else if (element == EL_INVISIBLE_STEELWALL ||
6721 element == EL_INVISIBLE_WALL ||
6722 element == EL_INVISIBLE_SAND)
6724 if (game.light_time_left > 0)
6725 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6727 TEST_DrawLevelField(x, y);
6729 // uncrumble neighbour fields, if needed
6730 if (element == EL_INVISIBLE_SAND)
6731 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6733 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6734 element == EL_INVISIBLE_WALL_ACTIVE ||
6735 element == EL_INVISIBLE_SAND_ACTIVE)
6737 if (game.light_time_left == 0)
6738 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6740 TEST_DrawLevelField(x, y);
6742 // re-crumble neighbour fields, if needed
6743 if (element == EL_INVISIBLE_SAND)
6744 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6749 static void RedrawAllInvisibleElementsForLenses(void)
6753 SCAN_PLAYFIELD(x, y)
6755 int element = Tile[x][y];
6757 if (element == EL_EMC_DRIPPER &&
6758 game.lenses_time_left > 0)
6760 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6761 TEST_DrawLevelField(x, y);
6763 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6764 game.lenses_time_left == 0)
6766 Tile[x][y] = EL_EMC_DRIPPER;
6767 TEST_DrawLevelField(x, y);
6769 else if (element == EL_INVISIBLE_STEELWALL ||
6770 element == EL_INVISIBLE_WALL ||
6771 element == EL_INVISIBLE_SAND)
6773 if (game.lenses_time_left > 0)
6774 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6776 TEST_DrawLevelField(x, y);
6778 // uncrumble neighbour fields, if needed
6779 if (element == EL_INVISIBLE_SAND)
6780 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6782 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6783 element == EL_INVISIBLE_WALL_ACTIVE ||
6784 element == EL_INVISIBLE_SAND_ACTIVE)
6786 if (game.lenses_time_left == 0)
6787 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6789 TEST_DrawLevelField(x, y);
6791 // re-crumble neighbour fields, if needed
6792 if (element == EL_INVISIBLE_SAND)
6793 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6798 static void RedrawAllInvisibleElementsForMagnifier(void)
6802 SCAN_PLAYFIELD(x, y)
6804 int element = Tile[x][y];
6806 if (element == EL_EMC_FAKE_GRASS &&
6807 game.magnify_time_left > 0)
6809 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6810 TEST_DrawLevelField(x, y);
6812 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6813 game.magnify_time_left == 0)
6815 Tile[x][y] = EL_EMC_FAKE_GRASS;
6816 TEST_DrawLevelField(x, y);
6818 else if (IS_GATE_GRAY(element) &&
6819 game.magnify_time_left > 0)
6821 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6822 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6823 IS_EM_GATE_GRAY(element) ?
6824 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6825 IS_EMC_GATE_GRAY(element) ?
6826 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6827 IS_DC_GATE_GRAY(element) ?
6828 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6830 TEST_DrawLevelField(x, y);
6832 else if (IS_GATE_GRAY_ACTIVE(element) &&
6833 game.magnify_time_left == 0)
6835 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6836 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6837 IS_EM_GATE_GRAY_ACTIVE(element) ?
6838 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6839 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6840 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6841 IS_DC_GATE_GRAY_ACTIVE(element) ?
6842 EL_DC_GATE_WHITE_GRAY :
6844 TEST_DrawLevelField(x, y);
6849 static void ToggleLightSwitch(int x, int y)
6851 int element = Tile[x][y];
6853 game.light_time_left =
6854 (element == EL_LIGHT_SWITCH ?
6855 level.time_light * FRAMES_PER_SECOND : 0);
6857 RedrawAllLightSwitchesAndInvisibleElements();
6860 static void ActivateTimegateSwitch(int x, int y)
6864 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6866 SCAN_PLAYFIELD(xx, yy)
6868 int element = Tile[xx][yy];
6870 if (element == EL_TIMEGATE_CLOSED ||
6871 element == EL_TIMEGATE_CLOSING)
6873 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6874 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6878 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6880 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6881 TEST_DrawLevelField(xx, yy);
6887 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6888 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6891 static void Impact(int x, int y)
6893 boolean last_line = (y == lev_fieldy - 1);
6894 boolean object_hit = FALSE;
6895 boolean impact = (last_line || object_hit);
6896 int element = Tile[x][y];
6897 int smashed = EL_STEELWALL;
6899 if (!last_line) // check if element below was hit
6901 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6904 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6905 MovDir[x][y + 1] != MV_DOWN ||
6906 MovPos[x][y + 1] <= TILEY / 2));
6908 // do not smash moving elements that left the smashed field in time
6909 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6910 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6913 #if USE_QUICKSAND_IMPACT_BUGFIX
6914 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6916 RemoveMovingField(x, y + 1);
6917 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6918 Tile[x][y + 2] = EL_ROCK;
6919 TEST_DrawLevelField(x, y + 2);
6924 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6926 RemoveMovingField(x, y + 1);
6927 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6928 Tile[x][y + 2] = EL_ROCK;
6929 TEST_DrawLevelField(x, y + 2);
6936 smashed = MovingOrBlocked2Element(x, y + 1);
6938 impact = (last_line || object_hit);
6941 if (!last_line && smashed == EL_ACID) // element falls into acid
6943 SplashAcid(x, y + 1);
6947 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6948 // only reset graphic animation if graphic really changes after impact
6950 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6952 ResetGfxAnimation(x, y);
6953 TEST_DrawLevelField(x, y);
6956 if (impact && CAN_EXPLODE_IMPACT(element))
6961 else if (impact && element == EL_PEARL &&
6962 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6964 ResetGfxAnimation(x, y);
6966 Tile[x][y] = EL_PEARL_BREAKING;
6967 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6970 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6972 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6977 if (impact && element == EL_AMOEBA_DROP)
6979 if (object_hit && IS_PLAYER(x, y + 1))
6980 KillPlayerUnlessEnemyProtected(x, y + 1);
6981 else if (object_hit && smashed == EL_PENGUIN)
6985 Tile[x][y] = EL_AMOEBA_GROWING;
6986 Store[x][y] = EL_AMOEBA_WET;
6988 ResetRandomAnimationValue(x, y);
6993 if (object_hit) // check which object was hit
6995 if ((CAN_PASS_MAGIC_WALL(element) &&
6996 (smashed == EL_MAGIC_WALL ||
6997 smashed == EL_BD_MAGIC_WALL)) ||
6998 (CAN_PASS_DC_MAGIC_WALL(element) &&
6999 smashed == EL_DC_MAGIC_WALL))
7002 int activated_magic_wall =
7003 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
7004 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
7005 EL_DC_MAGIC_WALL_ACTIVE);
7007 // activate magic wall / mill
7008 SCAN_PLAYFIELD(xx, yy)
7010 if (Tile[xx][yy] == smashed)
7011 Tile[xx][yy] = activated_magic_wall;
7014 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
7015 game.magic_wall_active = TRUE;
7017 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
7018 SND_MAGIC_WALL_ACTIVATING :
7019 smashed == EL_BD_MAGIC_WALL ?
7020 SND_BD_MAGIC_WALL_ACTIVATING :
7021 SND_DC_MAGIC_WALL_ACTIVATING));
7024 if (IS_PLAYER(x, y + 1))
7026 if (CAN_SMASH_PLAYER(element))
7028 KillPlayerUnlessEnemyProtected(x, y + 1);
7032 else if (smashed == EL_PENGUIN)
7034 if (CAN_SMASH_PLAYER(element))
7040 else if (element == EL_BD_DIAMOND)
7042 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
7048 else if (((element == EL_SP_INFOTRON ||
7049 element == EL_SP_ZONK) &&
7050 (smashed == EL_SP_SNIKSNAK ||
7051 smashed == EL_SP_ELECTRON ||
7052 smashed == EL_SP_DISK_ORANGE)) ||
7053 (element == EL_SP_INFOTRON &&
7054 smashed == EL_SP_DISK_YELLOW))
7059 else if (CAN_SMASH_EVERYTHING(element))
7061 if (IS_CLASSIC_ENEMY(smashed) ||
7062 CAN_EXPLODE_SMASHED(smashed))
7067 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
7069 if (smashed == EL_LAMP ||
7070 smashed == EL_LAMP_ACTIVE)
7075 else if (smashed == EL_NUT)
7077 Tile[x][y + 1] = EL_NUT_BREAKING;
7078 PlayLevelSound(x, y, SND_NUT_BREAKING);
7079 RaiseScoreElement(EL_NUT);
7082 else if (smashed == EL_PEARL)
7084 ResetGfxAnimation(x, y);
7086 Tile[x][y + 1] = EL_PEARL_BREAKING;
7087 PlayLevelSound(x, y, SND_PEARL_BREAKING);
7090 else if (smashed == EL_DIAMOND)
7092 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
7093 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
7096 else if (IS_BELT_SWITCH(smashed))
7098 ToggleBeltSwitch(x, y + 1);
7100 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
7101 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
7102 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
7103 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
7105 ToggleSwitchgateSwitch();
7107 else if (smashed == EL_LIGHT_SWITCH ||
7108 smashed == EL_LIGHT_SWITCH_ACTIVE)
7110 ToggleLightSwitch(x, y + 1);
7114 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7116 CheckElementChangeBySide(x, y + 1, smashed, element,
7117 CE_SWITCHED, CH_SIDE_TOP);
7118 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
7124 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7129 // play sound of magic wall / mill
7131 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7132 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
7133 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
7135 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7136 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
7137 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7138 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
7139 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7140 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
7145 // play sound of object that hits the ground
7146 if (last_line || object_hit)
7147 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
7150 static void TurnRoundExt(int x, int y)
7162 { 0, 0 }, { 0, 0 }, { 0, 0 },
7167 int left, right, back;
7171 { MV_DOWN, MV_UP, MV_RIGHT },
7172 { MV_UP, MV_DOWN, MV_LEFT },
7174 { MV_LEFT, MV_RIGHT, MV_DOWN },
7178 { MV_RIGHT, MV_LEFT, MV_UP }
7181 int element = Tile[x][y];
7182 int move_pattern = element_info[element].move_pattern;
7184 int old_move_dir = MovDir[x][y];
7185 int left_dir = turn[old_move_dir].left;
7186 int right_dir = turn[old_move_dir].right;
7187 int back_dir = turn[old_move_dir].back;
7189 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
7190 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
7191 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
7192 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
7194 int left_x = x + left_dx, left_y = y + left_dy;
7195 int right_x = x + right_dx, right_y = y + right_dy;
7196 int move_x = x + move_dx, move_y = y + move_dy;
7200 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
7202 TestIfBadThingTouchesOtherBadThing(x, y);
7204 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
7205 MovDir[x][y] = right_dir;
7206 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7207 MovDir[x][y] = left_dir;
7209 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7211 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7214 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7216 TestIfBadThingTouchesOtherBadThing(x, y);
7218 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7219 MovDir[x][y] = left_dir;
7220 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7221 MovDir[x][y] = right_dir;
7223 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7225 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7228 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7230 TestIfBadThingTouchesOtherBadThing(x, y);
7232 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7233 MovDir[x][y] = left_dir;
7234 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7235 MovDir[x][y] = right_dir;
7237 if (MovDir[x][y] != old_move_dir)
7240 else if (element == EL_YAMYAM)
7242 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7243 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7245 if (can_turn_left && can_turn_right)
7246 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7247 else if (can_turn_left)
7248 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7249 else if (can_turn_right)
7250 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7252 MovDir[x][y] = back_dir;
7254 MovDelay[x][y] = 16 + 16 * RND(3);
7256 else if (element == EL_DARK_YAMYAM)
7258 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7260 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7263 if (can_turn_left && can_turn_right)
7264 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7265 else if (can_turn_left)
7266 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7267 else if (can_turn_right)
7268 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7270 MovDir[x][y] = back_dir;
7272 MovDelay[x][y] = 16 + 16 * RND(3);
7274 else if (element == EL_PACMAN)
7276 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7277 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7279 if (can_turn_left && can_turn_right)
7280 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7281 else if (can_turn_left)
7282 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7283 else if (can_turn_right)
7284 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7286 MovDir[x][y] = back_dir;
7288 MovDelay[x][y] = 6 + RND(40);
7290 else if (element == EL_PIG)
7292 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7293 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7294 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7295 boolean should_turn_left, should_turn_right, should_move_on;
7297 int rnd = RND(rnd_value);
7299 should_turn_left = (can_turn_left &&
7301 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7302 y + back_dy + left_dy)));
7303 should_turn_right = (can_turn_right &&
7305 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7306 y + back_dy + right_dy)));
7307 should_move_on = (can_move_on &&
7310 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7311 y + move_dy + left_dy) ||
7312 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7313 y + move_dy + right_dy)));
7315 if (should_turn_left || should_turn_right || should_move_on)
7317 if (should_turn_left && should_turn_right && should_move_on)
7318 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7319 rnd < 2 * rnd_value / 3 ? right_dir :
7321 else if (should_turn_left && should_turn_right)
7322 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7323 else if (should_turn_left && should_move_on)
7324 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7325 else if (should_turn_right && should_move_on)
7326 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7327 else if (should_turn_left)
7328 MovDir[x][y] = left_dir;
7329 else if (should_turn_right)
7330 MovDir[x][y] = right_dir;
7331 else if (should_move_on)
7332 MovDir[x][y] = old_move_dir;
7334 else if (can_move_on && rnd > rnd_value / 8)
7335 MovDir[x][y] = old_move_dir;
7336 else if (can_turn_left && can_turn_right)
7337 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7338 else if (can_turn_left && rnd > rnd_value / 8)
7339 MovDir[x][y] = left_dir;
7340 else if (can_turn_right && rnd > rnd_value/8)
7341 MovDir[x][y] = right_dir;
7343 MovDir[x][y] = back_dir;
7345 xx = x + move_xy[MovDir[x][y]].dx;
7346 yy = y + move_xy[MovDir[x][y]].dy;
7348 if (!IN_LEV_FIELD(xx, yy) ||
7349 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7350 MovDir[x][y] = old_move_dir;
7354 else if (element == EL_DRAGON)
7356 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7357 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7358 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7360 int rnd = RND(rnd_value);
7362 if (can_move_on && rnd > rnd_value / 8)
7363 MovDir[x][y] = old_move_dir;
7364 else if (can_turn_left && can_turn_right)
7365 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7366 else if (can_turn_left && rnd > rnd_value / 8)
7367 MovDir[x][y] = left_dir;
7368 else if (can_turn_right && rnd > rnd_value / 8)
7369 MovDir[x][y] = right_dir;
7371 MovDir[x][y] = back_dir;
7373 xx = x + move_xy[MovDir[x][y]].dx;
7374 yy = y + move_xy[MovDir[x][y]].dy;
7376 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7377 MovDir[x][y] = old_move_dir;
7381 else if (element == EL_MOLE)
7383 boolean can_move_on =
7384 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7385 IS_AMOEBOID(Tile[move_x][move_y]) ||
7386 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7389 boolean can_turn_left =
7390 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7391 IS_AMOEBOID(Tile[left_x][left_y])));
7393 boolean can_turn_right =
7394 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7395 IS_AMOEBOID(Tile[right_x][right_y])));
7397 if (can_turn_left && can_turn_right)
7398 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7399 else if (can_turn_left)
7400 MovDir[x][y] = left_dir;
7402 MovDir[x][y] = right_dir;
7405 if (MovDir[x][y] != old_move_dir)
7408 else if (element == EL_BALLOON)
7410 MovDir[x][y] = game.wind_direction;
7413 else if (element == EL_SPRING)
7415 if (MovDir[x][y] & MV_HORIZONTAL)
7417 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7418 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7420 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7421 ResetGfxAnimation(move_x, move_y);
7422 TEST_DrawLevelField(move_x, move_y);
7424 MovDir[x][y] = back_dir;
7426 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7427 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7428 MovDir[x][y] = MV_NONE;
7433 else if (element == EL_ROBOT ||
7434 element == EL_SATELLITE ||
7435 element == EL_PENGUIN ||
7436 element == EL_EMC_ANDROID)
7438 int attr_x = -1, attr_y = -1;
7440 if (game.all_players_gone)
7442 attr_x = game.exit_x;
7443 attr_y = game.exit_y;
7449 for (i = 0; i < MAX_PLAYERS; i++)
7451 struct PlayerInfo *player = &stored_player[i];
7452 int jx = player->jx, jy = player->jy;
7454 if (!player->active)
7458 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7466 if (element == EL_ROBOT &&
7467 game.robot_wheel_x >= 0 &&
7468 game.robot_wheel_y >= 0 &&
7469 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7470 game.engine_version < VERSION_IDENT(3,1,0,0)))
7472 attr_x = game.robot_wheel_x;
7473 attr_y = game.robot_wheel_y;
7476 if (element == EL_PENGUIN)
7479 struct XY *xy = xy_topdown;
7481 for (i = 0; i < NUM_DIRECTIONS; i++)
7483 int ex = x + xy[i].x;
7484 int ey = y + xy[i].y;
7486 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7487 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7488 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7489 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7498 MovDir[x][y] = MV_NONE;
7500 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7501 else if (attr_x > x)
7502 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7504 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7505 else if (attr_y > y)
7506 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7508 if (element == EL_ROBOT)
7512 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7513 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7514 Moving2Blocked(x, y, &newx, &newy);
7516 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7517 MovDelay[x][y] = 8 + 8 * !RND(3);
7519 MovDelay[x][y] = 16;
7521 else if (element == EL_PENGUIN)
7527 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7529 boolean first_horiz = RND(2);
7530 int new_move_dir = MovDir[x][y];
7533 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7534 Moving2Blocked(x, y, &newx, &newy);
7536 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7540 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7541 Moving2Blocked(x, y, &newx, &newy);
7543 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7546 MovDir[x][y] = old_move_dir;
7550 else if (element == EL_SATELLITE)
7556 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7558 boolean first_horiz = RND(2);
7559 int new_move_dir = MovDir[x][y];
7562 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7563 Moving2Blocked(x, y, &newx, &newy);
7565 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7569 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7570 Moving2Blocked(x, y, &newx, &newy);
7572 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7575 MovDir[x][y] = old_move_dir;
7579 else if (element == EL_EMC_ANDROID)
7581 static int check_pos[16] =
7583 -1, // 0 => (invalid)
7586 -1, // 3 => (invalid)
7588 0, // 5 => MV_LEFT | MV_UP
7589 2, // 6 => MV_RIGHT | MV_UP
7590 -1, // 7 => (invalid)
7592 6, // 9 => MV_LEFT | MV_DOWN
7593 4, // 10 => MV_RIGHT | MV_DOWN
7594 -1, // 11 => (invalid)
7595 -1, // 12 => (invalid)
7596 -1, // 13 => (invalid)
7597 -1, // 14 => (invalid)
7598 -1, // 15 => (invalid)
7606 { -1, -1, MV_LEFT | MV_UP },
7608 { +1, -1, MV_RIGHT | MV_UP },
7609 { +1, 0, MV_RIGHT },
7610 { +1, +1, MV_RIGHT | MV_DOWN },
7612 { -1, +1, MV_LEFT | MV_DOWN },
7615 int start_pos, check_order;
7616 boolean can_clone = FALSE;
7619 // check if there is any free field around current position
7620 for (i = 0; i < 8; i++)
7622 int newx = x + check_xy[i].dx;
7623 int newy = y + check_xy[i].dy;
7625 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7633 if (can_clone) // randomly find an element to clone
7637 start_pos = check_pos[RND(8)];
7638 check_order = (RND(2) ? -1 : +1);
7640 for (i = 0; i < 8; i++)
7642 int pos_raw = start_pos + i * check_order;
7643 int pos = (pos_raw + 8) % 8;
7644 int newx = x + check_xy[pos].dx;
7645 int newy = y + check_xy[pos].dy;
7647 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7649 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7650 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7652 Store[x][y] = Tile[newx][newy];
7661 if (can_clone) // randomly find a direction to move
7665 start_pos = check_pos[RND(8)];
7666 check_order = (RND(2) ? -1 : +1);
7668 for (i = 0; i < 8; i++)
7670 int pos_raw = start_pos + i * check_order;
7671 int pos = (pos_raw + 8) % 8;
7672 int newx = x + check_xy[pos].dx;
7673 int newy = y + check_xy[pos].dy;
7674 int new_move_dir = check_xy[pos].dir;
7676 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7678 MovDir[x][y] = new_move_dir;
7679 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7688 if (can_clone) // cloning and moving successful
7691 // cannot clone -- try to move towards player
7693 start_pos = check_pos[MovDir[x][y] & 0x0f];
7694 check_order = (RND(2) ? -1 : +1);
7696 for (i = 0; i < 3; i++)
7698 // first check start_pos, then previous/next or (next/previous) pos
7699 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7700 int pos = (pos_raw + 8) % 8;
7701 int newx = x + check_xy[pos].dx;
7702 int newy = y + check_xy[pos].dy;
7703 int new_move_dir = check_xy[pos].dir;
7705 if (IS_PLAYER(newx, newy))
7708 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7710 MovDir[x][y] = new_move_dir;
7711 MovDelay[x][y] = level.android_move_time * 8 + 1;
7718 else if (move_pattern == MV_TURNING_LEFT ||
7719 move_pattern == MV_TURNING_RIGHT ||
7720 move_pattern == MV_TURNING_LEFT_RIGHT ||
7721 move_pattern == MV_TURNING_RIGHT_LEFT ||
7722 move_pattern == MV_TURNING_RANDOM ||
7723 move_pattern == MV_ALL_DIRECTIONS)
7725 boolean can_turn_left =
7726 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7727 boolean can_turn_right =
7728 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y);
7730 if (element_info[element].move_stepsize == 0) // "not moving"
7733 if (move_pattern == MV_TURNING_LEFT)
7734 MovDir[x][y] = left_dir;
7735 else if (move_pattern == MV_TURNING_RIGHT)
7736 MovDir[x][y] = right_dir;
7737 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7738 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7739 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7740 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7741 else if (move_pattern == MV_TURNING_RANDOM)
7742 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7743 can_turn_right && !can_turn_left ? right_dir :
7744 RND(2) ? left_dir : right_dir);
7745 else if (can_turn_left && can_turn_right)
7746 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7747 else if (can_turn_left)
7748 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7749 else if (can_turn_right)
7750 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7752 MovDir[x][y] = back_dir;
7754 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7756 else if (move_pattern == MV_HORIZONTAL ||
7757 move_pattern == MV_VERTICAL)
7759 if (move_pattern & old_move_dir)
7760 MovDir[x][y] = back_dir;
7761 else if (move_pattern == MV_HORIZONTAL)
7762 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7763 else if (move_pattern == MV_VERTICAL)
7764 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7766 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7768 else if (move_pattern & MV_ANY_DIRECTION)
7770 MovDir[x][y] = move_pattern;
7771 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7773 else if (move_pattern & MV_WIND_DIRECTION)
7775 MovDir[x][y] = game.wind_direction;
7776 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7778 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7780 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7781 MovDir[x][y] = left_dir;
7782 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7783 MovDir[x][y] = right_dir;
7785 if (MovDir[x][y] != old_move_dir)
7786 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7788 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7790 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7791 MovDir[x][y] = right_dir;
7792 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7793 MovDir[x][y] = left_dir;
7795 if (MovDir[x][y] != old_move_dir)
7796 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7798 else if (move_pattern == MV_TOWARDS_PLAYER ||
7799 move_pattern == MV_AWAY_FROM_PLAYER)
7801 int attr_x = -1, attr_y = -1;
7803 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7805 if (game.all_players_gone)
7807 attr_x = game.exit_x;
7808 attr_y = game.exit_y;
7814 for (i = 0; i < MAX_PLAYERS; i++)
7816 struct PlayerInfo *player = &stored_player[i];
7817 int jx = player->jx, jy = player->jy;
7819 if (!player->active)
7823 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7831 MovDir[x][y] = MV_NONE;
7833 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7834 else if (attr_x > x)
7835 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7837 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7838 else if (attr_y > y)
7839 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7841 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7843 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7845 boolean first_horiz = RND(2);
7846 int new_move_dir = MovDir[x][y];
7848 if (element_info[element].move_stepsize == 0) // "not moving"
7850 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7851 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7857 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7858 Moving2Blocked(x, y, &newx, &newy);
7860 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7864 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7865 Moving2Blocked(x, y, &newx, &newy);
7867 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7870 MovDir[x][y] = old_move_dir;
7873 else if (move_pattern == MV_WHEN_PUSHED ||
7874 move_pattern == MV_WHEN_DROPPED)
7876 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7877 MovDir[x][y] = MV_NONE;
7881 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7883 struct XY *test_xy = xy_topdown;
7884 static int test_dir[4] =
7891 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7892 int move_preference = -1000000; // start with very low preference
7893 int new_move_dir = MV_NONE;
7894 int start_test = RND(4);
7897 for (i = 0; i < NUM_DIRECTIONS; i++)
7899 int j = (start_test + i) % 4;
7900 int move_dir = test_dir[j];
7901 int move_dir_preference;
7903 xx = x + test_xy[j].x;
7904 yy = y + test_xy[j].y;
7906 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7907 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7909 new_move_dir = move_dir;
7914 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7917 move_dir_preference = -1 * RunnerVisit[xx][yy];
7918 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7919 move_dir_preference = PlayerVisit[xx][yy];
7921 if (move_dir_preference > move_preference)
7923 // prefer field that has not been visited for the longest time
7924 move_preference = move_dir_preference;
7925 new_move_dir = move_dir;
7927 else if (move_dir_preference == move_preference &&
7928 move_dir == old_move_dir)
7930 // prefer last direction when all directions are preferred equally
7931 move_preference = move_dir_preference;
7932 new_move_dir = move_dir;
7936 MovDir[x][y] = new_move_dir;
7937 if (old_move_dir != new_move_dir)
7938 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7942 static void TurnRound(int x, int y)
7944 int direction = MovDir[x][y];
7948 GfxDir[x][y] = MovDir[x][y];
7950 if (direction != MovDir[x][y])
7954 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7956 ResetGfxFrame(x, y);
7959 static boolean JustBeingPushed(int x, int y)
7963 for (i = 0; i < MAX_PLAYERS; i++)
7965 struct PlayerInfo *player = &stored_player[i];
7967 if (player->active && player->is_pushing && player->MovPos)
7969 int next_jx = player->jx + (player->jx - player->last_jx);
7970 int next_jy = player->jy + (player->jy - player->last_jy);
7972 if (x == next_jx && y == next_jy)
7980 static void StartMoving(int x, int y)
7982 boolean started_moving = FALSE; // some elements can fall _and_ move
7983 int element = Tile[x][y];
7988 if (MovDelay[x][y] == 0)
7989 GfxAction[x][y] = ACTION_DEFAULT;
7991 if (CAN_FALL(element) && y < lev_fieldy - 1)
7993 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7994 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7995 if (JustBeingPushed(x, y))
7998 if (element == EL_QUICKSAND_FULL)
8000 if (IS_FREE(x, y + 1))
8002 InitMovingField(x, y, MV_DOWN);
8003 started_moving = TRUE;
8005 Tile[x][y] = EL_QUICKSAND_EMPTYING;
8006 #if USE_QUICKSAND_BD_ROCK_BUGFIX
8007 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
8008 Store[x][y] = EL_ROCK;
8010 Store[x][y] = EL_ROCK;
8013 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
8015 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8017 if (!MovDelay[x][y])
8019 MovDelay[x][y] = TILEY + 1;
8021 ResetGfxAnimation(x, y);
8022 ResetGfxAnimation(x, y + 1);
8027 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
8028 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8035 Tile[x][y] = EL_QUICKSAND_EMPTY;
8036 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8037 Store[x][y + 1] = Store[x][y];
8040 PlayLevelSoundAction(x, y, ACTION_FILLING);
8042 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8044 if (!MovDelay[x][y])
8046 MovDelay[x][y] = TILEY + 1;
8048 ResetGfxAnimation(x, y);
8049 ResetGfxAnimation(x, y + 1);
8054 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
8055 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8062 Tile[x][y] = EL_QUICKSAND_EMPTY;
8063 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8064 Store[x][y + 1] = Store[x][y];
8067 PlayLevelSoundAction(x, y, ACTION_FILLING);
8070 else if (element == EL_QUICKSAND_FAST_FULL)
8072 if (IS_FREE(x, y + 1))
8074 InitMovingField(x, y, MV_DOWN);
8075 started_moving = TRUE;
8077 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
8078 #if USE_QUICKSAND_BD_ROCK_BUGFIX
8079 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
8080 Store[x][y] = EL_ROCK;
8082 Store[x][y] = EL_ROCK;
8085 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
8087 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8089 if (!MovDelay[x][y])
8091 MovDelay[x][y] = TILEY + 1;
8093 ResetGfxAnimation(x, y);
8094 ResetGfxAnimation(x, y + 1);
8099 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8100 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8107 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8108 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8109 Store[x][y + 1] = Store[x][y];
8112 PlayLevelSoundAction(x, y, ACTION_FILLING);
8114 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8116 if (!MovDelay[x][y])
8118 MovDelay[x][y] = TILEY + 1;
8120 ResetGfxAnimation(x, y);
8121 ResetGfxAnimation(x, y + 1);
8126 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8127 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8134 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8135 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8136 Store[x][y + 1] = Store[x][y];
8139 PlayLevelSoundAction(x, y, ACTION_FILLING);
8142 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8143 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8145 InitMovingField(x, y, MV_DOWN);
8146 started_moving = TRUE;
8148 Tile[x][y] = EL_QUICKSAND_FILLING;
8149 Store[x][y] = element;
8151 PlayLevelSoundAction(x, y, ACTION_FILLING);
8153 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8154 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8156 InitMovingField(x, y, MV_DOWN);
8157 started_moving = TRUE;
8159 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
8160 Store[x][y] = element;
8162 PlayLevelSoundAction(x, y, ACTION_FILLING);
8164 else if (element == EL_MAGIC_WALL_FULL)
8166 if (IS_FREE(x, y + 1))
8168 InitMovingField(x, y, MV_DOWN);
8169 started_moving = TRUE;
8171 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
8172 Store[x][y] = EL_CHANGED(Store[x][y]);
8174 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
8176 if (!MovDelay[x][y])
8177 MovDelay[x][y] = TILEY / 4 + 1;
8186 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
8187 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
8188 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8192 else if (element == EL_BD_MAGIC_WALL_FULL)
8194 if (IS_FREE(x, y + 1))
8196 InitMovingField(x, y, MV_DOWN);
8197 started_moving = TRUE;
8199 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8200 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8202 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8204 if (!MovDelay[x][y])
8205 MovDelay[x][y] = TILEY / 4 + 1;
8214 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8215 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8216 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8220 else if (element == EL_DC_MAGIC_WALL_FULL)
8222 if (IS_FREE(x, y + 1))
8224 InitMovingField(x, y, MV_DOWN);
8225 started_moving = TRUE;
8227 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8228 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8230 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8232 if (!MovDelay[x][y])
8233 MovDelay[x][y] = TILEY / 4 + 1;
8242 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8243 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8244 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8248 else if ((CAN_PASS_MAGIC_WALL(element) &&
8249 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8250 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8251 (CAN_PASS_DC_MAGIC_WALL(element) &&
8252 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8255 InitMovingField(x, y, MV_DOWN);
8256 started_moving = TRUE;
8259 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8260 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8261 EL_DC_MAGIC_WALL_FILLING);
8262 Store[x][y] = element;
8264 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8266 SplashAcid(x, y + 1);
8268 InitMovingField(x, y, MV_DOWN);
8269 started_moving = TRUE;
8271 Store[x][y] = EL_ACID;
8274 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8275 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8276 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8277 CAN_FALL(element) && WasJustFalling[x][y] &&
8278 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8280 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8281 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8282 (Tile[x][y + 1] == EL_BLOCKED)))
8284 /* this is needed for a special case not covered by calling "Impact()"
8285 from "ContinueMoving()": if an element moves to a tile directly below
8286 another element which was just falling on that tile (which was empty
8287 in the previous frame), the falling element above would just stop
8288 instead of smashing the element below (in previous version, the above
8289 element was just checked for "moving" instead of "falling", resulting
8290 in incorrect smashes caused by horizontal movement of the above
8291 element; also, the case of the player being the element to smash was
8292 simply not covered here... :-/ ) */
8294 CheckCollision[x][y] = 0;
8295 CheckImpact[x][y] = 0;
8299 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8301 if (MovDir[x][y] == MV_NONE)
8303 InitMovingField(x, y, MV_DOWN);
8304 started_moving = TRUE;
8307 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8309 if (WasJustFalling[x][y]) // prevent animation from being restarted
8310 MovDir[x][y] = MV_DOWN;
8312 InitMovingField(x, y, MV_DOWN);
8313 started_moving = TRUE;
8315 else if (element == EL_AMOEBA_DROP)
8317 Tile[x][y] = EL_AMOEBA_GROWING;
8318 Store[x][y] = EL_AMOEBA_WET;
8320 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8321 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8322 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8323 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8325 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8326 (IS_FREE(x - 1, y + 1) ||
8327 Tile[x - 1][y + 1] == EL_ACID));
8328 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8329 (IS_FREE(x + 1, y + 1) ||
8330 Tile[x + 1][y + 1] == EL_ACID));
8331 boolean can_fall_any = (can_fall_left || can_fall_right);
8332 boolean can_fall_both = (can_fall_left && can_fall_right);
8333 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8335 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8337 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8338 can_fall_right = FALSE;
8339 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8340 can_fall_left = FALSE;
8341 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8342 can_fall_right = FALSE;
8343 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8344 can_fall_left = FALSE;
8346 can_fall_any = (can_fall_left || can_fall_right);
8347 can_fall_both = FALSE;
8352 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8353 can_fall_right = FALSE; // slip down on left side
8355 can_fall_left = !(can_fall_right = RND(2));
8357 can_fall_both = FALSE;
8362 // if not determined otherwise, prefer left side for slipping down
8363 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8364 started_moving = TRUE;
8367 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8369 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8370 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8371 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8372 int belt_dir = game.belt_dir[belt_nr];
8374 if ((belt_dir == MV_LEFT && left_is_free) ||
8375 (belt_dir == MV_RIGHT && right_is_free))
8377 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8379 InitMovingField(x, y, belt_dir);
8380 started_moving = TRUE;
8382 Pushed[x][y] = TRUE;
8383 Pushed[nextx][y] = TRUE;
8385 GfxAction[x][y] = ACTION_DEFAULT;
8389 MovDir[x][y] = 0; // if element was moving, stop it
8394 // not "else if" because of elements that can fall and move (EL_SPRING)
8395 if (CAN_MOVE(element) && !started_moving)
8397 int move_pattern = element_info[element].move_pattern;
8400 Moving2Blocked(x, y, &newx, &newy);
8402 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8405 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8406 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8408 WasJustMoving[x][y] = 0;
8409 CheckCollision[x][y] = 0;
8411 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8413 if (Tile[x][y] != element) // element has changed
8417 if (!MovDelay[x][y]) // start new movement phase
8419 // all objects that can change their move direction after each step
8420 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8422 if (element != EL_YAMYAM &&
8423 element != EL_DARK_YAMYAM &&
8424 element != EL_PACMAN &&
8425 !(move_pattern & MV_ANY_DIRECTION) &&
8426 move_pattern != MV_TURNING_LEFT &&
8427 move_pattern != MV_TURNING_RIGHT &&
8428 move_pattern != MV_TURNING_LEFT_RIGHT &&
8429 move_pattern != MV_TURNING_RIGHT_LEFT &&
8430 move_pattern != MV_TURNING_RANDOM)
8434 if (MovDelay[x][y] && (element == EL_BUG ||
8435 element == EL_SPACESHIP ||
8436 element == EL_SP_SNIKSNAK ||
8437 element == EL_SP_ELECTRON ||
8438 element == EL_MOLE))
8439 TEST_DrawLevelField(x, y);
8443 if (MovDelay[x][y]) // wait some time before next movement
8447 if (element == EL_ROBOT ||
8448 element == EL_YAMYAM ||
8449 element == EL_DARK_YAMYAM)
8451 DrawLevelElementAnimationIfNeeded(x, y, element);
8452 PlayLevelSoundAction(x, y, ACTION_WAITING);
8454 else if (element == EL_SP_ELECTRON)
8455 DrawLevelElementAnimationIfNeeded(x, y, element);
8456 else if (element == EL_DRAGON)
8459 int dir = MovDir[x][y];
8460 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8461 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8462 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8463 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8464 dir == MV_UP ? IMG_FLAMES_1_UP :
8465 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8466 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8468 GfxAction[x][y] = ACTION_ATTACKING;
8470 if (IS_PLAYER(x, y))
8471 DrawPlayerField(x, y);
8473 TEST_DrawLevelField(x, y);
8475 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8477 for (i = 1; i <= 3; i++)
8479 int xx = x + i * dx;
8480 int yy = y + i * dy;
8481 int sx = SCREENX(xx);
8482 int sy = SCREENY(yy);
8483 int flame_graphic = graphic + (i - 1);
8485 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8490 int flamed = MovingOrBlocked2Element(xx, yy);
8492 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8495 RemoveMovingField(xx, yy);
8497 ChangeDelay[xx][yy] = 0;
8499 Tile[xx][yy] = EL_FLAMES;
8501 if (IN_SCR_FIELD(sx, sy))
8503 TEST_DrawLevelFieldCrumbled(xx, yy);
8504 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8509 if (Tile[xx][yy] == EL_FLAMES)
8510 Tile[xx][yy] = EL_EMPTY;
8511 TEST_DrawLevelField(xx, yy);
8516 if (MovDelay[x][y]) // element still has to wait some time
8518 PlayLevelSoundAction(x, y, ACTION_WAITING);
8524 // now make next step
8526 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8528 if (DONT_COLLIDE_WITH(element) &&
8529 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8530 !PLAYER_ENEMY_PROTECTED(newx, newy))
8532 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8537 else if (CAN_MOVE_INTO_ACID(element) &&
8538 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8539 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8540 (MovDir[x][y] == MV_DOWN ||
8541 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8543 SplashAcid(newx, newy);
8544 Store[x][y] = EL_ACID;
8546 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8548 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8549 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8550 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8551 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8554 TEST_DrawLevelField(x, y);
8556 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8557 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8558 DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0);
8560 game.friends_still_needed--;
8561 if (!game.friends_still_needed &&
8563 game.all_players_gone)
8568 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8570 if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING)
8571 TEST_DrawLevelField(newx, newy);
8573 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8575 else if (!IS_FREE(newx, newy))
8577 GfxAction[x][y] = ACTION_WAITING;
8579 if (IS_PLAYER(x, y))
8580 DrawPlayerField(x, y);
8582 TEST_DrawLevelField(x, y);
8587 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8589 if (IS_FOOD_PIG(Tile[newx][newy]))
8591 if (IS_MOVING(newx, newy))
8592 RemoveMovingField(newx, newy);
8595 Tile[newx][newy] = EL_EMPTY;
8596 TEST_DrawLevelField(newx, newy);
8599 PlayLevelSound(x, y, SND_PIG_DIGGING);
8601 else if (!IS_FREE(newx, newy))
8603 if (IS_PLAYER(x, y))
8604 DrawPlayerField(x, y);
8606 TEST_DrawLevelField(x, y);
8611 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8613 if (Store[x][y] != EL_EMPTY)
8615 boolean can_clone = FALSE;
8618 // check if element to clone is still there
8619 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8621 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8629 // cannot clone or target field not free anymore -- do not clone
8630 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8631 Store[x][y] = EL_EMPTY;
8634 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8636 if (IS_MV_DIAGONAL(MovDir[x][y]))
8638 int diagonal_move_dir = MovDir[x][y];
8639 int stored = Store[x][y];
8640 int change_delay = 8;
8643 // android is moving diagonally
8645 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8647 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8648 GfxElement[x][y] = EL_EMC_ANDROID;
8649 GfxAction[x][y] = ACTION_SHRINKING;
8650 GfxDir[x][y] = diagonal_move_dir;
8651 ChangeDelay[x][y] = change_delay;
8653 if (Store[x][y] == EL_EMPTY)
8654 Store[x][y] = GfxElementEmpty[x][y];
8656 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8659 DrawLevelGraphicAnimation(x, y, graphic);
8660 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8662 if (Tile[newx][newy] == EL_ACID)
8664 SplashAcid(newx, newy);
8669 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8671 Store[newx][newy] = EL_EMC_ANDROID;
8672 GfxElement[newx][newy] = EL_EMC_ANDROID;
8673 GfxAction[newx][newy] = ACTION_GROWING;
8674 GfxDir[newx][newy] = diagonal_move_dir;
8675 ChangeDelay[newx][newy] = change_delay;
8677 graphic = el_act_dir2img(GfxElement[newx][newy],
8678 GfxAction[newx][newy], GfxDir[newx][newy]);
8680 DrawLevelGraphicAnimation(newx, newy, graphic);
8681 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8687 Tile[newx][newy] = EL_EMPTY;
8688 TEST_DrawLevelField(newx, newy);
8690 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8693 else if (!IS_FREE(newx, newy))
8698 else if (IS_CUSTOM_ELEMENT(element) &&
8699 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8701 if (!DigFieldByCE(newx, newy, element))
8704 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8706 RunnerVisit[x][y] = FrameCounter;
8707 PlayerVisit[x][y] /= 8; // expire player visit path
8710 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8712 if (!IS_FREE(newx, newy))
8714 if (IS_PLAYER(x, y))
8715 DrawPlayerField(x, y);
8717 TEST_DrawLevelField(x, y);
8723 boolean wanna_flame = !RND(10);
8724 int dx = newx - x, dy = newy - y;
8725 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8726 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8727 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8728 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8729 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8730 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8733 IS_CLASSIC_ENEMY(element1) ||
8734 IS_CLASSIC_ENEMY(element2)) &&
8735 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8736 element1 != EL_FLAMES && element2 != EL_FLAMES)
8738 ResetGfxAnimation(x, y);
8739 GfxAction[x][y] = ACTION_ATTACKING;
8741 if (IS_PLAYER(x, y))
8742 DrawPlayerField(x, y);
8744 TEST_DrawLevelField(x, y);
8746 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8748 MovDelay[x][y] = 50;
8750 Tile[newx][newy] = EL_FLAMES;
8751 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8752 Tile[newx1][newy1] = EL_FLAMES;
8753 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8754 Tile[newx2][newy2] = EL_FLAMES;
8760 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8761 Tile[newx][newy] == EL_DIAMOND)
8763 if (IS_MOVING(newx, newy))
8764 RemoveMovingField(newx, newy);
8767 Tile[newx][newy] = EL_EMPTY;
8768 TEST_DrawLevelField(newx, newy);
8771 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8773 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8774 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8776 if (AmoebaNr[newx][newy])
8778 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8779 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8780 Tile[newx][newy] == EL_BD_AMOEBA)
8781 AmoebaCnt[AmoebaNr[newx][newy]]--;
8784 if (IS_MOVING(newx, newy))
8786 RemoveMovingField(newx, newy);
8790 Tile[newx][newy] = EL_EMPTY;
8791 TEST_DrawLevelField(newx, newy);
8794 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8796 else if ((element == EL_PACMAN || element == EL_MOLE)
8797 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8799 if (AmoebaNr[newx][newy])
8801 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8802 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8803 Tile[newx][newy] == EL_BD_AMOEBA)
8804 AmoebaCnt[AmoebaNr[newx][newy]]--;
8807 if (element == EL_MOLE)
8809 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8810 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8812 ResetGfxAnimation(x, y);
8813 GfxAction[x][y] = ACTION_DIGGING;
8814 TEST_DrawLevelField(x, y);
8816 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8818 return; // wait for shrinking amoeba
8820 else // element == EL_PACMAN
8822 Tile[newx][newy] = EL_EMPTY;
8823 TEST_DrawLevelField(newx, newy);
8824 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8827 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8828 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8829 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8831 // wait for shrinking amoeba to completely disappear
8834 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8836 // object was running against a wall
8840 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8841 DrawLevelElementAnimation(x, y, element);
8843 if (DONT_TOUCH(element))
8844 TestIfBadThingTouchesPlayer(x, y);
8849 InitMovingField(x, y, MovDir[x][y]);
8851 PlayLevelSoundAction(x, y, ACTION_MOVING);
8855 ContinueMoving(x, y);
8858 void ContinueMoving(int x, int y)
8860 int element = Tile[x][y];
8861 struct ElementInfo *ei = &element_info[element];
8862 int direction = MovDir[x][y];
8863 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8864 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8865 int newx = x + dx, newy = y + dy;
8866 int stored = Store[x][y];
8867 int stored_new = Store[newx][newy];
8868 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8869 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8870 boolean last_line = (newy == lev_fieldy - 1);
8871 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8873 if (pushed_by_player) // special case: moving object pushed by player
8875 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos));
8877 else if (use_step_delay) // special case: moving object has step delay
8879 if (!MovDelay[x][y])
8880 MovPos[x][y] += getElementMoveStepsize(x, y);
8885 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8889 TEST_DrawLevelField(x, y);
8891 return; // element is still waiting
8894 else // normal case: generically moving object
8896 MovPos[x][y] += getElementMoveStepsize(x, y);
8899 if (ABS(MovPos[x][y]) < TILEX)
8901 TEST_DrawLevelField(x, y);
8903 return; // element is still moving
8906 // element reached destination field
8908 Tile[x][y] = EL_EMPTY;
8909 Tile[newx][newy] = element;
8910 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8912 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8914 element = Tile[newx][newy] = EL_ACID;
8916 else if (element == EL_MOLE)
8918 Tile[x][y] = EL_SAND;
8920 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8922 else if (element == EL_QUICKSAND_FILLING)
8924 element = Tile[newx][newy] = get_next_element(element);
8925 Store[newx][newy] = Store[x][y];
8927 else if (element == EL_QUICKSAND_EMPTYING)
8929 Tile[x][y] = get_next_element(element);
8930 element = Tile[newx][newy] = Store[x][y];
8932 else if (element == EL_QUICKSAND_FAST_FILLING)
8934 element = Tile[newx][newy] = get_next_element(element);
8935 Store[newx][newy] = Store[x][y];
8937 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8939 Tile[x][y] = get_next_element(element);
8940 element = Tile[newx][newy] = Store[x][y];
8942 else if (element == EL_MAGIC_WALL_FILLING)
8944 element = Tile[newx][newy] = get_next_element(element);
8945 if (!game.magic_wall_active)
8946 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8947 Store[newx][newy] = Store[x][y];
8949 else if (element == EL_MAGIC_WALL_EMPTYING)
8951 Tile[x][y] = get_next_element(element);
8952 if (!game.magic_wall_active)
8953 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8954 element = Tile[newx][newy] = Store[x][y];
8956 InitField(newx, newy, FALSE);
8958 else if (element == EL_BD_MAGIC_WALL_FILLING)
8960 element = Tile[newx][newy] = get_next_element(element);
8961 if (!game.magic_wall_active)
8962 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8963 Store[newx][newy] = Store[x][y];
8965 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8967 Tile[x][y] = get_next_element(element);
8968 if (!game.magic_wall_active)
8969 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8970 element = Tile[newx][newy] = Store[x][y];
8972 InitField(newx, newy, FALSE);
8974 else if (element == EL_DC_MAGIC_WALL_FILLING)
8976 element = Tile[newx][newy] = get_next_element(element);
8977 if (!game.magic_wall_active)
8978 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8979 Store[newx][newy] = Store[x][y];
8981 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8983 Tile[x][y] = get_next_element(element);
8984 if (!game.magic_wall_active)
8985 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8986 element = Tile[newx][newy] = Store[x][y];
8988 InitField(newx, newy, FALSE);
8990 else if (element == EL_AMOEBA_DROPPING)
8992 Tile[x][y] = get_next_element(element);
8993 element = Tile[newx][newy] = Store[x][y];
8995 else if (element == EL_SOKOBAN_OBJECT)
8998 Tile[x][y] = Back[x][y];
9000 if (Back[newx][newy])
9001 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
9003 Back[x][y] = Back[newx][newy] = 0;
9006 Store[x][y] = EL_EMPTY;
9011 MovDelay[newx][newy] = 0;
9013 if (CAN_CHANGE_OR_HAS_ACTION(element))
9015 // copy element change control values to new field
9016 ChangeDelay[newx][newy] = ChangeDelay[x][y];
9017 ChangePage[newx][newy] = ChangePage[x][y];
9018 ChangeCount[newx][newy] = ChangeCount[x][y];
9019 ChangeEvent[newx][newy] = ChangeEvent[x][y];
9022 CustomValue[newx][newy] = CustomValue[x][y];
9024 ChangeDelay[x][y] = 0;
9025 ChangePage[x][y] = -1;
9026 ChangeCount[x][y] = 0;
9027 ChangeEvent[x][y] = -1;
9029 CustomValue[x][y] = 0;
9031 // copy animation control values to new field
9032 GfxFrame[newx][newy] = GfxFrame[x][y];
9033 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
9034 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
9035 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
9037 Pushed[x][y] = Pushed[newx][newy] = FALSE;
9039 // some elements can leave other elements behind after moving
9040 if (ei->move_leave_element != EL_EMPTY &&
9041 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
9042 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
9044 int move_leave_element = ei->move_leave_element;
9046 // this makes it possible to leave the removed element again
9047 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
9048 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
9050 Tile[x][y] = move_leave_element;
9052 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
9053 MovDir[x][y] = direction;
9055 InitField(x, y, FALSE);
9057 if (GFX_CRUMBLED(Tile[x][y]))
9058 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
9060 if (IS_PLAYER_ELEMENT(move_leave_element))
9061 RelocatePlayer(x, y, move_leave_element);
9064 // do this after checking for left-behind element
9065 ResetGfxAnimation(x, y); // reset animation values for old field
9067 if (!CAN_MOVE(element) ||
9068 (CAN_FALL(element) && direction == MV_DOWN &&
9069 (element == EL_SPRING ||
9070 element_info[element].move_pattern == MV_WHEN_PUSHED ||
9071 element_info[element].move_pattern == MV_WHEN_DROPPED)))
9072 GfxDir[x][y] = MovDir[newx][newy] = 0;
9074 TEST_DrawLevelField(x, y);
9075 TEST_DrawLevelField(newx, newy);
9077 Stop[newx][newy] = TRUE; // ignore this element until the next frame
9079 // prevent pushed element from moving on in pushed direction
9080 if (pushed_by_player && CAN_MOVE(element) &&
9081 element_info[element].move_pattern & MV_ANY_DIRECTION &&
9082 !(element_info[element].move_pattern & direction))
9083 TurnRound(newx, newy);
9085 // prevent elements on conveyor belt from moving on in last direction
9086 if (pushed_by_conveyor && CAN_FALL(element) &&
9087 direction & MV_HORIZONTAL)
9088 MovDir[newx][newy] = 0;
9090 if (!pushed_by_player)
9092 int nextx = newx + dx, nexty = newy + dy;
9093 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
9095 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
9097 if (CAN_FALL(element) && direction == MV_DOWN)
9098 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
9100 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
9101 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
9103 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
9104 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
9107 if (DONT_TOUCH(element)) // object may be nasty to player or others
9109 TestIfBadThingTouchesPlayer(newx, newy);
9110 TestIfBadThingTouchesFriend(newx, newy);
9112 if (!IS_CUSTOM_ELEMENT(element))
9113 TestIfBadThingTouchesOtherBadThing(newx, newy);
9115 else if (element == EL_PENGUIN)
9116 TestIfFriendTouchesBadThing(newx, newy);
9118 if (DONT_GET_HIT_BY(element))
9120 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
9123 // give the player one last chance (one more frame) to move away
9124 if (CAN_FALL(element) && direction == MV_DOWN &&
9125 (last_line || (!IS_FREE(x, newy + 1) &&
9126 (!IS_PLAYER(x, newy + 1) ||
9127 game.engine_version < VERSION_IDENT(3,1,1,0)))))
9130 if (pushed_by_player && !game.use_change_when_pushing_bug)
9132 int push_side = MV_DIR_OPPOSITE(direction);
9133 struct PlayerInfo *player = PLAYERINFO(x, y);
9135 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
9136 player->index_bit, push_side);
9137 CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X,
9138 player->index_bit, push_side);
9141 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
9142 MovDelay[newx][newy] = 1;
9144 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
9146 TestIfElementTouchesCustomElement(x, y); // empty or new element
9147 TestIfElementHitsCustomElement(newx, newy, direction);
9148 TestIfPlayerTouchesCustomElement(newx, newy);
9149 TestIfElementTouchesCustomElement(newx, newy);
9151 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
9152 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
9153 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
9154 MV_DIR_OPPOSITE(direction));
9157 int AmoebaNeighbourNr(int ax, int ay)
9160 int element = Tile[ax][ay];
9162 struct XY *xy = xy_topdown;
9164 for (i = 0; i < NUM_DIRECTIONS; i++)
9166 int x = ax + xy[i].x;
9167 int y = ay + xy[i].y;
9169 if (!IN_LEV_FIELD(x, y))
9172 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
9173 group_nr = AmoebaNr[x][y];
9179 static void AmoebaMerge(int ax, int ay)
9181 int i, x, y, xx, yy;
9182 int new_group_nr = AmoebaNr[ax][ay];
9183 struct XY *xy = xy_topdown;
9185 if (new_group_nr == 0)
9188 for (i = 0; i < NUM_DIRECTIONS; i++)
9193 if (!IN_LEV_FIELD(x, y))
9196 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9197 Tile[x][y] == EL_BD_AMOEBA ||
9198 Tile[x][y] == EL_AMOEBA_DEAD) &&
9199 AmoebaNr[x][y] != new_group_nr)
9201 int old_group_nr = AmoebaNr[x][y];
9203 if (old_group_nr == 0)
9206 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9207 AmoebaCnt[old_group_nr] = 0;
9208 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9209 AmoebaCnt2[old_group_nr] = 0;
9211 SCAN_PLAYFIELD(xx, yy)
9213 if (AmoebaNr[xx][yy] == old_group_nr)
9214 AmoebaNr[xx][yy] = new_group_nr;
9220 void AmoebaToDiamond(int ax, int ay)
9224 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9226 int group_nr = AmoebaNr[ax][ay];
9231 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9232 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9238 SCAN_PLAYFIELD(x, y)
9240 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9243 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9247 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9248 SND_AMOEBA_TURNING_TO_GEM :
9249 SND_AMOEBA_TURNING_TO_ROCK));
9254 struct XY *xy = xy_topdown;
9256 for (i = 0; i < NUM_DIRECTIONS; i++)
9261 if (!IN_LEV_FIELD(x, y))
9264 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9266 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9267 SND_AMOEBA_TURNING_TO_GEM :
9268 SND_AMOEBA_TURNING_TO_ROCK));
9275 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9278 int group_nr = AmoebaNr[ax][ay];
9279 boolean done = FALSE;
9284 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9285 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9291 SCAN_PLAYFIELD(x, y)
9293 if (AmoebaNr[x][y] == group_nr &&
9294 (Tile[x][y] == EL_AMOEBA_DEAD ||
9295 Tile[x][y] == EL_BD_AMOEBA ||
9296 Tile[x][y] == EL_AMOEBA_GROWING))
9299 Tile[x][y] = new_element;
9300 InitField(x, y, FALSE);
9301 TEST_DrawLevelField(x, y);
9307 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9308 SND_BD_AMOEBA_TURNING_TO_ROCK :
9309 SND_BD_AMOEBA_TURNING_TO_GEM));
9312 static void AmoebaGrowing(int x, int y)
9314 static DelayCounter sound_delay = { 0 };
9316 if (!MovDelay[x][y]) // start new growing cycle
9320 if (DelayReached(&sound_delay))
9322 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9323 sound_delay.value = 30;
9327 if (MovDelay[x][y]) // wait some time before growing bigger
9330 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9332 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9333 6 - MovDelay[x][y]);
9335 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9338 if (!MovDelay[x][y])
9340 Tile[x][y] = Store[x][y];
9342 TEST_DrawLevelField(x, y);
9347 static void AmoebaShrinking(int x, int y)
9349 static DelayCounter sound_delay = { 0 };
9351 if (!MovDelay[x][y]) // start new shrinking cycle
9355 if (DelayReached(&sound_delay))
9356 sound_delay.value = 30;
9359 if (MovDelay[x][y]) // wait some time before shrinking
9362 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9364 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9365 6 - MovDelay[x][y]);
9367 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9370 if (!MovDelay[x][y])
9372 Tile[x][y] = EL_EMPTY;
9373 TEST_DrawLevelField(x, y);
9375 // don't let mole enter this field in this cycle;
9376 // (give priority to objects falling to this field from above)
9382 static void AmoebaReproduce(int ax, int ay)
9385 int element = Tile[ax][ay];
9386 int graphic = el2img(element);
9387 int newax = ax, neway = ay;
9388 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9389 struct XY *xy = xy_topdown;
9391 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9393 Tile[ax][ay] = EL_AMOEBA_DEAD;
9394 TEST_DrawLevelField(ax, ay);
9398 if (IS_ANIMATED(graphic))
9399 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9401 if (!MovDelay[ax][ay]) // start making new amoeba field
9402 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9404 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9407 if (MovDelay[ax][ay])
9411 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9414 int x = ax + xy[start].x;
9415 int y = ay + xy[start].y;
9417 if (!IN_LEV_FIELD(x, y))
9420 if (IS_FREE(x, y) ||
9421 CAN_GROW_INTO(Tile[x][y]) ||
9422 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9423 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9429 if (newax == ax && neway == ay)
9432 else // normal or "filled" (BD style) amoeba
9435 boolean waiting_for_player = FALSE;
9437 for (i = 0; i < NUM_DIRECTIONS; i++)
9439 int j = (start + i) % 4;
9440 int x = ax + xy[j].x;
9441 int y = ay + xy[j].y;
9443 if (!IN_LEV_FIELD(x, y))
9446 if (IS_FREE(x, y) ||
9447 CAN_GROW_INTO(Tile[x][y]) ||
9448 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9449 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9455 else if (IS_PLAYER(x, y))
9456 waiting_for_player = TRUE;
9459 if (newax == ax && neway == ay) // amoeba cannot grow
9461 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9463 Tile[ax][ay] = EL_AMOEBA_DEAD;
9464 TEST_DrawLevelField(ax, ay);
9465 AmoebaCnt[AmoebaNr[ax][ay]]--;
9467 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9469 if (element == EL_AMOEBA_FULL)
9470 AmoebaToDiamond(ax, ay);
9471 else if (element == EL_BD_AMOEBA)
9472 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9477 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9479 // amoeba gets larger by growing in some direction
9481 int new_group_nr = AmoebaNr[ax][ay];
9484 if (new_group_nr == 0)
9486 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9488 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9494 AmoebaNr[newax][neway] = new_group_nr;
9495 AmoebaCnt[new_group_nr]++;
9496 AmoebaCnt2[new_group_nr]++;
9498 // if amoeba touches other amoeba(s) after growing, unify them
9499 AmoebaMerge(newax, neway);
9501 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9503 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9509 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9510 (neway == lev_fieldy - 1 && newax != ax))
9512 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9513 Store[newax][neway] = element;
9515 else if (neway == ay || element == EL_EMC_DRIPPER)
9517 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9519 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9523 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9524 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9525 Store[ax][ay] = EL_AMOEBA_DROP;
9526 ContinueMoving(ax, ay);
9530 TEST_DrawLevelField(newax, neway);
9533 static void Life(int ax, int ay)
9537 int element = Tile[ax][ay];
9538 int graphic = el2img(element);
9539 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9541 boolean changed = FALSE;
9543 if (IS_ANIMATED(graphic))
9544 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9549 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9550 MovDelay[ax][ay] = life_time;
9552 if (MovDelay[ax][ay]) // wait some time before next cycle
9555 if (MovDelay[ax][ay])
9559 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9561 int xx = ax + x1, yy = ay + y1;
9562 int old_element = Tile[xx][yy];
9563 int num_neighbours = 0;
9565 if (!IN_LEV_FIELD(xx, yy))
9568 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9570 int x = xx + x2, y = yy + y2;
9572 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9575 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9576 boolean is_neighbour = FALSE;
9578 if (level.use_life_bugs)
9580 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9581 (IS_FREE(x, y) && Stop[x][y]));
9584 (Last[x][y] == element || is_player_cell);
9590 boolean is_free = FALSE;
9592 if (level.use_life_bugs)
9593 is_free = (IS_FREE(xx, yy));
9595 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9597 if (xx == ax && yy == ay) // field in the middle
9599 if (num_neighbours < life_parameter[0] ||
9600 num_neighbours > life_parameter[1])
9602 Tile[xx][yy] = EL_EMPTY;
9603 if (Tile[xx][yy] != old_element)
9604 TEST_DrawLevelField(xx, yy);
9605 Stop[xx][yy] = TRUE;
9609 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9610 { // free border field
9611 if (num_neighbours >= life_parameter[2] &&
9612 num_neighbours <= life_parameter[3])
9614 Tile[xx][yy] = element;
9615 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9616 if (Tile[xx][yy] != old_element)
9617 TEST_DrawLevelField(xx, yy);
9618 Stop[xx][yy] = TRUE;
9625 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9626 SND_GAME_OF_LIFE_GROWING);
9629 static void InitRobotWheel(int x, int y)
9631 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9634 static void RunRobotWheel(int x, int y)
9636 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9639 static void StopRobotWheel(int x, int y)
9641 if (game.robot_wheel_x == x &&
9642 game.robot_wheel_y == y)
9644 game.robot_wheel_x = -1;
9645 game.robot_wheel_y = -1;
9646 game.robot_wheel_active = FALSE;
9650 static void InitTimegateWheel(int x, int y)
9652 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9655 static void RunTimegateWheel(int x, int y)
9657 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9660 static void InitMagicBallDelay(int x, int y)
9662 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9665 static void ActivateMagicBall(int bx, int by)
9669 if (level.ball_random)
9671 int pos_border = RND(8); // select one of the eight border elements
9672 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9673 int xx = pos_content % 3;
9674 int yy = pos_content / 3;
9679 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9680 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9684 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9686 int xx = x - bx + 1;
9687 int yy = y - by + 1;
9689 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9690 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9694 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9697 static void CheckExit(int x, int y)
9699 if (game.gems_still_needed > 0 ||
9700 game.sokoban_fields_still_needed > 0 ||
9701 game.sokoban_objects_still_needed > 0 ||
9702 game.lights_still_needed > 0)
9704 int element = Tile[x][y];
9705 int graphic = el2img(element);
9707 if (IS_ANIMATED(graphic))
9708 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9713 // do not re-open exit door closed after last player
9714 if (game.all_players_gone)
9717 Tile[x][y] = EL_EXIT_OPENING;
9719 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9722 static void CheckExitEM(int x, int y)
9724 if (game.gems_still_needed > 0 ||
9725 game.sokoban_fields_still_needed > 0 ||
9726 game.sokoban_objects_still_needed > 0 ||
9727 game.lights_still_needed > 0)
9729 int element = Tile[x][y];
9730 int graphic = el2img(element);
9732 if (IS_ANIMATED(graphic))
9733 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9738 // do not re-open exit door closed after last player
9739 if (game.all_players_gone)
9742 Tile[x][y] = EL_EM_EXIT_OPENING;
9744 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9747 static void CheckExitSteel(int x, int y)
9749 if (game.gems_still_needed > 0 ||
9750 game.sokoban_fields_still_needed > 0 ||
9751 game.sokoban_objects_still_needed > 0 ||
9752 game.lights_still_needed > 0)
9754 int element = Tile[x][y];
9755 int graphic = el2img(element);
9757 if (IS_ANIMATED(graphic))
9758 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9763 // do not re-open exit door closed after last player
9764 if (game.all_players_gone)
9767 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9769 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9772 static void CheckExitSteelEM(int x, int y)
9774 if (game.gems_still_needed > 0 ||
9775 game.sokoban_fields_still_needed > 0 ||
9776 game.sokoban_objects_still_needed > 0 ||
9777 game.lights_still_needed > 0)
9779 int element = Tile[x][y];
9780 int graphic = el2img(element);
9782 if (IS_ANIMATED(graphic))
9783 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9788 // do not re-open exit door closed after last player
9789 if (game.all_players_gone)
9792 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9794 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9797 static void CheckExitSP(int x, int y)
9799 if (game.gems_still_needed > 0)
9801 int element = Tile[x][y];
9802 int graphic = el2img(element);
9804 if (IS_ANIMATED(graphic))
9805 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9810 // do not re-open exit door closed after last player
9811 if (game.all_players_gone)
9814 Tile[x][y] = EL_SP_EXIT_OPENING;
9816 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9819 static void CloseAllOpenTimegates(void)
9823 SCAN_PLAYFIELD(x, y)
9825 int element = Tile[x][y];
9827 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9829 Tile[x][y] = EL_TIMEGATE_CLOSING;
9831 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9836 static void DrawTwinkleOnField(int x, int y)
9838 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9841 if (Tile[x][y] == EL_BD_DIAMOND)
9844 if (MovDelay[x][y] == 0) // next animation frame
9845 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9847 if (MovDelay[x][y] != 0) // wait some time before next frame
9851 DrawLevelElementAnimation(x, y, Tile[x][y]);
9853 if (MovDelay[x][y] != 0)
9855 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9856 10 - MovDelay[x][y]);
9858 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9863 static void WallGrowing(int x, int y)
9867 if (!MovDelay[x][y]) // next animation frame
9868 MovDelay[x][y] = 3 * delay;
9870 if (MovDelay[x][y]) // wait some time before next frame
9874 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9876 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9877 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9879 DrawLevelGraphic(x, y, graphic, frame);
9882 if (!MovDelay[x][y])
9884 if (MovDir[x][y] == MV_LEFT)
9886 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9887 TEST_DrawLevelField(x - 1, y);
9889 else if (MovDir[x][y] == MV_RIGHT)
9891 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9892 TEST_DrawLevelField(x + 1, y);
9894 else if (MovDir[x][y] == MV_UP)
9896 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9897 TEST_DrawLevelField(x, y - 1);
9901 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9902 TEST_DrawLevelField(x, y + 1);
9905 Tile[x][y] = Store[x][y];
9907 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9908 TEST_DrawLevelField(x, y);
9913 static void CheckWallGrowing(int ax, int ay)
9915 int element = Tile[ax][ay];
9916 int graphic = el2img(element);
9917 boolean free_top = FALSE;
9918 boolean free_bottom = FALSE;
9919 boolean free_left = FALSE;
9920 boolean free_right = FALSE;
9921 boolean stop_top = FALSE;
9922 boolean stop_bottom = FALSE;
9923 boolean stop_left = FALSE;
9924 boolean stop_right = FALSE;
9925 boolean new_wall = FALSE;
9927 boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9928 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9929 element == EL_EXPANDABLE_STEELWALL_ANY);
9931 boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9932 element == EL_EXPANDABLE_WALL_ANY ||
9933 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9934 element == EL_EXPANDABLE_STEELWALL_ANY);
9936 boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9937 element == EL_EXPANDABLE_WALL_ANY ||
9938 element == EL_EXPANDABLE_WALL ||
9939 element == EL_BD_EXPANDABLE_WALL ||
9940 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9941 element == EL_EXPANDABLE_STEELWALL_ANY);
9943 boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9944 element == EL_EXPANDABLE_STEELWALL_VERTICAL);
9946 boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9947 element == EL_EXPANDABLE_WALL ||
9948 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL);
9950 int wall_growing = (is_steelwall ?
9951 EL_EXPANDABLE_STEELWALL_GROWING :
9952 EL_EXPANDABLE_WALL_GROWING);
9954 int gfx_wall_growing_up = (is_steelwall ?
9955 IMG_EXPANDABLE_STEELWALL_GROWING_UP :
9956 IMG_EXPANDABLE_WALL_GROWING_UP);
9957 int gfx_wall_growing_down = (is_steelwall ?
9958 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN :
9959 IMG_EXPANDABLE_WALL_GROWING_DOWN);
9960 int gfx_wall_growing_left = (is_steelwall ?
9961 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT :
9962 IMG_EXPANDABLE_WALL_GROWING_LEFT);
9963 int gfx_wall_growing_right = (is_steelwall ?
9964 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT :
9965 IMG_EXPANDABLE_WALL_GROWING_RIGHT);
9967 if (IS_ANIMATED(graphic))
9968 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9970 if (!MovDelay[ax][ay]) // start building new wall
9971 MovDelay[ax][ay] = 6;
9973 if (MovDelay[ax][ay]) // wait some time before building new wall
9976 if (MovDelay[ax][ay])
9980 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9982 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9984 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9986 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9993 Tile[ax][ay - 1] = wall_growing;
9994 Store[ax][ay - 1] = element;
9995 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9997 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9998 DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0);
10005 Tile[ax][ay + 1] = wall_growing;
10006 Store[ax][ay + 1] = element;
10007 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
10009 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
10010 DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0);
10016 if (grow_horizontal)
10020 Tile[ax - 1][ay] = wall_growing;
10021 Store[ax - 1][ay] = element;
10022 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
10024 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
10025 DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0);
10032 Tile[ax + 1][ay] = wall_growing;
10033 Store[ax + 1][ay] = element;
10034 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
10036 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
10037 DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0);
10043 if (element == EL_EXPANDABLE_WALL && (free_left || free_right))
10044 TEST_DrawLevelField(ax, ay);
10046 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
10048 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
10049 stop_bottom = TRUE;
10050 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
10052 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
10055 if (((stop_top && stop_bottom) || stop_horizontal) &&
10056 ((stop_left && stop_right) || stop_vertical))
10057 Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL);
10060 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
10063 static void CheckForDragon(int x, int y)
10066 boolean dragon_found = FALSE;
10067 struct XY *xy = xy_topdown;
10069 for (i = 0; i < NUM_DIRECTIONS; i++)
10071 for (j = 0; j < 4; j++)
10073 int xx = x + j * xy[i].x;
10074 int yy = y + j * xy[i].y;
10076 if (IN_LEV_FIELD(xx, yy) &&
10077 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
10079 if (Tile[xx][yy] == EL_DRAGON)
10080 dragon_found = TRUE;
10089 for (i = 0; i < NUM_DIRECTIONS; i++)
10091 for (j = 0; j < 3; j++)
10093 int xx = x + j * xy[i].x;
10094 int yy = y + j * xy[i].y;
10096 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
10098 Tile[xx][yy] = EL_EMPTY;
10099 TEST_DrawLevelField(xx, yy);
10108 static void InitBuggyBase(int x, int y)
10110 int element = Tile[x][y];
10111 int activating_delay = FRAMES_PER_SECOND / 4;
10113 ChangeDelay[x][y] =
10114 (element == EL_SP_BUGGY_BASE ?
10115 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10116 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10118 element == EL_SP_BUGGY_BASE_ACTIVE ?
10119 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10122 static void WarnBuggyBase(int x, int y)
10125 struct XY *xy = xy_topdown;
10127 for (i = 0; i < NUM_DIRECTIONS; i++)
10129 int xx = x + xy[i].x;
10130 int yy = y + xy[i].y;
10132 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10134 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10141 static void InitTrap(int x, int y)
10143 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10146 static void ActivateTrap(int x, int y)
10148 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10151 static void ChangeActiveTrap(int x, int y)
10153 int graphic = IMG_TRAP_ACTIVE;
10155 // if new animation frame was drawn, correct crumbled sand border
10156 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10157 TEST_DrawLevelFieldCrumbled(x, y);
10160 static int getSpecialActionElement(int element, int number, int base_element)
10162 return (element != EL_EMPTY ? element :
10163 number != -1 ? base_element + number - 1 :
10167 static int getModifiedActionNumber(int value_old, int operator, int operand,
10168 int value_min, int value_max)
10170 int value_new = (operator == CA_MODE_SET ? operand :
10171 operator == CA_MODE_ADD ? value_old + operand :
10172 operator == CA_MODE_SUBTRACT ? value_old - operand :
10173 operator == CA_MODE_MULTIPLY ? value_old * operand :
10174 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10175 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10178 return (value_new < value_min ? value_min :
10179 value_new > value_max ? value_max :
10183 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10185 struct ElementInfo *ei = &element_info[element];
10186 struct ElementChangeInfo *change = &ei->change_page[page];
10187 int target_element = change->target_element;
10188 int action_type = change->action_type;
10189 int action_mode = change->action_mode;
10190 int action_arg = change->action_arg;
10191 int action_element = change->action_element;
10194 if (!change->has_action)
10197 // ---------- determine action paramater values -----------------------------
10199 int level_time_value =
10200 (level.time > 0 ? TimeLeft :
10203 int action_arg_element_raw =
10204 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10205 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10206 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10207 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10208 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10209 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10210 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10212 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10214 int action_arg_direction =
10215 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10216 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10217 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10218 change->actual_trigger_side :
10219 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10220 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10223 int action_arg_number_min =
10224 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10227 int action_arg_number_max =
10228 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10229 action_type == CA_SET_LEVEL_GEMS ? 999 :
10230 action_type == CA_SET_LEVEL_TIME ? 9999 :
10231 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10232 action_type == CA_SET_CE_VALUE ? 9999 :
10233 action_type == CA_SET_CE_SCORE ? 9999 :
10236 int action_arg_number_reset =
10237 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10238 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10239 action_type == CA_SET_LEVEL_TIME ? level.time :
10240 action_type == CA_SET_LEVEL_SCORE ? 0 :
10241 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10242 action_type == CA_SET_CE_SCORE ? 0 :
10245 int action_arg_number =
10246 (action_arg <= CA_ARG_MAX ? action_arg :
10247 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10248 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10249 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10250 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10251 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10252 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10253 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10254 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10255 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10256 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10257 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10258 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10259 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10260 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10261 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10262 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10263 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10264 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10265 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10266 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10267 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10270 int action_arg_number_old =
10271 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10272 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10273 action_type == CA_SET_LEVEL_SCORE ? game.score :
10274 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10275 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10278 int action_arg_number_new =
10279 getModifiedActionNumber(action_arg_number_old,
10280 action_mode, action_arg_number,
10281 action_arg_number_min, action_arg_number_max);
10283 int trigger_player_bits =
10284 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10285 change->actual_trigger_player_bits : change->trigger_player);
10287 int action_arg_player_bits =
10288 (action_arg >= CA_ARG_PLAYER_1 &&
10289 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10290 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10291 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10294 // ---------- execute action -----------------------------------------------
10296 switch (action_type)
10303 // ---------- level actions ----------------------------------------------
10305 case CA_RESTART_LEVEL:
10307 game.restart_level = TRUE;
10312 case CA_SHOW_ENVELOPE:
10314 int element = getSpecialActionElement(action_arg_element,
10315 action_arg_number, EL_ENVELOPE_1);
10317 if (IS_ENVELOPE(element))
10318 local_player->show_envelope = element;
10323 case CA_SET_LEVEL_TIME:
10325 if (level.time > 0) // only modify limited time value
10327 TimeLeft = action_arg_number_new;
10329 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10331 DisplayGameControlValues();
10333 if (!TimeLeft && game.time_limit)
10334 for (i = 0; i < MAX_PLAYERS; i++)
10335 KillPlayer(&stored_player[i]);
10341 case CA_SET_LEVEL_SCORE:
10343 game.score = action_arg_number_new;
10345 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10347 DisplayGameControlValues();
10352 case CA_SET_LEVEL_GEMS:
10354 game.gems_still_needed = action_arg_number_new;
10356 game.snapshot.collected_item = TRUE;
10358 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10360 DisplayGameControlValues();
10365 case CA_SET_LEVEL_WIND:
10367 game.wind_direction = action_arg_direction;
10372 case CA_SET_LEVEL_RANDOM_SEED:
10374 // ensure that setting a new random seed while playing is predictable
10375 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10380 // ---------- player actions ---------------------------------------------
10382 case CA_MOVE_PLAYER:
10383 case CA_MOVE_PLAYER_NEW:
10385 // automatically move to the next field in specified direction
10386 for (i = 0; i < MAX_PLAYERS; i++)
10387 if (trigger_player_bits & (1 << i))
10388 if (action_type == CA_MOVE_PLAYER ||
10389 stored_player[i].MovPos == 0)
10390 stored_player[i].programmed_action = action_arg_direction;
10395 case CA_EXIT_PLAYER:
10397 for (i = 0; i < MAX_PLAYERS; i++)
10398 if (action_arg_player_bits & (1 << i))
10399 ExitPlayer(&stored_player[i]);
10401 if (game.players_still_needed == 0)
10407 case CA_KILL_PLAYER:
10409 for (i = 0; i < MAX_PLAYERS; i++)
10410 if (action_arg_player_bits & (1 << i))
10411 KillPlayer(&stored_player[i]);
10416 case CA_SET_PLAYER_KEYS:
10418 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10419 int element = getSpecialActionElement(action_arg_element,
10420 action_arg_number, EL_KEY_1);
10422 if (IS_KEY(element))
10424 for (i = 0; i < MAX_PLAYERS; i++)
10426 if (trigger_player_bits & (1 << i))
10428 stored_player[i].key[KEY_NR(element)] = key_state;
10430 DrawGameDoorValues();
10438 case CA_SET_PLAYER_SPEED:
10440 for (i = 0; i < MAX_PLAYERS; i++)
10442 if (trigger_player_bits & (1 << i))
10444 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10446 if (action_arg == CA_ARG_SPEED_FASTER &&
10447 stored_player[i].cannot_move)
10449 action_arg_number = STEPSIZE_VERY_SLOW;
10451 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10452 action_arg == CA_ARG_SPEED_FASTER)
10454 action_arg_number = 2;
10455 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10458 else if (action_arg == CA_ARG_NUMBER_RESET)
10460 action_arg_number = level.initial_player_stepsize[i];
10464 getModifiedActionNumber(move_stepsize,
10467 action_arg_number_min,
10468 action_arg_number_max);
10470 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10477 case CA_SET_PLAYER_SHIELD:
10479 for (i = 0; i < MAX_PLAYERS; i++)
10481 if (trigger_player_bits & (1 << i))
10483 if (action_arg == CA_ARG_SHIELD_OFF)
10485 stored_player[i].shield_normal_time_left = 0;
10486 stored_player[i].shield_deadly_time_left = 0;
10488 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10490 stored_player[i].shield_normal_time_left = 999999;
10492 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10494 stored_player[i].shield_normal_time_left = 999999;
10495 stored_player[i].shield_deadly_time_left = 999999;
10503 case CA_SET_PLAYER_GRAVITY:
10505 for (i = 0; i < MAX_PLAYERS; i++)
10507 if (trigger_player_bits & (1 << i))
10509 stored_player[i].gravity =
10510 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10511 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10512 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10513 stored_player[i].gravity);
10520 case CA_SET_PLAYER_ARTWORK:
10522 for (i = 0; i < MAX_PLAYERS; i++)
10524 if (trigger_player_bits & (1 << i))
10526 int artwork_element = action_arg_element;
10528 if (action_arg == CA_ARG_ELEMENT_RESET)
10530 (level.use_artwork_element[i] ? level.artwork_element[i] :
10531 stored_player[i].element_nr);
10533 if (stored_player[i].artwork_element != artwork_element)
10534 stored_player[i].Frame = 0;
10536 stored_player[i].artwork_element = artwork_element;
10538 SetPlayerWaiting(&stored_player[i], FALSE);
10540 // set number of special actions for bored and sleeping animation
10541 stored_player[i].num_special_action_bored =
10542 get_num_special_action(artwork_element,
10543 ACTION_BORING_1, ACTION_BORING_LAST);
10544 stored_player[i].num_special_action_sleeping =
10545 get_num_special_action(artwork_element,
10546 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10553 case CA_SET_PLAYER_INVENTORY:
10555 for (i = 0; i < MAX_PLAYERS; i++)
10557 struct PlayerInfo *player = &stored_player[i];
10560 if (trigger_player_bits & (1 << i))
10562 int inventory_element = action_arg_element;
10564 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10565 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10566 action_arg == CA_ARG_ELEMENT_ACTION)
10568 int element = inventory_element;
10569 int collect_count = element_info[element].collect_count_initial;
10571 if (!IS_CUSTOM_ELEMENT(element))
10574 if (collect_count == 0)
10575 player->inventory_infinite_element = element;
10577 for (k = 0; k < collect_count; k++)
10578 if (player->inventory_size < MAX_INVENTORY_SIZE)
10579 player->inventory_element[player->inventory_size++] =
10582 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10583 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10584 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10586 if (player->inventory_infinite_element != EL_UNDEFINED &&
10587 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10588 action_arg_element_raw))
10589 player->inventory_infinite_element = EL_UNDEFINED;
10591 for (k = 0, j = 0; j < player->inventory_size; j++)
10593 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10594 action_arg_element_raw))
10595 player->inventory_element[k++] = player->inventory_element[j];
10598 player->inventory_size = k;
10600 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10602 if (player->inventory_size > 0)
10604 for (j = 0; j < player->inventory_size - 1; j++)
10605 player->inventory_element[j] = player->inventory_element[j + 1];
10607 player->inventory_size--;
10610 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10612 if (player->inventory_size > 0)
10613 player->inventory_size--;
10615 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10617 player->inventory_infinite_element = EL_UNDEFINED;
10618 player->inventory_size = 0;
10620 else if (action_arg == CA_ARG_INVENTORY_RESET)
10622 player->inventory_infinite_element = EL_UNDEFINED;
10623 player->inventory_size = 0;
10625 if (level.use_initial_inventory[i])
10627 for (j = 0; j < level.initial_inventory_size[i]; j++)
10629 int element = level.initial_inventory_content[i][j];
10630 int collect_count = element_info[element].collect_count_initial;
10632 if (!IS_CUSTOM_ELEMENT(element))
10635 if (collect_count == 0)
10636 player->inventory_infinite_element = element;
10638 for (k = 0; k < collect_count; k++)
10639 if (player->inventory_size < MAX_INVENTORY_SIZE)
10640 player->inventory_element[player->inventory_size++] =
10651 // ---------- CE actions -------------------------------------------------
10653 case CA_SET_CE_VALUE:
10655 int last_ce_value = CustomValue[x][y];
10657 CustomValue[x][y] = action_arg_number_new;
10659 if (CustomValue[x][y] != last_ce_value)
10661 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10662 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10664 if (CustomValue[x][y] == 0)
10666 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10667 ChangeCount[x][y] = 0; // allow at least one more change
10669 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10670 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10677 case CA_SET_CE_SCORE:
10679 int last_ce_score = ei->collect_score;
10681 ei->collect_score = action_arg_number_new;
10683 if (ei->collect_score != last_ce_score)
10685 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10686 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10688 if (ei->collect_score == 0)
10692 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10693 ChangeCount[x][y] = 0; // allow at least one more change
10695 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10696 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10699 This is a very special case that seems to be a mixture between
10700 CheckElementChange() and CheckTriggeredElementChange(): while
10701 the first one only affects single elements that are triggered
10702 directly, the second one affects multiple elements in the playfield
10703 that are triggered indirectly by another element. This is a third
10704 case: Changing the CE score always affects multiple identical CEs,
10705 so every affected CE must be checked, not only the single CE for
10706 which the CE score was changed in the first place (as every instance
10707 of that CE shares the same CE score, and therefore also can change)!
10709 SCAN_PLAYFIELD(xx, yy)
10711 if (Tile[xx][yy] == element)
10712 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10713 CE_SCORE_GETS_ZERO);
10721 case CA_SET_CE_ARTWORK:
10723 int artwork_element = action_arg_element;
10724 boolean reset_frame = FALSE;
10727 if (action_arg == CA_ARG_ELEMENT_RESET)
10728 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10731 if (ei->gfx_element != artwork_element)
10732 reset_frame = TRUE;
10734 ei->gfx_element = artwork_element;
10736 SCAN_PLAYFIELD(xx, yy)
10738 if (Tile[xx][yy] == element)
10742 ResetGfxAnimation(xx, yy);
10743 ResetRandomAnimationValue(xx, yy);
10746 TEST_DrawLevelField(xx, yy);
10753 // ---------- engine actions ---------------------------------------------
10755 case CA_SET_ENGINE_SCAN_MODE:
10757 InitPlayfieldScanMode(action_arg);
10767 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10769 int old_element = Tile[x][y];
10770 int new_element = GetElementFromGroupElement(element);
10771 int previous_move_direction = MovDir[x][y];
10772 int last_ce_value = CustomValue[x][y];
10773 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10774 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10775 boolean add_player_onto_element = (new_element_is_player &&
10776 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10777 IS_WALKABLE(old_element));
10779 if (!add_player_onto_element)
10781 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10782 RemoveMovingField(x, y);
10786 Tile[x][y] = new_element;
10788 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10789 MovDir[x][y] = previous_move_direction;
10791 if (element_info[new_element].use_last_ce_value)
10792 CustomValue[x][y] = last_ce_value;
10794 InitField_WithBug1(x, y, FALSE);
10796 new_element = Tile[x][y]; // element may have changed
10798 ResetGfxAnimation(x, y);
10799 ResetRandomAnimationValue(x, y);
10801 TEST_DrawLevelField(x, y);
10803 if (GFX_CRUMBLED(new_element))
10804 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10806 if (old_element == EL_EXPLOSION)
10808 Store[x][y] = Store2[x][y] = 0;
10810 // check if new element replaces an exploding player, requiring cleanup
10811 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
10812 StorePlayer[x][y] = 0;
10815 // check if element under the player changes from accessible to unaccessible
10816 // (needed for special case of dropping element which then changes)
10817 // (must be checked after creating new element for walkable group elements)
10818 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10819 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10821 KillPlayer(PLAYERINFO(x, y));
10827 // "ChangeCount" not set yet to allow "entered by player" change one time
10828 if (new_element_is_player)
10829 RelocatePlayer(x, y, new_element);
10832 ChangeCount[x][y]++; // count number of changes in the same frame
10834 TestIfBadThingTouchesPlayer(x, y);
10835 TestIfPlayerTouchesCustomElement(x, y);
10836 TestIfElementTouchesCustomElement(x, y);
10839 static void CreateField(int x, int y, int element)
10841 CreateFieldExt(x, y, element, FALSE);
10844 static void CreateElementFromChange(int x, int y, int element)
10846 element = GET_VALID_RUNTIME_ELEMENT(element);
10848 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10850 int old_element = Tile[x][y];
10852 // prevent changed element from moving in same engine frame
10853 // unless both old and new element can either fall or move
10854 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10855 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10859 CreateFieldExt(x, y, element, TRUE);
10862 static boolean ChangeElement(int x, int y, int element, int page)
10864 struct ElementInfo *ei = &element_info[element];
10865 struct ElementChangeInfo *change = &ei->change_page[page];
10866 int ce_value = CustomValue[x][y];
10867 int ce_score = ei->collect_score;
10868 int target_element;
10869 int old_element = Tile[x][y];
10871 // always use default change event to prevent running into a loop
10872 if (ChangeEvent[x][y] == -1)
10873 ChangeEvent[x][y] = CE_DELAY;
10875 if (ChangeEvent[x][y] == CE_DELAY)
10877 // reset actual trigger element, trigger player and action element
10878 change->actual_trigger_element = EL_EMPTY;
10879 change->actual_trigger_player = EL_EMPTY;
10880 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10881 change->actual_trigger_side = CH_SIDE_NONE;
10882 change->actual_trigger_ce_value = 0;
10883 change->actual_trigger_ce_score = 0;
10884 change->actual_trigger_x = -1;
10885 change->actual_trigger_y = -1;
10888 // do not change elements more than a specified maximum number of changes
10889 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10892 ChangeCount[x][y]++; // count number of changes in the same frame
10894 if (ei->has_anim_event)
10895 HandleGlobalAnimEventByElementChange(element, page, x, y,
10896 change->actual_trigger_x,
10897 change->actual_trigger_y);
10899 if (change->explode)
10906 if (change->use_target_content)
10908 boolean complete_replace = TRUE;
10909 boolean can_replace[3][3];
10912 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10915 boolean is_walkable;
10916 boolean is_diggable;
10917 boolean is_collectible;
10918 boolean is_removable;
10919 boolean is_destructible;
10920 int ex = x + xx - 1;
10921 int ey = y + yy - 1;
10922 int content_element = change->target_content.e[xx][yy];
10925 can_replace[xx][yy] = TRUE;
10927 if (ex == x && ey == y) // do not check changing element itself
10930 if (content_element == EL_EMPTY_SPACE)
10932 can_replace[xx][yy] = FALSE; // do not replace border with space
10937 if (!IN_LEV_FIELD(ex, ey))
10939 can_replace[xx][yy] = FALSE;
10940 complete_replace = FALSE;
10947 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10948 e = MovingOrBlocked2Element(ex, ey);
10950 is_empty = (IS_FREE(ex, ey) ||
10951 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10953 is_walkable = (is_empty || IS_WALKABLE(e));
10954 is_diggable = (is_empty || IS_DIGGABLE(e));
10955 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10956 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10957 is_removable = (is_diggable || is_collectible);
10959 can_replace[xx][yy] =
10960 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10961 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10962 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10963 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10964 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10965 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10966 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10968 if (!can_replace[xx][yy])
10969 complete_replace = FALSE;
10972 if (!change->only_if_complete || complete_replace)
10974 boolean something_has_changed = FALSE;
10976 if (change->only_if_complete && change->use_random_replace &&
10977 RND(100) < change->random_percentage)
10980 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10982 int ex = x + xx - 1;
10983 int ey = y + yy - 1;
10984 int content_element;
10986 if (can_replace[xx][yy] && (!change->use_random_replace ||
10987 RND(100) < change->random_percentage))
10989 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10990 RemoveMovingField(ex, ey);
10992 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10994 content_element = change->target_content.e[xx][yy];
10995 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10996 ce_value, ce_score);
10998 CreateElementFromChange(ex, ey, target_element);
11000 something_has_changed = TRUE;
11002 // for symmetry reasons, freeze newly created border elements
11003 if (ex != x || ey != y)
11004 Stop[ex][ey] = TRUE; // no more moving in this frame
11008 if (something_has_changed)
11010 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
11011 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
11017 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
11018 ce_value, ce_score);
11020 if (element == EL_DIAGONAL_GROWING ||
11021 element == EL_DIAGONAL_SHRINKING)
11023 target_element = Store[x][y];
11025 Store[x][y] = EL_EMPTY;
11028 // special case: element changes to player (and may be kept if walkable)
11029 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
11030 CreateElementFromChange(x, y, EL_EMPTY);
11032 CreateElementFromChange(x, y, target_element);
11034 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
11035 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
11038 // this uses direct change before indirect change
11039 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
11044 static void HandleElementChange(int x, int y, int page)
11046 int element = MovingOrBlocked2Element(x, y);
11047 struct ElementInfo *ei = &element_info[element];
11048 struct ElementChangeInfo *change = &ei->change_page[page];
11049 boolean handle_action_before_change = FALSE;
11052 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
11053 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
11055 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
11056 x, y, element, element_info[element].token_name);
11057 Debug("game:playing:HandleElementChange", "This should never happen!");
11061 // this can happen with classic bombs on walkable, changing elements
11062 if (!CAN_CHANGE_OR_HAS_ACTION(element))
11067 if (ChangeDelay[x][y] == 0) // initialize element change
11069 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
11071 if (change->can_change)
11073 // !!! not clear why graphic animation should be reset at all here !!!
11074 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
11075 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
11078 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
11080 When using an animation frame delay of 1 (this only happens with
11081 "sp_zonk.moving.left/right" in the classic graphics), the default
11082 (non-moving) animation shows wrong animation frames (while the
11083 moving animation, like "sp_zonk.moving.left/right", is correct,
11084 so this graphical bug never shows up with the classic graphics).
11085 For an animation with 4 frames, this causes wrong frames 0,0,1,2
11086 be drawn instead of the correct frames 0,1,2,3. This is caused by
11087 "GfxFrame[][]" being reset *twice* (in two successive frames) after
11088 an element change: First when the change delay ("ChangeDelay[][]")
11089 counter has reached zero after decrementing, then a second time in
11090 the next frame (after "GfxFrame[][]" was already incremented) when
11091 "ChangeDelay[][]" is reset to the initial delay value again.
11093 This causes frame 0 to be drawn twice, while the last frame won't
11094 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
11096 As some animations may already be cleverly designed around this bug
11097 (at least the "Snake Bite" snake tail animation does this), it cannot
11098 simply be fixed here without breaking such existing animations.
11099 Unfortunately, it cannot easily be detected if a graphics set was
11100 designed "before" or "after" the bug was fixed. As a workaround,
11101 a new graphics set option "game.graphics_engine_version" was added
11102 to be able to specify the game's major release version for which the
11103 graphics set was designed, which can then be used to decide if the
11104 bugfix should be used (version 4 and above) or not (version 3 or
11105 below, or if no version was specified at all, as with old sets).
11107 (The wrong/fixed animation frames can be tested with the test level set
11108 "test_gfxframe" and level "000", which contains a specially prepared
11109 custom element at level position (x/y) == (11/9) which uses the zonk
11110 animation mentioned above. Using "game.graphics_engine_version: 4"
11111 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
11112 This can also be seen from the debug output for this test element.)
11115 // when a custom element is about to change (for example by change delay),
11116 // do not reset graphic animation when the custom element is moving
11117 if (game.graphics_engine_version < 4 &&
11120 ResetGfxAnimation(x, y);
11121 ResetRandomAnimationValue(x, y);
11124 if (change->pre_change_function)
11125 change->pre_change_function(x, y);
11129 ChangeDelay[x][y]--;
11131 if (ChangeDelay[x][y] != 0) // continue element change
11133 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11135 // also needed if CE can not change, but has CE delay with CE action
11136 if (IS_ANIMATED(graphic))
11137 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11139 if (change->can_change)
11141 if (change->change_function)
11142 change->change_function(x, y);
11145 else // finish element change
11147 if (ChangePage[x][y] != -1) // remember page from delayed change
11149 page = ChangePage[x][y];
11150 ChangePage[x][y] = -1;
11152 change = &ei->change_page[page];
11155 if (IS_MOVING(x, y)) // never change a running system ;-)
11157 ChangeDelay[x][y] = 1; // try change after next move step
11158 ChangePage[x][y] = page; // remember page to use for change
11163 // special case: set new level random seed before changing element
11164 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11165 handle_action_before_change = TRUE;
11167 if (change->has_action && handle_action_before_change)
11168 ExecuteCustomElementAction(x, y, element, page);
11170 if (change->can_change)
11172 if (ChangeElement(x, y, element, page))
11174 if (change->post_change_function)
11175 change->post_change_function(x, y);
11179 if (change->has_action && !handle_action_before_change)
11180 ExecuteCustomElementAction(x, y, element, page);
11184 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11185 int trigger_element,
11187 int trigger_player,
11191 boolean change_done_any = FALSE;
11192 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11195 if (!(trigger_events[trigger_element][trigger_event]))
11198 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11200 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11202 int element = EL_CUSTOM_START + i;
11203 boolean change_done = FALSE;
11206 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11207 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11210 for (p = 0; p < element_info[element].num_change_pages; p++)
11212 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11214 if (change->can_change_or_has_action &&
11215 change->has_event[trigger_event] &&
11216 change->trigger_side & trigger_side &&
11217 change->trigger_player & trigger_player &&
11218 change->trigger_page & trigger_page_bits &&
11219 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11221 change->actual_trigger_element = trigger_element;
11222 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11223 change->actual_trigger_player_bits = trigger_player;
11224 change->actual_trigger_side = trigger_side;
11225 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11226 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11227 change->actual_trigger_x = trigger_x;
11228 change->actual_trigger_y = trigger_y;
11230 if ((change->can_change && !change_done) || change->has_action)
11234 SCAN_PLAYFIELD(x, y)
11236 if (Tile[x][y] == element)
11238 if (change->can_change && !change_done)
11240 // if element already changed in this frame, not only prevent
11241 // another element change (checked in ChangeElement()), but
11242 // also prevent additional element actions for this element
11244 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11245 !level.use_action_after_change_bug)
11248 ChangeDelay[x][y] = 1;
11249 ChangeEvent[x][y] = trigger_event;
11251 HandleElementChange(x, y, p);
11253 else if (change->has_action)
11255 // if element already changed in this frame, not only prevent
11256 // another element change (checked in ChangeElement()), but
11257 // also prevent additional element actions for this element
11259 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11260 !level.use_action_after_change_bug)
11263 ExecuteCustomElementAction(x, y, element, p);
11264 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11269 if (change->can_change)
11271 change_done = TRUE;
11272 change_done_any = TRUE;
11279 RECURSION_LOOP_DETECTION_END();
11281 return change_done_any;
11284 static boolean CheckElementChangeExt(int x, int y,
11286 int trigger_element,
11288 int trigger_player,
11291 boolean change_done = FALSE;
11294 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11295 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11298 if (Tile[x][y] == EL_BLOCKED)
11300 Blocked2Moving(x, y, &x, &y);
11301 element = Tile[x][y];
11304 // check if element has already changed or is about to change after moving
11305 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11306 Tile[x][y] != element) ||
11308 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11309 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11310 ChangePage[x][y] != -1)))
11313 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11315 for (p = 0; p < element_info[element].num_change_pages; p++)
11317 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11319 /* check trigger element for all events where the element that is checked
11320 for changing interacts with a directly adjacent element -- this is
11321 different to element changes that affect other elements to change on the
11322 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11323 boolean check_trigger_element =
11324 (trigger_event == CE_NEXT_TO_X ||
11325 trigger_event == CE_TOUCHING_X ||
11326 trigger_event == CE_HITTING_X ||
11327 trigger_event == CE_HIT_BY_X ||
11328 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11330 if (change->can_change_or_has_action &&
11331 change->has_event[trigger_event] &&
11332 change->trigger_side & trigger_side &&
11333 change->trigger_player & trigger_player &&
11334 (!check_trigger_element ||
11335 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11337 change->actual_trigger_element = trigger_element;
11338 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11339 change->actual_trigger_player_bits = trigger_player;
11340 change->actual_trigger_side = trigger_side;
11341 change->actual_trigger_ce_value = CustomValue[x][y];
11342 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11343 change->actual_trigger_x = x;
11344 change->actual_trigger_y = y;
11346 // special case: trigger element not at (x,y) position for some events
11347 if (check_trigger_element)
11359 { 0, 0 }, { 0, 0 }, { 0, 0 },
11363 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11364 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11366 change->actual_trigger_ce_value = CustomValue[xx][yy];
11367 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11368 change->actual_trigger_x = xx;
11369 change->actual_trigger_y = yy;
11372 if (change->can_change && !change_done)
11374 ChangeDelay[x][y] = 1;
11375 ChangeEvent[x][y] = trigger_event;
11377 HandleElementChange(x, y, p);
11379 change_done = TRUE;
11381 else if (change->has_action)
11383 ExecuteCustomElementAction(x, y, element, p);
11384 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11389 RECURSION_LOOP_DETECTION_END();
11391 return change_done;
11394 static void PlayPlayerSound(struct PlayerInfo *player)
11396 int jx = player->jx, jy = player->jy;
11397 int sound_element = player->artwork_element;
11398 int last_action = player->last_action_waiting;
11399 int action = player->action_waiting;
11401 if (player->is_waiting)
11403 if (action != last_action)
11404 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11406 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11410 if (action != last_action)
11411 StopSound(element_info[sound_element].sound[last_action]);
11413 if (last_action == ACTION_SLEEPING)
11414 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11418 static void PlayAllPlayersSound(void)
11422 for (i = 0; i < MAX_PLAYERS; i++)
11423 if (stored_player[i].active)
11424 PlayPlayerSound(&stored_player[i]);
11427 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11429 boolean last_waiting = player->is_waiting;
11430 int move_dir = player->MovDir;
11432 player->dir_waiting = move_dir;
11433 player->last_action_waiting = player->action_waiting;
11437 if (!last_waiting) // not waiting -> waiting
11439 player->is_waiting = TRUE;
11441 player->frame_counter_bored =
11443 game.player_boring_delay_fixed +
11444 GetSimpleRandom(game.player_boring_delay_random);
11445 player->frame_counter_sleeping =
11447 game.player_sleeping_delay_fixed +
11448 GetSimpleRandom(game.player_sleeping_delay_random);
11450 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11453 if (game.player_sleeping_delay_fixed +
11454 game.player_sleeping_delay_random > 0 &&
11455 player->anim_delay_counter == 0 &&
11456 player->post_delay_counter == 0 &&
11457 FrameCounter >= player->frame_counter_sleeping)
11458 player->is_sleeping = TRUE;
11459 else if (game.player_boring_delay_fixed +
11460 game.player_boring_delay_random > 0 &&
11461 FrameCounter >= player->frame_counter_bored)
11462 player->is_bored = TRUE;
11464 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11465 player->is_bored ? ACTION_BORING :
11468 if (player->is_sleeping && player->use_murphy)
11470 // special case for sleeping Murphy when leaning against non-free tile
11472 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11473 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11474 !IS_MOVING(player->jx - 1, player->jy)))
11475 move_dir = MV_LEFT;
11476 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11477 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11478 !IS_MOVING(player->jx + 1, player->jy)))
11479 move_dir = MV_RIGHT;
11481 player->is_sleeping = FALSE;
11483 player->dir_waiting = move_dir;
11486 if (player->is_sleeping)
11488 if (player->num_special_action_sleeping > 0)
11490 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11492 int last_special_action = player->special_action_sleeping;
11493 int num_special_action = player->num_special_action_sleeping;
11494 int special_action =
11495 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11496 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11497 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11498 last_special_action + 1 : ACTION_SLEEPING);
11499 int special_graphic =
11500 el_act_dir2img(player->artwork_element, special_action, move_dir);
11502 player->anim_delay_counter =
11503 graphic_info[special_graphic].anim_delay_fixed +
11504 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11505 player->post_delay_counter =
11506 graphic_info[special_graphic].post_delay_fixed +
11507 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11509 player->special_action_sleeping = special_action;
11512 if (player->anim_delay_counter > 0)
11514 player->action_waiting = player->special_action_sleeping;
11515 player->anim_delay_counter--;
11517 else if (player->post_delay_counter > 0)
11519 player->post_delay_counter--;
11523 else if (player->is_bored)
11525 if (player->num_special_action_bored > 0)
11527 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11529 int special_action =
11530 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11531 int special_graphic =
11532 el_act_dir2img(player->artwork_element, special_action, move_dir);
11534 player->anim_delay_counter =
11535 graphic_info[special_graphic].anim_delay_fixed +
11536 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11537 player->post_delay_counter =
11538 graphic_info[special_graphic].post_delay_fixed +
11539 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11541 player->special_action_bored = special_action;
11544 if (player->anim_delay_counter > 0)
11546 player->action_waiting = player->special_action_bored;
11547 player->anim_delay_counter--;
11549 else if (player->post_delay_counter > 0)
11551 player->post_delay_counter--;
11556 else if (last_waiting) // waiting -> not waiting
11558 player->is_waiting = FALSE;
11559 player->is_bored = FALSE;
11560 player->is_sleeping = FALSE;
11562 player->frame_counter_bored = -1;
11563 player->frame_counter_sleeping = -1;
11565 player->anim_delay_counter = 0;
11566 player->post_delay_counter = 0;
11568 player->dir_waiting = player->MovDir;
11569 player->action_waiting = ACTION_DEFAULT;
11571 player->special_action_bored = ACTION_DEFAULT;
11572 player->special_action_sleeping = ACTION_DEFAULT;
11576 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11578 if ((!player->is_moving && player->was_moving) ||
11579 (player->MovPos == 0 && player->was_moving) ||
11580 (player->is_snapping && !player->was_snapping) ||
11581 (player->is_dropping && !player->was_dropping))
11583 if (!CheckSaveEngineSnapshotToList())
11586 player->was_moving = FALSE;
11587 player->was_snapping = TRUE;
11588 player->was_dropping = TRUE;
11592 if (player->is_moving)
11593 player->was_moving = TRUE;
11595 if (!player->is_snapping)
11596 player->was_snapping = FALSE;
11598 if (!player->is_dropping)
11599 player->was_dropping = FALSE;
11602 static struct MouseActionInfo mouse_action_last = { 0 };
11603 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11604 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11607 CheckSaveEngineSnapshotToList();
11609 mouse_action_last = mouse_action;
11612 static void CheckSingleStepMode(struct PlayerInfo *player)
11614 if (tape.single_step && tape.recording && !tape.pausing)
11616 // as it is called "single step mode", just return to pause mode when the
11617 // player stopped moving after one tile (or never starts moving at all)
11618 // (reverse logic needed here in case single step mode used in team mode)
11619 if (player->is_moving ||
11620 player->is_pushing ||
11621 player->is_dropping_pressed ||
11622 player->effective_mouse_action.button)
11623 game.enter_single_step_mode = FALSE;
11626 CheckSaveEngineSnapshot(player);
11629 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11631 int left = player_action & JOY_LEFT;
11632 int right = player_action & JOY_RIGHT;
11633 int up = player_action & JOY_UP;
11634 int down = player_action & JOY_DOWN;
11635 int button1 = player_action & JOY_BUTTON_1;
11636 int button2 = player_action & JOY_BUTTON_2;
11637 int dx = (left ? -1 : right ? 1 : 0);
11638 int dy = (up ? -1 : down ? 1 : 0);
11640 if (!player->active || tape.pausing)
11646 SnapField(player, dx, dy);
11650 DropElement(player);
11652 MovePlayer(player, dx, dy);
11655 CheckSingleStepMode(player);
11657 SetPlayerWaiting(player, FALSE);
11659 return player_action;
11663 // no actions for this player (no input at player's configured device)
11665 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11666 SnapField(player, 0, 0);
11667 CheckGravityMovementWhenNotMoving(player);
11669 if (player->MovPos == 0)
11670 SetPlayerWaiting(player, TRUE);
11672 if (player->MovPos == 0) // needed for tape.playing
11673 player->is_moving = FALSE;
11675 player->is_dropping = FALSE;
11676 player->is_dropping_pressed = FALSE;
11677 player->drop_pressed_delay = 0;
11679 CheckSingleStepMode(player);
11685 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11688 if (!tape.use_mouse_actions)
11691 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11692 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11693 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11696 static void SetTapeActionFromMouseAction(byte *tape_action,
11697 struct MouseActionInfo *mouse_action)
11699 if (!tape.use_mouse_actions)
11702 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11703 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11704 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11707 static void CheckLevelSolved(void)
11709 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11711 if (game_bd.level_solved &&
11712 !game_bd.game_over) // game won
11716 game_bd.game_over = TRUE;
11718 game.all_players_gone = TRUE;
11721 if (game_bd.game_over) // game lost
11722 game.all_players_gone = TRUE;
11724 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11726 if (game_em.level_solved &&
11727 !game_em.game_over) // game won
11731 game_em.game_over = TRUE;
11733 game.all_players_gone = TRUE;
11736 if (game_em.game_over) // game lost
11737 game.all_players_gone = TRUE;
11739 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11741 if (game_sp.level_solved &&
11742 !game_sp.game_over) // game won
11746 game_sp.game_over = TRUE;
11748 game.all_players_gone = TRUE;
11751 if (game_sp.game_over) // game lost
11752 game.all_players_gone = TRUE;
11754 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11756 if (game_mm.level_solved &&
11757 !game_mm.game_over) // game won
11761 game_mm.game_over = TRUE;
11763 game.all_players_gone = TRUE;
11766 if (game_mm.game_over) // game lost
11767 game.all_players_gone = TRUE;
11771 static void PlayTimeoutSound(int seconds_left)
11773 // will be played directly by BD engine (for classic bonus time sounds)
11774 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && checkBonusTime_BD())
11777 // try to use individual "running out of time" sound for each second left
11778 int sound = SND_GAME_RUNNING_OUT_OF_TIME_0 - seconds_left;
11780 // if special sound per second not defined, use default sound
11781 if (getSoundInfoEntryFilename(sound) == NULL)
11782 sound = SND_GAME_RUNNING_OUT_OF_TIME;
11784 // if out of time, but player still alive, play special "timeout" sound, if defined
11785 if (seconds_left == 0 && !checkGameFailed())
11786 if (getSoundInfoEntryFilename(SND_GAME_TIMEOUT) != NULL)
11787 sound = SND_GAME_TIMEOUT;
11792 static void CheckLevelTime_StepCounter(void)
11802 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11803 PlayTimeoutSound(TimeLeft);
11805 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11807 DisplayGameControlValues();
11809 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11810 for (i = 0; i < MAX_PLAYERS; i++)
11811 KillPlayer(&stored_player[i]);
11813 else if (game.no_level_time_limit && !game.all_players_gone)
11815 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11817 DisplayGameControlValues();
11821 static void CheckLevelTime(void)
11823 int frames_per_second = FRAMES_PER_SECOND;
11826 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11828 // level time may be running slower in native BD engine
11829 frames_per_second = getFramesPerSecond_BD();
11831 // if native engine time changed, force main engine time change
11832 if (getTimeLeft_BD() < TimeLeft)
11833 TimeFrames = frames_per_second;
11835 // if last second running, wait for native engine time to exactly reach zero
11836 if (getTimeLeft_BD() == 1 && TimeLeft == 1)
11837 TimeFrames = frames_per_second - 1;
11840 if (TimeFrames >= frames_per_second)
11844 for (i = 0; i < MAX_PLAYERS; i++)
11846 struct PlayerInfo *player = &stored_player[i];
11848 if (SHIELD_ON(player))
11850 player->shield_normal_time_left--;
11852 if (player->shield_deadly_time_left > 0)
11853 player->shield_deadly_time_left--;
11857 if (!game.LevelSolved && !level.use_step_counter)
11865 if (TimeLeft <= 10 && game.time_limit)
11866 PlayTimeoutSound(TimeLeft);
11868 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11869 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11871 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11873 if (!TimeLeft && game.time_limit)
11875 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11877 if (game_bd.game->cave->player_state == GD_PL_LIVING)
11878 game_bd.game->cave->player_state = GD_PL_TIMEOUT;
11880 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11882 game_em.lev->killed_out_of_time = TRUE;
11886 for (i = 0; i < MAX_PLAYERS; i++)
11887 KillPlayer(&stored_player[i]);
11891 else if (game.no_level_time_limit && !game.all_players_gone)
11893 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11896 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11900 if (TapeTimeFrames >= FRAMES_PER_SECOND)
11902 TapeTimeFrames = 0;
11905 if (tape.recording || tape.playing)
11906 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11909 if (tape.recording || tape.playing)
11910 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11912 UpdateAndDisplayGameControlValues();
11915 void AdvanceFrameAndPlayerCounters(int player_nr)
11919 // handle game and tape time differently for native BD game engine
11921 // tape time is running in native BD engine even if player is not hatched yet
11922 if (!checkGameRunning())
11925 // advance frame counters (global frame counter and tape time frame counter)
11929 // level time is running in native BD engine after player is being hatched
11930 if (!checkGamePlaying())
11933 // advance time frame counter (used to control available time to solve level)
11936 // advance player counters (counters for move delay, move animation etc.)
11937 for (i = 0; i < MAX_PLAYERS; i++)
11939 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11940 int move_delay_value = stored_player[i].move_delay_value;
11941 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11943 if (!advance_player_counters) // not all players may be affected
11946 if (move_frames == 0) // less than one move per game frame
11948 int stepsize = TILEX / move_delay_value;
11949 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11950 int count = (stored_player[i].is_moving ?
11951 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11953 if (count % delay == 0)
11957 stored_player[i].Frame += move_frames;
11959 if (stored_player[i].MovPos != 0)
11960 stored_player[i].StepFrame += move_frames;
11962 if (stored_player[i].move_delay > 0)
11963 stored_player[i].move_delay--;
11965 // due to bugs in previous versions, counter must count up, not down
11966 if (stored_player[i].push_delay != -1)
11967 stored_player[i].push_delay++;
11969 if (stored_player[i].drop_delay > 0)
11970 stored_player[i].drop_delay--;
11972 if (stored_player[i].is_dropping_pressed)
11973 stored_player[i].drop_pressed_delay++;
11977 void AdvanceFrameCounter(void)
11982 void AdvanceGfxFrame(void)
11986 SCAN_PLAYFIELD(x, y)
11992 static void HandleMouseAction(struct MouseActionInfo *mouse_action,
11993 struct MouseActionInfo *mouse_action_last)
11995 if (mouse_action->button)
11997 int new_button = (mouse_action->button && mouse_action_last->button == 0);
11998 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button);
11999 int x = mouse_action->lx;
12000 int y = mouse_action->ly;
12001 int element = Tile[x][y];
12005 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12006 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12010 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12011 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12014 if (level.use_step_counter)
12016 boolean counted_click = FALSE;
12018 // element clicked that can change when clicked/pressed
12019 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
12020 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
12021 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
12022 counted_click = TRUE;
12024 // element clicked that can trigger change when clicked/pressed
12025 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
12026 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
12027 counted_click = TRUE;
12029 if (new_button && counted_click)
12030 CheckLevelTime_StepCounter();
12035 void StartGameActions(boolean init_network_game, boolean record_tape,
12038 unsigned int new_random_seed = InitRND(random_seed);
12041 TapeStartRecording(new_random_seed);
12043 if (setup.auto_pause_on_start && !tape.pausing)
12044 TapeTogglePause(TAPE_TOGGLE_MANUAL);
12046 if (init_network_game)
12048 SendToServer_LevelFile();
12049 SendToServer_StartPlaying();
12057 static void GameActionsExt(void)
12060 static unsigned int game_frame_delay = 0;
12062 unsigned int game_frame_delay_value;
12063 byte *recorded_player_action;
12064 byte summarized_player_action = 0;
12065 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
12068 // detect endless loops, caused by custom element programming
12069 if (recursion_loop_detected && recursion_loop_depth == 0)
12071 char *message = getStringCat3("Internal Error! Element ",
12072 EL_NAME(recursion_loop_element),
12073 " caused endless loop! Quit the game?");
12075 Warn("element '%s' caused endless loop in game engine",
12076 EL_NAME(recursion_loop_element));
12078 RequestQuitGameExt(program.headless, level_editor_test_game, message);
12080 recursion_loop_detected = FALSE; // if game should be continued
12087 if (game.restart_level)
12088 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
12090 CheckLevelSolved();
12092 if (game.LevelSolved && !game.LevelSolved_GameEnd)
12095 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
12098 if (game_status != GAME_MODE_PLAYING) // status might have changed
12101 game_frame_delay_value =
12102 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
12104 if (tape.playing && tape.warp_forward && !tape.pausing)
12105 game_frame_delay_value = 0;
12107 SetVideoFrameDelay(game_frame_delay_value);
12109 // (de)activate virtual buttons depending on current game status
12110 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
12112 if (game.all_players_gone) // if no players there to be controlled anymore
12113 SetOverlayActive(FALSE);
12114 else if (!tape.playing) // if game continues after tape stopped playing
12115 SetOverlayActive(TRUE);
12120 // ---------- main game synchronization point ----------
12122 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12124 Debug("game:playing:skip", "skip == %d", skip);
12127 // ---------- main game synchronization point ----------
12129 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12133 if (network_playing && !network_player_action_received)
12135 // try to get network player actions in time
12137 // last chance to get network player actions without main loop delay
12138 HandleNetworking();
12140 // game was quit by network peer
12141 if (game_status != GAME_MODE_PLAYING)
12144 // check if network player actions still missing and game still running
12145 if (!network_player_action_received && !checkGameEnded())
12146 return; // failed to get network player actions in time
12148 // do not yet reset "network_player_action_received" (for tape.pausing)
12154 // at this point we know that we really continue executing the game
12156 network_player_action_received = FALSE;
12158 // when playing tape, read previously recorded player input from tape data
12159 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
12161 local_player->effective_mouse_action = local_player->mouse_action;
12163 if (recorded_player_action != NULL)
12164 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
12165 recorded_player_action);
12167 // TapePlayAction() may return NULL when toggling to "pause before death"
12171 if (tape.set_centered_player)
12173 game.centered_player_nr_next = tape.centered_player_nr_next;
12174 game.set_centered_player = TRUE;
12177 for (i = 0; i < MAX_PLAYERS; i++)
12179 summarized_player_action |= stored_player[i].action;
12181 if (!network_playing && (game.team_mode || tape.playing))
12182 stored_player[i].effective_action = stored_player[i].action;
12185 if (network_playing && !checkGameEnded())
12186 SendToServer_MovePlayer(summarized_player_action);
12188 // summarize all actions at local players mapped input device position
12189 // (this allows using different input devices in single player mode)
12190 if (!network.enabled && !game.team_mode)
12191 stored_player[map_player_action[local_player->index_nr]].effective_action =
12192 summarized_player_action;
12194 // summarize all actions at centered player in local team mode
12195 if (tape.recording &&
12196 setup.team_mode && !network.enabled &&
12197 setup.input_on_focus &&
12198 game.centered_player_nr != -1)
12200 for (i = 0; i < MAX_PLAYERS; i++)
12201 stored_player[map_player_action[i]].effective_action =
12202 (i == game.centered_player_nr ? summarized_player_action : 0);
12205 if (recorded_player_action != NULL)
12206 for (i = 0; i < MAX_PLAYERS; i++)
12207 stored_player[i].effective_action = recorded_player_action[i];
12209 for (i = 0; i < MAX_PLAYERS; i++)
12211 tape_action[i] = stored_player[i].effective_action;
12213 /* (this may happen in the RND game engine if a player was not present on
12214 the playfield on level start, but appeared later from a custom element */
12215 if (setup.team_mode &&
12218 !tape.player_participates[i])
12219 tape.player_participates[i] = TRUE;
12222 SetTapeActionFromMouseAction(tape_action,
12223 &local_player->effective_mouse_action);
12225 // only record actions from input devices, but not programmed actions
12226 if (tape.recording)
12227 TapeRecordAction(tape_action);
12229 // remember if game was played (especially after tape stopped playing)
12230 if (!tape.playing && summarized_player_action && !checkGameFailed())
12231 game.GamePlayed = TRUE;
12233 #if USE_NEW_PLAYER_ASSIGNMENTS
12234 // !!! also map player actions in single player mode !!!
12235 // if (game.team_mode)
12238 byte mapped_action[MAX_PLAYERS];
12240 #if DEBUG_PLAYER_ACTIONS
12241 for (i = 0; i < MAX_PLAYERS; i++)
12242 DebugContinued("", "%d, ", stored_player[i].effective_action);
12245 for (i = 0; i < MAX_PLAYERS; i++)
12246 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
12248 for (i = 0; i < MAX_PLAYERS; i++)
12249 stored_player[i].effective_action = mapped_action[i];
12251 #if DEBUG_PLAYER_ACTIONS
12252 DebugContinued("", "=> ");
12253 for (i = 0; i < MAX_PLAYERS; i++)
12254 DebugContinued("", "%d, ", stored_player[i].effective_action);
12255 DebugContinued("game:playing:player", "\n");
12258 #if DEBUG_PLAYER_ACTIONS
12261 for (i = 0; i < MAX_PLAYERS; i++)
12262 DebugContinued("", "%d, ", stored_player[i].effective_action);
12263 DebugContinued("game:playing:player", "\n");
12268 for (i = 0; i < MAX_PLAYERS; i++)
12270 // allow engine snapshot in case of changed movement attempt
12271 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12272 (stored_player[i].effective_action & KEY_MOTION))
12273 game.snapshot.changed_action = TRUE;
12275 // allow engine snapshot in case of snapping/dropping attempt
12276 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12277 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12278 game.snapshot.changed_action = TRUE;
12280 game.snapshot.last_action[i] = stored_player[i].effective_action;
12283 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
12285 GameActions_BD_Main();
12287 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12289 GameActions_EM_Main();
12291 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12293 GameActions_SP_Main();
12295 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12297 GameActions_MM_Main();
12301 GameActions_RND_Main();
12304 BlitScreenToBitmap(backbuffer);
12306 CheckLevelSolved();
12309 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12311 if (global.show_frames_per_second)
12313 static unsigned int fps_counter = 0;
12314 static int fps_frames = 0;
12315 unsigned int fps_delay_ms = Counter() - fps_counter;
12319 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12321 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12324 fps_counter = Counter();
12326 // always draw FPS to screen after FPS value was updated
12327 redraw_mask |= REDRAW_FPS;
12330 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12331 if (GetDrawDeactivationMask() == REDRAW_NONE)
12332 redraw_mask |= REDRAW_FPS;
12336 static void GameActions_CheckSaveEngineSnapshot(void)
12338 if (!game.snapshot.save_snapshot)
12341 // clear flag for saving snapshot _before_ saving snapshot
12342 game.snapshot.save_snapshot = FALSE;
12344 SaveEngineSnapshotToList();
12347 void GameActions(void)
12351 GameActions_CheckSaveEngineSnapshot();
12354 void GameActions_BD_Main(void)
12356 byte effective_action[MAX_PLAYERS];
12359 for (i = 0; i < MAX_PLAYERS; i++)
12360 effective_action[i] = stored_player[i].effective_action;
12362 GameActions_BD(effective_action);
12365 void GameActions_EM_Main(void)
12367 byte effective_action[MAX_PLAYERS];
12370 for (i = 0; i < MAX_PLAYERS; i++)
12371 effective_action[i] = stored_player[i].effective_action;
12373 GameActions_EM(effective_action);
12376 void GameActions_SP_Main(void)
12378 byte effective_action[MAX_PLAYERS];
12381 for (i = 0; i < MAX_PLAYERS; i++)
12382 effective_action[i] = stored_player[i].effective_action;
12384 GameActions_SP(effective_action);
12386 for (i = 0; i < MAX_PLAYERS; i++)
12388 if (stored_player[i].force_dropping)
12389 stored_player[i].action |= KEY_BUTTON_DROP;
12391 stored_player[i].force_dropping = FALSE;
12395 void GameActions_MM_Main(void)
12399 GameActions_MM(local_player->effective_mouse_action);
12402 void GameActions_RND_Main(void)
12407 void GameActions_RND(void)
12409 static struct MouseActionInfo mouse_action_last = { 0 };
12410 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12411 int magic_wall_x = 0, magic_wall_y = 0;
12412 int i, x, y, element, graphic, last_gfx_frame;
12414 InitPlayfieldScanModeVars();
12416 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12418 SCAN_PLAYFIELD(x, y)
12420 ChangeCount[x][y] = 0;
12421 ChangeEvent[x][y] = -1;
12425 if (game.set_centered_player)
12427 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12429 // switching to "all players" only possible if all players fit to screen
12430 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12432 game.centered_player_nr_next = game.centered_player_nr;
12433 game.set_centered_player = FALSE;
12436 // do not switch focus to non-existing (or non-active) player
12437 if (game.centered_player_nr_next >= 0 &&
12438 !stored_player[game.centered_player_nr_next].active)
12440 game.centered_player_nr_next = game.centered_player_nr;
12441 game.set_centered_player = FALSE;
12445 if (game.set_centered_player &&
12446 ScreenMovPos == 0) // screen currently aligned at tile position
12450 if (game.centered_player_nr_next == -1)
12452 setScreenCenteredToAllPlayers(&sx, &sy);
12456 sx = stored_player[game.centered_player_nr_next].jx;
12457 sy = stored_player[game.centered_player_nr_next].jy;
12460 game.centered_player_nr = game.centered_player_nr_next;
12461 game.set_centered_player = FALSE;
12463 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12464 DrawGameDoorValues();
12467 // check single step mode (set flag and clear again if any player is active)
12468 game.enter_single_step_mode =
12469 (tape.single_step && tape.recording && !tape.pausing);
12471 for (i = 0; i < MAX_PLAYERS; i++)
12473 int actual_player_action = stored_player[i].effective_action;
12476 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12477 - rnd_equinox_tetrachloride 048
12478 - rnd_equinox_tetrachloride_ii 096
12479 - rnd_emanuel_schmieg 002
12480 - doctor_sloan_ww 001, 020
12482 if (stored_player[i].MovPos == 0)
12483 CheckGravityMovement(&stored_player[i]);
12486 // overwrite programmed action with tape action
12487 if (stored_player[i].programmed_action)
12488 actual_player_action = stored_player[i].programmed_action;
12490 PlayerActions(&stored_player[i], actual_player_action);
12492 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12495 // single step pause mode may already have been toggled by "ScrollPlayer()"
12496 if (game.enter_single_step_mode && !tape.pausing)
12497 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12499 ScrollScreen(NULL, SCROLL_GO_ON);
12501 /* for backwards compatibility, the following code emulates a fixed bug that
12502 occured when pushing elements (causing elements that just made their last
12503 pushing step to already (if possible) make their first falling step in the
12504 same game frame, which is bad); this code is also needed to use the famous
12505 "spring push bug" which is used in older levels and might be wanted to be
12506 used also in newer levels, but in this case the buggy pushing code is only
12507 affecting the "spring" element and no other elements */
12509 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12511 for (i = 0; i < MAX_PLAYERS; i++)
12513 struct PlayerInfo *player = &stored_player[i];
12514 int x = player->jx;
12515 int y = player->jy;
12517 if (player->active && player->is_pushing && player->is_moving &&
12519 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12520 Tile[x][y] == EL_SPRING))
12522 ContinueMoving(x, y);
12524 // continue moving after pushing (this is actually a bug)
12525 if (!IS_MOVING(x, y))
12526 Stop[x][y] = FALSE;
12531 SCAN_PLAYFIELD(x, y)
12533 Last[x][y] = Tile[x][y];
12535 ChangeCount[x][y] = 0;
12536 ChangeEvent[x][y] = -1;
12538 // this must be handled before main playfield loop
12539 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12542 if (MovDelay[x][y] <= 0)
12546 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12549 if (MovDelay[x][y] <= 0)
12551 int element = Store[x][y];
12552 int move_direction = MovDir[x][y];
12553 int player_index_bit = Store2[x][y];
12559 TEST_DrawLevelField(x, y);
12561 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12563 if (IS_ENVELOPE(element))
12564 local_player->show_envelope = element;
12569 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12571 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12573 Debug("game:playing:GameActions_RND", "This should never happen!");
12575 ChangePage[x][y] = -1;
12579 Stop[x][y] = FALSE;
12580 if (WasJustMoving[x][y] > 0)
12581 WasJustMoving[x][y]--;
12582 if (WasJustFalling[x][y] > 0)
12583 WasJustFalling[x][y]--;
12584 if (CheckCollision[x][y] > 0)
12585 CheckCollision[x][y]--;
12586 if (CheckImpact[x][y] > 0)
12587 CheckImpact[x][y]--;
12591 /* reset finished pushing action (not done in ContinueMoving() to allow
12592 continuous pushing animation for elements with zero push delay) */
12593 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12595 ResetGfxAnimation(x, y);
12596 TEST_DrawLevelField(x, y);
12600 if (IS_BLOCKED(x, y))
12604 Blocked2Moving(x, y, &oldx, &oldy);
12605 if (!IS_MOVING(oldx, oldy))
12607 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12608 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12609 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12610 Debug("game:playing:GameActions_RND", "This should never happen!");
12616 HandleMouseAction(&mouse_action, &mouse_action_last);
12618 SCAN_PLAYFIELD(x, y)
12620 element = Tile[x][y];
12621 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12622 last_gfx_frame = GfxFrame[x][y];
12624 if (element == EL_EMPTY)
12625 graphic = el2img(GfxElementEmpty[x][y]);
12627 ResetGfxFrame(x, y);
12629 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12630 DrawLevelGraphicAnimation(x, y, graphic);
12632 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12633 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12634 ResetRandomAnimationValue(x, y);
12636 SetRandomAnimationValue(x, y);
12638 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12640 if (IS_INACTIVE(element))
12642 if (IS_ANIMATED(graphic))
12643 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12648 // this may take place after moving, so 'element' may have changed
12649 if (IS_CHANGING(x, y) &&
12650 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12652 int page = element_info[element].event_page_nr[CE_DELAY];
12654 HandleElementChange(x, y, page);
12656 element = Tile[x][y];
12657 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12660 CheckNextToConditions(x, y);
12662 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12666 element = Tile[x][y];
12667 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12669 if (IS_ANIMATED(graphic) &&
12670 !IS_MOVING(x, y) &&
12672 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12674 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12675 TEST_DrawTwinkleOnField(x, y);
12677 else if (element == EL_ACID)
12680 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12682 else if ((element == EL_EXIT_OPEN ||
12683 element == EL_EM_EXIT_OPEN ||
12684 element == EL_SP_EXIT_OPEN ||
12685 element == EL_STEEL_EXIT_OPEN ||
12686 element == EL_EM_STEEL_EXIT_OPEN ||
12687 element == EL_SP_TERMINAL ||
12688 element == EL_SP_TERMINAL_ACTIVE ||
12689 element == EL_EXTRA_TIME ||
12690 element == EL_SHIELD_NORMAL ||
12691 element == EL_SHIELD_DEADLY) &&
12692 IS_ANIMATED(graphic))
12693 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12694 else if (IS_MOVING(x, y))
12695 ContinueMoving(x, y);
12696 else if (IS_ACTIVE_BOMB(element))
12697 CheckDynamite(x, y);
12698 else if (element == EL_AMOEBA_GROWING)
12699 AmoebaGrowing(x, y);
12700 else if (element == EL_AMOEBA_SHRINKING)
12701 AmoebaShrinking(x, y);
12703 #if !USE_NEW_AMOEBA_CODE
12704 else if (IS_AMOEBALIVE(element))
12705 AmoebaReproduce(x, y);
12708 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12710 else if (element == EL_EXIT_CLOSED)
12712 else if (element == EL_EM_EXIT_CLOSED)
12714 else if (element == EL_STEEL_EXIT_CLOSED)
12715 CheckExitSteel(x, y);
12716 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12717 CheckExitSteelEM(x, y);
12718 else if (element == EL_SP_EXIT_CLOSED)
12720 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12721 element == EL_EXPANDABLE_STEELWALL_GROWING)
12723 else if (element == EL_EXPANDABLE_WALL ||
12724 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12725 element == EL_EXPANDABLE_WALL_VERTICAL ||
12726 element == EL_EXPANDABLE_WALL_ANY ||
12727 element == EL_BD_EXPANDABLE_WALL ||
12728 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12729 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12730 element == EL_EXPANDABLE_STEELWALL_ANY)
12731 CheckWallGrowing(x, y);
12732 else if (element == EL_FLAMES)
12733 CheckForDragon(x, y);
12734 else if (element == EL_EXPLOSION)
12735 ; // drawing of correct explosion animation is handled separately
12736 else if (element == EL_ELEMENT_SNAPPING ||
12737 element == EL_DIAGONAL_SHRINKING ||
12738 element == EL_DIAGONAL_GROWING)
12740 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]);
12742 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12744 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12745 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12747 if (IS_BELT_ACTIVE(element))
12748 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12750 if (game.magic_wall_active)
12752 int jx = local_player->jx, jy = local_player->jy;
12754 // play the element sound at the position nearest to the player
12755 if ((element == EL_MAGIC_WALL_FULL ||
12756 element == EL_MAGIC_WALL_ACTIVE ||
12757 element == EL_MAGIC_WALL_EMPTYING ||
12758 element == EL_BD_MAGIC_WALL_FULL ||
12759 element == EL_BD_MAGIC_WALL_ACTIVE ||
12760 element == EL_BD_MAGIC_WALL_EMPTYING ||
12761 element == EL_DC_MAGIC_WALL_FULL ||
12762 element == EL_DC_MAGIC_WALL_ACTIVE ||
12763 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12764 ABS(x - jx) + ABS(y - jy) <
12765 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12773 #if USE_NEW_AMOEBA_CODE
12774 // new experimental amoeba growth stuff
12775 if (!(FrameCounter % 8))
12777 static unsigned int random = 1684108901;
12779 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12781 x = RND(lev_fieldx);
12782 y = RND(lev_fieldy);
12783 element = Tile[x][y];
12785 if (!IS_PLAYER(x, y) &&
12786 (element == EL_EMPTY ||
12787 CAN_GROW_INTO(element) ||
12788 element == EL_QUICKSAND_EMPTY ||
12789 element == EL_QUICKSAND_FAST_EMPTY ||
12790 element == EL_ACID_SPLASH_LEFT ||
12791 element == EL_ACID_SPLASH_RIGHT))
12793 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12794 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12795 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12796 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12797 Tile[x][y] = EL_AMOEBA_DROP;
12800 random = random * 129 + 1;
12805 game.explosions_delayed = FALSE;
12807 SCAN_PLAYFIELD(x, y)
12809 element = Tile[x][y];
12811 if (ExplodeField[x][y])
12812 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12813 else if (element == EL_EXPLOSION)
12814 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12816 ExplodeField[x][y] = EX_TYPE_NONE;
12819 game.explosions_delayed = TRUE;
12821 if (game.magic_wall_active)
12823 if (!(game.magic_wall_time_left % 4))
12825 int element = Tile[magic_wall_x][magic_wall_y];
12827 if (element == EL_BD_MAGIC_WALL_FULL ||
12828 element == EL_BD_MAGIC_WALL_ACTIVE ||
12829 element == EL_BD_MAGIC_WALL_EMPTYING)
12830 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12831 else if (element == EL_DC_MAGIC_WALL_FULL ||
12832 element == EL_DC_MAGIC_WALL_ACTIVE ||
12833 element == EL_DC_MAGIC_WALL_EMPTYING)
12834 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12836 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12839 if (game.magic_wall_time_left > 0)
12841 game.magic_wall_time_left--;
12843 if (!game.magic_wall_time_left)
12845 SCAN_PLAYFIELD(x, y)
12847 element = Tile[x][y];
12849 if (element == EL_MAGIC_WALL_ACTIVE ||
12850 element == EL_MAGIC_WALL_FULL)
12852 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12853 TEST_DrawLevelField(x, y);
12855 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12856 element == EL_BD_MAGIC_WALL_FULL)
12858 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12859 TEST_DrawLevelField(x, y);
12861 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12862 element == EL_DC_MAGIC_WALL_FULL)
12864 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12865 TEST_DrawLevelField(x, y);
12869 game.magic_wall_active = FALSE;
12874 if (game.light_time_left > 0)
12876 game.light_time_left--;
12878 if (game.light_time_left == 0)
12879 RedrawAllLightSwitchesAndInvisibleElements();
12882 if (game.timegate_time_left > 0)
12884 game.timegate_time_left--;
12886 if (game.timegate_time_left == 0)
12887 CloseAllOpenTimegates();
12890 if (game.lenses_time_left > 0)
12892 game.lenses_time_left--;
12894 if (game.lenses_time_left == 0)
12895 RedrawAllInvisibleElementsForLenses();
12898 if (game.magnify_time_left > 0)
12900 game.magnify_time_left--;
12902 if (game.magnify_time_left == 0)
12903 RedrawAllInvisibleElementsForMagnifier();
12906 for (i = 0; i < MAX_PLAYERS; i++)
12908 struct PlayerInfo *player = &stored_player[i];
12910 if (SHIELD_ON(player))
12912 if (player->shield_deadly_time_left)
12913 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12914 else if (player->shield_normal_time_left)
12915 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12919 #if USE_DELAYED_GFX_REDRAW
12920 SCAN_PLAYFIELD(x, y)
12922 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12924 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12925 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12927 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12928 DrawLevelField(x, y);
12930 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12931 DrawLevelFieldCrumbled(x, y);
12933 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12934 DrawLevelFieldCrumbledNeighbours(x, y);
12936 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12937 DrawTwinkleOnField(x, y);
12940 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12945 PlayAllPlayersSound();
12947 for (i = 0; i < MAX_PLAYERS; i++)
12949 struct PlayerInfo *player = &stored_player[i];
12951 if (player->show_envelope != 0 && (!player->active ||
12952 player->MovPos == 0))
12954 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12956 player->show_envelope = 0;
12960 // use random number generator in every frame to make it less predictable
12961 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12964 mouse_action_last = mouse_action;
12967 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12969 int min_x = x, min_y = y, max_x = x, max_y = y;
12970 int scr_fieldx = getScreenFieldSizeX();
12971 int scr_fieldy = getScreenFieldSizeY();
12974 for (i = 0; i < MAX_PLAYERS; i++)
12976 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12978 if (!stored_player[i].active || &stored_player[i] == player)
12981 min_x = MIN(min_x, jx);
12982 min_y = MIN(min_y, jy);
12983 max_x = MAX(max_x, jx);
12984 max_y = MAX(max_y, jy);
12987 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12990 static boolean AllPlayersInVisibleScreen(void)
12994 for (i = 0; i < MAX_PLAYERS; i++)
12996 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12998 if (!stored_player[i].active)
13001 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13008 void ScrollLevel(int dx, int dy)
13010 int scroll_offset = 2 * TILEX_VAR;
13013 BlitBitmap(drawto_field, drawto_field,
13014 FX + TILEX_VAR * (dx == -1) - scroll_offset,
13015 FY + TILEY_VAR * (dy == -1) - scroll_offset,
13016 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
13017 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
13018 FX + TILEX_VAR * (dx == 1) - scroll_offset,
13019 FY + TILEY_VAR * (dy == 1) - scroll_offset);
13023 x = (dx == 1 ? BX1 : BX2);
13024 for (y = BY1; y <= BY2; y++)
13025 DrawScreenField(x, y);
13030 y = (dy == 1 ? BY1 : BY2);
13031 for (x = BX1; x <= BX2; x++)
13032 DrawScreenField(x, y);
13035 redraw_mask |= REDRAW_FIELD;
13038 static boolean canFallDown(struct PlayerInfo *player)
13040 int jx = player->jx, jy = player->jy;
13042 return (IN_LEV_FIELD(jx, jy + 1) &&
13043 (IS_FREE(jx, jy + 1) ||
13044 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
13045 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
13046 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
13049 static boolean canPassField(int x, int y, int move_dir)
13051 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13052 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13053 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13054 int nextx = x + dx;
13055 int nexty = y + dy;
13056 int element = Tile[x][y];
13058 return (IS_PASSABLE_FROM(element, opposite_dir) &&
13059 !CAN_MOVE(element) &&
13060 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
13061 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
13062 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
13065 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
13067 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13068 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13069 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13073 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
13074 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
13075 (IS_DIGGABLE(Tile[newx][newy]) ||
13076 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
13077 canPassField(newx, newy, move_dir)));
13080 static void CheckGravityMovement(struct PlayerInfo *player)
13082 if (player->gravity && !player->programmed_action)
13084 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
13085 int move_dir_vertical = player->effective_action & MV_VERTICAL;
13086 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
13087 int jx = player->jx, jy = player->jy;
13088 boolean player_is_moving_to_valid_field =
13089 (!player_is_snapping &&
13090 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
13091 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
13092 boolean player_can_fall_down = canFallDown(player);
13094 if (player_can_fall_down &&
13095 !player_is_moving_to_valid_field)
13096 player->programmed_action = MV_DOWN;
13100 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
13102 return CheckGravityMovement(player);
13104 if (player->gravity && !player->programmed_action)
13106 int jx = player->jx, jy = player->jy;
13107 boolean field_under_player_is_free =
13108 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
13109 boolean player_is_standing_on_valid_field =
13110 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
13111 (IS_WALKABLE(Tile[jx][jy]) &&
13112 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
13114 if (field_under_player_is_free && !player_is_standing_on_valid_field)
13115 player->programmed_action = MV_DOWN;
13120 MovePlayerOneStep()
13121 -----------------------------------------------------------------------------
13122 dx, dy: direction (non-diagonal) to try to move the player to
13123 real_dx, real_dy: direction as read from input device (can be diagonal)
13126 boolean MovePlayerOneStep(struct PlayerInfo *player,
13127 int dx, int dy, int real_dx, int real_dy)
13129 int jx = player->jx, jy = player->jy;
13130 int new_jx = jx + dx, new_jy = jy + dy;
13132 boolean player_can_move = !player->cannot_move;
13134 if (!player->active || (!dx && !dy))
13135 return MP_NO_ACTION;
13137 player->MovDir = (dx < 0 ? MV_LEFT :
13138 dx > 0 ? MV_RIGHT :
13140 dy > 0 ? MV_DOWN : MV_NONE);
13142 if (!IN_LEV_FIELD(new_jx, new_jy))
13143 return MP_NO_ACTION;
13145 if (!player_can_move)
13147 if (player->MovPos == 0)
13149 player->is_moving = FALSE;
13150 player->is_digging = FALSE;
13151 player->is_collecting = FALSE;
13152 player->is_snapping = FALSE;
13153 player->is_pushing = FALSE;
13157 if (!network.enabled && game.centered_player_nr == -1 &&
13158 !AllPlayersInSight(player, new_jx, new_jy))
13159 return MP_NO_ACTION;
13161 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG);
13162 if (can_move != MP_MOVING)
13165 // check if DigField() has caused relocation of the player
13166 if (player->jx != jx || player->jy != jy)
13167 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
13169 StorePlayer[jx][jy] = 0;
13170 player->last_jx = jx;
13171 player->last_jy = jy;
13172 player->jx = new_jx;
13173 player->jy = new_jy;
13174 StorePlayer[new_jx][new_jy] = player->element_nr;
13176 if (player->move_delay_value_next != -1)
13178 player->move_delay_value = player->move_delay_value_next;
13179 player->move_delay_value_next = -1;
13183 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
13185 player->step_counter++;
13187 PlayerVisit[jx][jy] = FrameCounter;
13189 player->is_moving = TRUE;
13192 // should better be called in MovePlayer(), but this breaks some tapes
13193 ScrollPlayer(player, SCROLL_INIT);
13199 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
13201 int jx = player->jx, jy = player->jy;
13202 int old_jx = jx, old_jy = jy;
13203 int moved = MP_NO_ACTION;
13205 if (!player->active)
13210 if (player->MovPos == 0)
13212 player->is_moving = FALSE;
13213 player->is_digging = FALSE;
13214 player->is_collecting = FALSE;
13215 player->is_snapping = FALSE;
13216 player->is_pushing = FALSE;
13222 if (player->move_delay > 0)
13225 player->move_delay = -1; // set to "uninitialized" value
13227 // store if player is automatically moved to next field
13228 player->is_auto_moving = (player->programmed_action != MV_NONE);
13230 // remove the last programmed player action
13231 player->programmed_action = 0;
13233 if (player->MovPos)
13235 // should only happen if pre-1.2 tape recordings are played
13236 // this is only for backward compatibility
13238 int original_move_delay_value = player->move_delay_value;
13241 Debug("game:playing:MovePlayer",
13242 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13246 // scroll remaining steps with finest movement resolution
13247 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13249 while (player->MovPos)
13251 ScrollPlayer(player, SCROLL_GO_ON);
13252 ScrollScreen(NULL, SCROLL_GO_ON);
13254 AdvanceFrameAndPlayerCounters(player->index_nr);
13257 BackToFront_WithFrameDelay(0);
13260 player->move_delay_value = original_move_delay_value;
13263 player->is_active = FALSE;
13265 if (player->last_move_dir & MV_HORIZONTAL)
13267 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13268 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13272 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13273 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13276 if (!moved && !player->is_active)
13278 player->is_moving = FALSE;
13279 player->is_digging = FALSE;
13280 player->is_collecting = FALSE;
13281 player->is_snapping = FALSE;
13282 player->is_pushing = FALSE;
13288 if (moved & MP_MOVING && !ScreenMovPos &&
13289 (player->index_nr == game.centered_player_nr ||
13290 game.centered_player_nr == -1))
13292 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13294 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13296 // actual player has left the screen -- scroll in that direction
13297 if (jx != old_jx) // player has moved horizontally
13298 scroll_x += (jx - old_jx);
13299 else // player has moved vertically
13300 scroll_y += (jy - old_jy);
13304 int offset_raw = game.scroll_delay_value;
13306 if (jx != old_jx) // player has moved horizontally
13308 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13309 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13310 int new_scroll_x = jx - MIDPOSX + offset_x;
13312 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13313 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13314 scroll_x = new_scroll_x;
13316 // don't scroll over playfield boundaries
13317 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13319 // don't scroll more than one field at a time
13320 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13322 // don't scroll against the player's moving direction
13323 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13324 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13325 scroll_x = old_scroll_x;
13327 else // player has moved vertically
13329 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13330 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13331 int new_scroll_y = jy - MIDPOSY + offset_y;
13333 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13334 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13335 scroll_y = new_scroll_y;
13337 // don't scroll over playfield boundaries
13338 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13340 // don't scroll more than one field at a time
13341 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13343 // don't scroll against the player's moving direction
13344 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13345 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13346 scroll_y = old_scroll_y;
13350 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13352 if (!network.enabled && game.centered_player_nr == -1 &&
13353 !AllPlayersInVisibleScreen())
13355 scroll_x = old_scroll_x;
13356 scroll_y = old_scroll_y;
13360 ScrollScreen(player, SCROLL_INIT);
13361 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13366 player->StepFrame = 0;
13368 if (moved & MP_MOVING)
13370 if (old_jx != jx && old_jy == jy)
13371 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13372 else if (old_jx == jx && old_jy != jy)
13373 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13375 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13377 player->last_move_dir = player->MovDir;
13378 player->is_moving = TRUE;
13379 player->is_snapping = FALSE;
13380 player->is_switching = FALSE;
13381 player->is_dropping = FALSE;
13382 player->is_dropping_pressed = FALSE;
13383 player->drop_pressed_delay = 0;
13386 // should better be called here than above, but this breaks some tapes
13387 ScrollPlayer(player, SCROLL_INIT);
13392 CheckGravityMovementWhenNotMoving(player);
13394 player->is_moving = FALSE;
13396 /* at this point, the player is allowed to move, but cannot move right now
13397 (e.g. because of something blocking the way) -- ensure that the player
13398 is also allowed to move in the next frame (in old versions before 3.1.1,
13399 the player was forced to wait again for eight frames before next try) */
13401 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13402 player->move_delay = 0; // allow direct movement in the next frame
13405 if (player->move_delay == -1) // not yet initialized by DigField()
13406 player->move_delay = player->move_delay_value;
13408 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13410 TestIfPlayerTouchesBadThing(jx, jy);
13411 TestIfPlayerTouchesCustomElement(jx, jy);
13414 if (!player->active)
13415 RemovePlayer(player);
13420 void ScrollPlayer(struct PlayerInfo *player, int mode)
13422 int jx = player->jx, jy = player->jy;
13423 int last_jx = player->last_jx, last_jy = player->last_jy;
13424 int move_stepsize = TILEX / player->move_delay_value;
13426 if (!player->active)
13429 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13432 if (mode == SCROLL_INIT)
13434 player->actual_frame_counter.count = FrameCounter;
13435 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13437 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13438 Tile[last_jx][last_jy] == EL_EMPTY)
13440 int last_field_block_delay = 0; // start with no blocking at all
13441 int block_delay_adjustment = player->block_delay_adjustment;
13443 // if player blocks last field, add delay for exactly one move
13444 if (player->block_last_field)
13446 last_field_block_delay += player->move_delay_value;
13448 // when blocking enabled, prevent moving up despite gravity
13449 if (player->gravity && player->MovDir == MV_UP)
13450 block_delay_adjustment = -1;
13453 // add block delay adjustment (also possible when not blocking)
13454 last_field_block_delay += block_delay_adjustment;
13456 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13457 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13460 if (player->MovPos != 0) // player has not yet reached destination
13463 else if (!FrameReached(&player->actual_frame_counter))
13466 if (player->MovPos != 0)
13468 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13469 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13471 // before DrawPlayer() to draw correct player graphic for this case
13472 if (player->MovPos == 0)
13473 CheckGravityMovement(player);
13476 if (player->MovPos == 0) // player reached destination field
13478 if (player->move_delay_reset_counter > 0)
13480 player->move_delay_reset_counter--;
13482 if (player->move_delay_reset_counter == 0)
13484 // continue with normal speed after quickly moving through gate
13485 HALVE_PLAYER_SPEED(player);
13487 // be able to make the next move without delay
13488 player->move_delay = 0;
13492 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13493 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13494 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13495 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13496 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13497 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13498 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13499 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13501 ExitPlayer(player);
13503 if (game.players_still_needed == 0 &&
13504 (game.friends_still_needed == 0 ||
13505 IS_SP_ELEMENT(Tile[jx][jy])))
13509 player->last_jx = jx;
13510 player->last_jy = jy;
13512 // this breaks one level: "machine", level 000
13514 int move_direction = player->MovDir;
13515 int enter_side = MV_DIR_OPPOSITE(move_direction);
13516 int leave_side = move_direction;
13517 int old_jx = last_jx;
13518 int old_jy = last_jy;
13519 int old_element = Tile[old_jx][old_jy];
13520 int new_element = Tile[jx][jy];
13522 if (IS_CUSTOM_ELEMENT(old_element))
13523 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13525 player->index_bit, leave_side);
13527 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13528 CE_PLAYER_LEAVES_X,
13529 player->index_bit, leave_side);
13531 // needed because pushed element has not yet reached its destination,
13532 // so it would trigger a change event at its previous field location
13533 if (!player->is_pushing)
13535 if (IS_CUSTOM_ELEMENT(new_element))
13536 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13537 player->index_bit, enter_side);
13539 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13540 CE_PLAYER_ENTERS_X,
13541 player->index_bit, enter_side);
13544 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13545 CE_MOVE_OF_X, move_direction);
13548 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13550 TestIfPlayerTouchesBadThing(jx, jy);
13551 TestIfPlayerTouchesCustomElement(jx, jy);
13553 // needed because pushed element has not yet reached its destination,
13554 // so it would trigger a change event at its previous field location
13555 if (!player->is_pushing)
13556 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13558 if (level.finish_dig_collect &&
13559 (player->is_digging || player->is_collecting))
13561 int last_element = player->last_removed_element;
13562 int move_direction = player->MovDir;
13563 int enter_side = MV_DIR_OPPOSITE(move_direction);
13564 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13565 CE_PLAYER_COLLECTS_X);
13567 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13568 player->index_bit, enter_side);
13570 player->last_removed_element = EL_UNDEFINED;
13573 if (!player->active)
13574 RemovePlayer(player);
13577 if (level.use_step_counter)
13578 CheckLevelTime_StepCounter();
13580 if (tape.single_step && tape.recording && !tape.pausing &&
13581 !player->programmed_action)
13582 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13584 if (!player->programmed_action)
13585 CheckSaveEngineSnapshot(player);
13589 void ScrollScreen(struct PlayerInfo *player, int mode)
13591 static DelayCounter screen_frame_counter = { 0 };
13593 if (mode == SCROLL_INIT)
13595 // set scrolling step size according to actual player's moving speed
13596 ScrollStepSize = TILEX / player->move_delay_value;
13598 screen_frame_counter.count = FrameCounter;
13599 screen_frame_counter.value = 1;
13601 ScreenMovDir = player->MovDir;
13602 ScreenMovPos = player->MovPos;
13603 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13606 else if (!FrameReached(&screen_frame_counter))
13611 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13612 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13613 redraw_mask |= REDRAW_FIELD;
13616 ScreenMovDir = MV_NONE;
13619 void CheckNextToConditions(int x, int y)
13621 int element = Tile[x][y];
13623 if (IS_PLAYER(x, y))
13624 TestIfPlayerNextToCustomElement(x, y);
13626 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13627 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13628 TestIfElementNextToCustomElement(x, y);
13631 void TestIfPlayerNextToCustomElement(int x, int y)
13633 struct XY *xy = xy_topdown;
13634 static int trigger_sides[4][2] =
13636 // center side border side
13637 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13638 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13639 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13640 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13644 if (!IS_PLAYER(x, y))
13647 struct PlayerInfo *player = PLAYERINFO(x, y);
13649 if (player->is_moving)
13652 for (i = 0; i < NUM_DIRECTIONS; i++)
13654 int xx = x + xy[i].x;
13655 int yy = y + xy[i].y;
13656 int border_side = trigger_sides[i][1];
13657 int border_element;
13659 if (!IN_LEV_FIELD(xx, yy))
13662 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13663 continue; // center and border element not connected
13665 border_element = Tile[xx][yy];
13667 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13668 player->index_bit, border_side);
13669 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13670 CE_PLAYER_NEXT_TO_X,
13671 player->index_bit, border_side);
13673 /* use player element that is initially defined in the level playfield,
13674 not the player element that corresponds to the runtime player number
13675 (example: a level that contains EL_PLAYER_3 as the only player would
13676 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13678 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13679 CE_NEXT_TO_X, border_side);
13683 void TestIfPlayerTouchesCustomElement(int x, int y)
13685 struct XY *xy = xy_topdown;
13686 static int trigger_sides[4][2] =
13688 // center side border side
13689 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13690 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13691 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13692 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13694 static int touch_dir[4] =
13696 MV_LEFT | MV_RIGHT,
13701 int center_element = Tile[x][y]; // should always be non-moving!
13704 for (i = 0; i < NUM_DIRECTIONS; i++)
13706 int xx = x + xy[i].x;
13707 int yy = y + xy[i].y;
13708 int center_side = trigger_sides[i][0];
13709 int border_side = trigger_sides[i][1];
13710 int border_element;
13712 if (!IN_LEV_FIELD(xx, yy))
13715 if (IS_PLAYER(x, y)) // player found at center element
13717 struct PlayerInfo *player = PLAYERINFO(x, y);
13719 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13720 border_element = Tile[xx][yy]; // may be moving!
13721 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13722 border_element = Tile[xx][yy];
13723 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13724 border_element = MovingOrBlocked2Element(xx, yy);
13726 continue; // center and border element do not touch
13728 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13729 player->index_bit, border_side);
13730 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13731 CE_PLAYER_TOUCHES_X,
13732 player->index_bit, border_side);
13735 /* use player element that is initially defined in the level playfield,
13736 not the player element that corresponds to the runtime player number
13737 (example: a level that contains EL_PLAYER_3 as the only player would
13738 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13739 int player_element = PLAYERINFO(x, y)->initial_element;
13741 // as element "X" is the player here, check opposite (center) side
13742 CheckElementChangeBySide(xx, yy, border_element, player_element,
13743 CE_TOUCHING_X, center_side);
13746 else if (IS_PLAYER(xx, yy)) // player found at border element
13748 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13750 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13752 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13753 continue; // center and border element do not touch
13756 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13757 player->index_bit, center_side);
13758 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13759 CE_PLAYER_TOUCHES_X,
13760 player->index_bit, center_side);
13763 /* use player element that is initially defined in the level playfield,
13764 not the player element that corresponds to the runtime player number
13765 (example: a level that contains EL_PLAYER_3 as the only player would
13766 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13767 int player_element = PLAYERINFO(xx, yy)->initial_element;
13769 // as element "X" is the player here, check opposite (border) side
13770 CheckElementChangeBySide(x, y, center_element, player_element,
13771 CE_TOUCHING_X, border_side);
13779 void TestIfElementNextToCustomElement(int x, int y)
13781 struct XY *xy = xy_topdown;
13782 static int trigger_sides[4][2] =
13784 // center side border side
13785 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13786 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13787 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13788 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13790 int center_element = Tile[x][y]; // should always be non-moving!
13793 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13796 for (i = 0; i < NUM_DIRECTIONS; i++)
13798 int xx = x + xy[i].x;
13799 int yy = y + xy[i].y;
13800 int border_side = trigger_sides[i][1];
13801 int border_element;
13803 if (!IN_LEV_FIELD(xx, yy))
13806 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13807 continue; // center and border element not connected
13809 border_element = Tile[xx][yy];
13811 // check for change of center element (but change it only once)
13812 if (CheckElementChangeBySide(x, y, center_element, border_element,
13813 CE_NEXT_TO_X, border_side))
13818 void TestIfElementTouchesCustomElement(int x, int y)
13820 struct XY *xy = xy_topdown;
13821 static int trigger_sides[4][2] =
13823 // center side border side
13824 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13825 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13826 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13827 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13829 static int touch_dir[4] =
13831 MV_LEFT | MV_RIGHT,
13836 boolean change_center_element = FALSE;
13837 int center_element = Tile[x][y]; // should always be non-moving!
13838 int border_element_old[NUM_DIRECTIONS];
13841 for (i = 0; i < NUM_DIRECTIONS; i++)
13843 int xx = x + xy[i].x;
13844 int yy = y + xy[i].y;
13845 int border_element;
13847 border_element_old[i] = -1;
13849 if (!IN_LEV_FIELD(xx, yy))
13852 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13853 border_element = Tile[xx][yy]; // may be moving!
13854 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13855 border_element = Tile[xx][yy];
13856 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13857 border_element = MovingOrBlocked2Element(xx, yy);
13859 continue; // center and border element do not touch
13861 border_element_old[i] = border_element;
13864 for (i = 0; i < NUM_DIRECTIONS; i++)
13866 int xx = x + xy[i].x;
13867 int yy = y + xy[i].y;
13868 int center_side = trigger_sides[i][0];
13869 int border_element = border_element_old[i];
13871 if (border_element == -1)
13874 // check for change of border element
13875 CheckElementChangeBySide(xx, yy, border_element, center_element,
13876 CE_TOUCHING_X, center_side);
13878 // (center element cannot be player, so we don't have to check this here)
13881 for (i = 0; i < NUM_DIRECTIONS; i++)
13883 int xx = x + xy[i].x;
13884 int yy = y + xy[i].y;
13885 int border_side = trigger_sides[i][1];
13886 int border_element = border_element_old[i];
13888 if (border_element == -1)
13891 // check for change of center element (but change it only once)
13892 if (!change_center_element)
13893 change_center_element =
13894 CheckElementChangeBySide(x, y, center_element, border_element,
13895 CE_TOUCHING_X, border_side);
13897 if (IS_PLAYER(xx, yy))
13899 /* use player element that is initially defined in the level playfield,
13900 not the player element that corresponds to the runtime player number
13901 (example: a level that contains EL_PLAYER_3 as the only player would
13902 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13903 int player_element = PLAYERINFO(xx, yy)->initial_element;
13905 // as element "X" is the player here, check opposite (border) side
13906 CheckElementChangeBySide(x, y, center_element, player_element,
13907 CE_TOUCHING_X, border_side);
13912 void TestIfElementHitsCustomElement(int x, int y, int direction)
13914 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13915 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13916 int hitx = x + dx, hity = y + dy;
13917 int hitting_element = Tile[x][y];
13918 int touched_element;
13920 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13923 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13924 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13926 if (IN_LEV_FIELD(hitx, hity))
13928 int opposite_direction = MV_DIR_OPPOSITE(direction);
13929 int hitting_side = direction;
13930 int touched_side = opposite_direction;
13931 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13932 MovDir[hitx][hity] != direction ||
13933 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13939 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13940 CE_HITTING_X, touched_side);
13942 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13943 CE_HIT_BY_X, hitting_side);
13945 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13946 CE_HIT_BY_SOMETHING, opposite_direction);
13948 if (IS_PLAYER(hitx, hity))
13950 /* use player element that is initially defined in the level playfield,
13951 not the player element that corresponds to the runtime player number
13952 (example: a level that contains EL_PLAYER_3 as the only player would
13953 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13954 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13956 CheckElementChangeBySide(x, y, hitting_element, player_element,
13957 CE_HITTING_X, touched_side);
13962 // "hitting something" is also true when hitting the playfield border
13963 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13964 CE_HITTING_SOMETHING, direction);
13967 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13969 int i, kill_x = -1, kill_y = -1;
13971 int bad_element = -1;
13972 struct XY *test_xy = xy_topdown;
13973 static int test_dir[4] =
13981 for (i = 0; i < NUM_DIRECTIONS; i++)
13983 int test_x, test_y, test_move_dir, test_element;
13985 test_x = good_x + test_xy[i].x;
13986 test_y = good_y + test_xy[i].y;
13988 if (!IN_LEV_FIELD(test_x, test_y))
13992 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13994 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13996 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13997 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13999 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
14000 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
14004 bad_element = test_element;
14010 if (kill_x != -1 || kill_y != -1)
14012 if (IS_PLAYER(good_x, good_y))
14014 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
14016 if (player->shield_deadly_time_left > 0 &&
14017 !IS_INDESTRUCTIBLE(bad_element))
14018 Bang(kill_x, kill_y);
14019 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
14020 KillPlayer(player);
14023 Bang(good_x, good_y);
14027 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
14029 int i, kill_x = -1, kill_y = -1;
14030 int bad_element = Tile[bad_x][bad_y];
14031 struct XY *test_xy = xy_topdown;
14032 static int touch_dir[4] =
14034 MV_LEFT | MV_RIGHT,
14039 static int test_dir[4] =
14047 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
14050 for (i = 0; i < NUM_DIRECTIONS; i++)
14052 int test_x, test_y, test_move_dir, test_element;
14054 test_x = bad_x + test_xy[i].x;
14055 test_y = bad_y + test_xy[i].y;
14057 if (!IN_LEV_FIELD(test_x, test_y))
14061 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14063 test_element = Tile[test_x][test_y];
14065 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
14066 2nd case: DONT_TOUCH style bad thing does not move away from good thing
14068 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
14069 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
14071 // good thing is player or penguin that does not move away
14072 if (IS_PLAYER(test_x, test_y))
14074 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14076 if (bad_element == EL_ROBOT && player->is_moving)
14077 continue; // robot does not kill player if he is moving
14079 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
14081 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
14082 continue; // center and border element do not touch
14090 else if (test_element == EL_PENGUIN)
14100 if (kill_x != -1 || kill_y != -1)
14102 if (IS_PLAYER(kill_x, kill_y))
14104 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14106 if (player->shield_deadly_time_left > 0 &&
14107 !IS_INDESTRUCTIBLE(bad_element))
14108 Bang(bad_x, bad_y);
14109 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14110 KillPlayer(player);
14113 Bang(kill_x, kill_y);
14117 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
14119 int bad_element = Tile[bad_x][bad_y];
14120 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
14121 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
14122 int test_x = bad_x + dx, test_y = bad_y + dy;
14123 int test_move_dir, test_element;
14124 int kill_x = -1, kill_y = -1;
14126 if (!IN_LEV_FIELD(test_x, test_y))
14130 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14132 test_element = Tile[test_x][test_y];
14134 if (test_move_dir != bad_move_dir)
14136 // good thing can be player or penguin that does not move away
14137 if (IS_PLAYER(test_x, test_y))
14139 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14141 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
14142 player as being hit when he is moving towards the bad thing, because
14143 the "get hit by" condition would be lost after the player stops) */
14144 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
14145 return; // player moves away from bad thing
14150 else if (test_element == EL_PENGUIN)
14157 if (kill_x != -1 || kill_y != -1)
14159 if (IS_PLAYER(kill_x, kill_y))
14161 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14163 if (player->shield_deadly_time_left > 0 &&
14164 !IS_INDESTRUCTIBLE(bad_element))
14165 Bang(bad_x, bad_y);
14166 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14167 KillPlayer(player);
14170 Bang(kill_x, kill_y);
14174 void TestIfPlayerTouchesBadThing(int x, int y)
14176 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14179 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
14181 TestIfGoodThingHitsBadThing(x, y, move_dir);
14184 void TestIfBadThingTouchesPlayer(int x, int y)
14186 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14189 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
14191 TestIfBadThingHitsGoodThing(x, y, move_dir);
14194 void TestIfFriendTouchesBadThing(int x, int y)
14196 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14199 void TestIfBadThingTouchesFriend(int x, int y)
14201 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14204 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
14206 int i, kill_x = bad_x, kill_y = bad_y;
14207 struct XY *xy = xy_topdown;
14209 for (i = 0; i < NUM_DIRECTIONS; i++)
14213 x = bad_x + xy[i].x;
14214 y = bad_y + xy[i].y;
14215 if (!IN_LEV_FIELD(x, y))
14218 element = Tile[x][y];
14219 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14220 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14228 if (kill_x != bad_x || kill_y != bad_y)
14229 Bang(bad_x, bad_y);
14232 void KillPlayer(struct PlayerInfo *player)
14234 int jx = player->jx, jy = player->jy;
14236 if (!player->active)
14240 Debug("game:playing:KillPlayer",
14241 "0: killed == %d, active == %d, reanimated == %d",
14242 player->killed, player->active, player->reanimated);
14245 /* the following code was introduced to prevent an infinite loop when calling
14247 -> CheckTriggeredElementChangeExt()
14248 -> ExecuteCustomElementAction()
14250 -> (infinitely repeating the above sequence of function calls)
14251 which occurs when killing the player while having a CE with the setting
14252 "kill player X when explosion of <player X>"; the solution using a new
14253 field "player->killed" was chosen for backwards compatibility, although
14254 clever use of the fields "player->active" etc. would probably also work */
14256 if (player->killed)
14260 player->killed = TRUE;
14262 // remove accessible field at the player's position
14263 RemoveField(jx, jy);
14265 // deactivate shield (else Bang()/Explode() would not work right)
14266 player->shield_normal_time_left = 0;
14267 player->shield_deadly_time_left = 0;
14270 Debug("game:playing:KillPlayer",
14271 "1: killed == %d, active == %d, reanimated == %d",
14272 player->killed, player->active, player->reanimated);
14278 Debug("game:playing:KillPlayer",
14279 "2: killed == %d, active == %d, reanimated == %d",
14280 player->killed, player->active, player->reanimated);
14283 if (player->reanimated) // killed player may have been reanimated
14284 player->killed = player->reanimated = FALSE;
14286 BuryPlayer(player);
14289 static void KillPlayerUnlessEnemyProtected(int x, int y)
14291 if (!PLAYER_ENEMY_PROTECTED(x, y))
14292 KillPlayer(PLAYERINFO(x, y));
14295 static void KillPlayerUnlessExplosionProtected(int x, int y)
14297 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14298 KillPlayer(PLAYERINFO(x, y));
14301 void BuryPlayer(struct PlayerInfo *player)
14303 int jx = player->jx, jy = player->jy;
14305 if (!player->active)
14308 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14310 RemovePlayer(player);
14312 player->buried = TRUE;
14314 if (game.all_players_gone)
14315 game.GameOver = TRUE;
14318 void RemovePlayer(struct PlayerInfo *player)
14320 int jx = player->jx, jy = player->jy;
14321 int i, found = FALSE;
14323 player->present = FALSE;
14324 player->active = FALSE;
14326 // required for some CE actions (even if the player is not active anymore)
14327 player->MovPos = 0;
14329 if (!ExplodeField[jx][jy])
14330 StorePlayer[jx][jy] = 0;
14332 if (player->is_moving)
14333 TEST_DrawLevelField(player->last_jx, player->last_jy);
14335 for (i = 0; i < MAX_PLAYERS; i++)
14336 if (stored_player[i].active)
14341 game.all_players_gone = TRUE;
14342 game.GameOver = TRUE;
14345 game.exit_x = game.robot_wheel_x = jx;
14346 game.exit_y = game.robot_wheel_y = jy;
14349 void ExitPlayer(struct PlayerInfo *player)
14351 DrawPlayer(player); // needed here only to cleanup last field
14352 RemovePlayer(player);
14354 if (game.players_still_needed > 0)
14355 game.players_still_needed--;
14358 static void SetFieldForSnapping(int x, int y, int element, int direction,
14359 int player_index_bit)
14361 struct ElementInfo *ei = &element_info[element];
14362 int direction_bit = MV_DIR_TO_BIT(direction);
14363 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14364 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14365 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14367 Tile[x][y] = EL_ELEMENT_SNAPPING;
14368 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14369 MovDir[x][y] = direction;
14370 Store[x][y] = element;
14371 Store2[x][y] = player_index_bit;
14373 ResetGfxAnimation(x, y);
14375 GfxElement[x][y] = element;
14376 GfxAction[x][y] = action;
14377 GfxDir[x][y] = direction;
14378 GfxFrame[x][y] = -1;
14381 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14382 int player_index_bit)
14384 TestIfElementTouchesCustomElement(x, y); // for empty space
14386 if (level.finish_dig_collect)
14388 int dig_side = MV_DIR_OPPOSITE(direction);
14389 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14390 CE_PLAYER_COLLECTS_X);
14392 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14393 player_index_bit, dig_side);
14394 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14395 player_index_bit, dig_side);
14400 =============================================================================
14401 checkDiagonalPushing()
14402 -----------------------------------------------------------------------------
14403 check if diagonal input device direction results in pushing of object
14404 (by checking if the alternative direction is walkable, diggable, ...)
14405 =============================================================================
14408 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14409 int x, int y, int real_dx, int real_dy)
14411 int jx, jy, dx, dy, xx, yy;
14413 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14416 // diagonal direction: check alternative direction
14421 xx = jx + (dx == 0 ? real_dx : 0);
14422 yy = jy + (dy == 0 ? real_dy : 0);
14424 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14428 =============================================================================
14430 -----------------------------------------------------------------------------
14431 x, y: field next to player (non-diagonal) to try to dig to
14432 real_dx, real_dy: direction as read from input device (can be diagonal)
14433 =============================================================================
14436 static int DigField(struct PlayerInfo *player,
14437 int oldx, int oldy, int x, int y,
14438 int real_dx, int real_dy, int mode)
14440 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14441 boolean player_was_pushing = player->is_pushing;
14442 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14443 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14444 int jx = oldx, jy = oldy;
14445 int dx = x - jx, dy = y - jy;
14446 int nextx = x + dx, nexty = y + dy;
14447 int move_direction = (dx == -1 ? MV_LEFT :
14448 dx == +1 ? MV_RIGHT :
14450 dy == +1 ? MV_DOWN : MV_NONE);
14451 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14452 int dig_side = MV_DIR_OPPOSITE(move_direction);
14453 int old_element = Tile[jx][jy];
14454 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14457 if (is_player) // function can also be called by EL_PENGUIN
14459 if (player->MovPos == 0)
14461 player->is_digging = FALSE;
14462 player->is_collecting = FALSE;
14465 if (player->MovPos == 0) // last pushing move finished
14466 player->is_pushing = FALSE;
14468 if (mode == DF_NO_PUSH) // player just stopped pushing
14470 player->is_switching = FALSE;
14471 player->push_delay = -1;
14473 return MP_NO_ACTION;
14476 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14477 old_element = Back[jx][jy];
14479 // in case of element dropped at player position, check background
14480 else if (Back[jx][jy] != EL_EMPTY &&
14481 game.engine_version >= VERSION_IDENT(2,2,0,0))
14482 old_element = Back[jx][jy];
14484 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14485 return MP_NO_ACTION; // field has no opening in this direction
14487 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction))
14488 return MP_NO_ACTION; // field has no opening in this direction
14490 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14494 Tile[jx][jy] = player->artwork_element;
14495 InitMovingField(jx, jy, MV_DOWN);
14496 Store[jx][jy] = EL_ACID;
14497 ContinueMoving(jx, jy);
14498 BuryPlayer(player);
14500 return MP_DONT_RUN_INTO;
14503 if (player_can_move && DONT_RUN_INTO(element))
14505 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14507 return MP_DONT_RUN_INTO;
14510 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14511 return MP_NO_ACTION;
14513 collect_count = element_info[element].collect_count_initial;
14515 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14516 return MP_NO_ACTION;
14518 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14519 player_can_move = player_can_move_or_snap;
14521 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14522 game.engine_version >= VERSION_IDENT(2,2,0,0))
14524 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14525 player->index_bit, dig_side);
14526 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14527 player->index_bit, dig_side);
14529 if (element == EL_DC_LANDMINE)
14532 if (Tile[x][y] != element) // field changed by snapping
14535 return MP_NO_ACTION;
14538 if (player->gravity && is_player && !player->is_auto_moving &&
14539 canFallDown(player) && move_direction != MV_DOWN &&
14540 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14541 return MP_NO_ACTION; // player cannot walk here due to gravity
14543 if (player_can_move &&
14544 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14546 int sound_element = SND_ELEMENT(element);
14547 int sound_action = ACTION_WALKING;
14549 if (IS_RND_GATE(element))
14551 if (!player->key[RND_GATE_NR(element)])
14552 return MP_NO_ACTION;
14554 else if (IS_RND_GATE_GRAY(element))
14556 if (!player->key[RND_GATE_GRAY_NR(element)])
14557 return MP_NO_ACTION;
14559 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14561 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14562 return MP_NO_ACTION;
14564 else if (element == EL_EXIT_OPEN ||
14565 element == EL_EM_EXIT_OPEN ||
14566 element == EL_EM_EXIT_OPENING ||
14567 element == EL_STEEL_EXIT_OPEN ||
14568 element == EL_EM_STEEL_EXIT_OPEN ||
14569 element == EL_EM_STEEL_EXIT_OPENING ||
14570 element == EL_SP_EXIT_OPEN ||
14571 element == EL_SP_EXIT_OPENING)
14573 sound_action = ACTION_PASSING; // player is passing exit
14575 else if (element == EL_EMPTY)
14577 sound_action = ACTION_MOVING; // nothing to walk on
14580 // play sound from background or player, whatever is available
14581 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14582 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14584 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14586 else if (player_can_move &&
14587 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14589 if (!ACCESS_FROM(element, opposite_direction))
14590 return MP_NO_ACTION; // field not accessible from this direction
14592 if (CAN_MOVE(element)) // only fixed elements can be passed!
14593 return MP_NO_ACTION;
14595 if (IS_EM_GATE(element))
14597 if (!player->key[EM_GATE_NR(element)])
14598 return MP_NO_ACTION;
14600 else if (IS_EM_GATE_GRAY(element))
14602 if (!player->key[EM_GATE_GRAY_NR(element)])
14603 return MP_NO_ACTION;
14605 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14607 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14608 return MP_NO_ACTION;
14610 else if (IS_EMC_GATE(element))
14612 if (!player->key[EMC_GATE_NR(element)])
14613 return MP_NO_ACTION;
14615 else if (IS_EMC_GATE_GRAY(element))
14617 if (!player->key[EMC_GATE_GRAY_NR(element)])
14618 return MP_NO_ACTION;
14620 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14622 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14623 return MP_NO_ACTION;
14625 else if (element == EL_DC_GATE_WHITE ||
14626 element == EL_DC_GATE_WHITE_GRAY ||
14627 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14629 if (player->num_white_keys == 0)
14630 return MP_NO_ACTION;
14632 player->num_white_keys--;
14634 else if (IS_SP_PORT(element))
14636 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14637 element == EL_SP_GRAVITY_PORT_RIGHT ||
14638 element == EL_SP_GRAVITY_PORT_UP ||
14639 element == EL_SP_GRAVITY_PORT_DOWN)
14640 player->gravity = !player->gravity;
14641 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14642 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14643 element == EL_SP_GRAVITY_ON_PORT_UP ||
14644 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14645 player->gravity = TRUE;
14646 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14647 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14648 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14649 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14650 player->gravity = FALSE;
14653 // automatically move to the next field with double speed
14654 player->programmed_action = move_direction;
14656 if (player->move_delay_reset_counter == 0)
14658 player->move_delay_reset_counter = 2; // two double speed steps
14660 DOUBLE_PLAYER_SPEED(player);
14663 PlayLevelSoundAction(x, y, ACTION_PASSING);
14665 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14669 if (mode != DF_SNAP)
14671 GfxElement[x][y] = GFX_ELEMENT(element);
14672 player->is_digging = TRUE;
14675 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14677 // use old behaviour for old levels (digging)
14678 if (!level.finish_dig_collect)
14680 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14681 player->index_bit, dig_side);
14683 // if digging triggered player relocation, finish digging tile
14684 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14685 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14688 if (mode == DF_SNAP)
14690 if (level.block_snap_field)
14691 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14693 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14695 // use old behaviour for old levels (snapping)
14696 if (!level.finish_dig_collect)
14697 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14698 player->index_bit, dig_side);
14701 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14705 if (is_player && mode != DF_SNAP)
14707 GfxElement[x][y] = element;
14708 player->is_collecting = TRUE;
14711 if (element == EL_SPEED_PILL)
14713 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14715 else if (element == EL_EXTRA_TIME && level.time > 0)
14717 TimeLeft += level.extra_time;
14719 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14721 DisplayGameControlValues();
14723 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14725 int shield_time = (element == EL_SHIELD_DEADLY ?
14726 level.shield_deadly_time :
14727 level.shield_normal_time);
14729 player->shield_normal_time_left += shield_time;
14730 if (element == EL_SHIELD_DEADLY)
14731 player->shield_deadly_time_left += shield_time;
14733 else if (element == EL_DYNAMITE ||
14734 element == EL_EM_DYNAMITE ||
14735 element == EL_SP_DISK_RED)
14737 if (player->inventory_size < MAX_INVENTORY_SIZE)
14738 player->inventory_element[player->inventory_size++] = element;
14740 DrawGameDoorValues();
14742 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14744 player->dynabomb_count++;
14745 player->dynabombs_left++;
14747 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14749 player->dynabomb_size++;
14751 else if (element == EL_DYNABOMB_INCREASE_POWER)
14753 player->dynabomb_xl = TRUE;
14755 else if (IS_KEY(element))
14757 player->key[KEY_NR(element)] = TRUE;
14759 DrawGameDoorValues();
14761 else if (element == EL_DC_KEY_WHITE)
14763 player->num_white_keys++;
14765 // display white keys?
14766 // DrawGameDoorValues();
14768 else if (IS_ENVELOPE(element))
14770 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14772 if (!wait_for_snapping)
14773 player->show_envelope = element;
14775 else if (element == EL_EMC_LENSES)
14777 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14779 RedrawAllInvisibleElementsForLenses();
14781 else if (element == EL_EMC_MAGNIFIER)
14783 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14785 RedrawAllInvisibleElementsForMagnifier();
14787 else if (IS_DROPPABLE(element) ||
14788 IS_THROWABLE(element)) // can be collected and dropped
14792 if (collect_count == 0)
14793 player->inventory_infinite_element = element;
14795 for (i = 0; i < collect_count; i++)
14796 if (player->inventory_size < MAX_INVENTORY_SIZE)
14797 player->inventory_element[player->inventory_size++] = element;
14799 DrawGameDoorValues();
14801 else if (collect_count > 0)
14803 game.gems_still_needed -= collect_count;
14804 if (game.gems_still_needed < 0)
14805 game.gems_still_needed = 0;
14807 game.snapshot.collected_item = TRUE;
14809 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14811 DisplayGameControlValues();
14814 RaiseScoreElement(element);
14815 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14817 // use old behaviour for old levels (collecting)
14818 if (!level.finish_dig_collect && is_player)
14820 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14821 player->index_bit, dig_side);
14823 // if collecting triggered player relocation, finish collecting tile
14824 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14825 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14828 if (mode == DF_SNAP)
14830 if (level.block_snap_field)
14831 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14833 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14835 // use old behaviour for old levels (snapping)
14836 if (!level.finish_dig_collect)
14837 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14838 player->index_bit, dig_side);
14841 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14843 if (mode == DF_SNAP && element != EL_BD_ROCK)
14844 return MP_NO_ACTION;
14846 if (CAN_FALL(element) && dy)
14847 return MP_NO_ACTION;
14849 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14850 !(element == EL_SPRING && level.use_spring_bug))
14851 return MP_NO_ACTION;
14853 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14854 ((move_direction & MV_VERTICAL &&
14855 ((element_info[element].move_pattern & MV_LEFT &&
14856 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14857 (element_info[element].move_pattern & MV_RIGHT &&
14858 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14859 (move_direction & MV_HORIZONTAL &&
14860 ((element_info[element].move_pattern & MV_UP &&
14861 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14862 (element_info[element].move_pattern & MV_DOWN &&
14863 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14864 return MP_NO_ACTION;
14866 // do not push elements already moving away faster than player
14867 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14868 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14869 return MP_NO_ACTION;
14871 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14873 if (player->push_delay_value == -1 || !player_was_pushing)
14874 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14876 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14878 if (player->push_delay_value == -1)
14879 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14881 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14883 if (!player->is_pushing)
14884 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14887 player->is_pushing = TRUE;
14888 player->is_active = TRUE;
14890 if (!(IN_LEV_FIELD(nextx, nexty) &&
14891 (IS_FREE(nextx, nexty) ||
14892 (IS_SB_ELEMENT(element) &&
14893 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14894 (IS_CUSTOM_ELEMENT(element) &&
14895 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14896 return MP_NO_ACTION;
14898 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14899 return MP_NO_ACTION;
14901 if (player->push_delay == -1) // new pushing; restart delay
14902 player->push_delay = 0;
14904 if (player->push_delay < player->push_delay_value &&
14905 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14906 element != EL_SPRING && element != EL_BALLOON)
14908 // make sure that there is no move delay before next try to push
14909 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14910 player->move_delay = 0;
14912 return MP_NO_ACTION;
14915 if (IS_CUSTOM_ELEMENT(element) &&
14916 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14918 if (!DigFieldByCE(nextx, nexty, element))
14919 return MP_NO_ACTION;
14922 if (IS_SB_ELEMENT(element))
14924 boolean sokoban_task_solved = FALSE;
14926 if (element == EL_SOKOBAN_FIELD_FULL)
14928 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14930 IncrementSokobanFieldsNeeded();
14931 IncrementSokobanObjectsNeeded();
14934 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14936 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14938 DecrementSokobanFieldsNeeded();
14939 DecrementSokobanObjectsNeeded();
14941 // sokoban object was pushed from empty field to sokoban field
14942 if (Back[x][y] == EL_EMPTY)
14943 sokoban_task_solved = TRUE;
14946 Tile[x][y] = EL_SOKOBAN_OBJECT;
14948 if (Back[x][y] == Back[nextx][nexty])
14949 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14950 else if (Back[x][y] != 0)
14951 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14954 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14957 if (sokoban_task_solved &&
14958 game.sokoban_fields_still_needed == 0 &&
14959 game.sokoban_objects_still_needed == 0 &&
14960 level.auto_exit_sokoban)
14962 game.players_still_needed = 0;
14966 PlaySound(SND_GAME_SOKOBAN_SOLVING);
14970 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14972 InitMovingField(x, y, move_direction);
14973 GfxAction[x][y] = ACTION_PUSHING;
14975 if (mode == DF_SNAP)
14976 ContinueMoving(x, y);
14978 MovPos[x][y] = (dx != 0 ? dx : dy);
14980 Pushed[x][y] = TRUE;
14981 Pushed[nextx][nexty] = TRUE;
14983 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14984 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14986 player->push_delay_value = -1; // get new value later
14988 // check for element change _after_ element has been pushed
14989 if (game.use_change_when_pushing_bug)
14991 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14992 player->index_bit, dig_side);
14993 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14994 player->index_bit, dig_side);
14997 else if (IS_SWITCHABLE(element))
14999 if (PLAYER_SWITCHING(player, x, y))
15001 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15002 player->index_bit, dig_side);
15007 player->is_switching = TRUE;
15008 player->switch_x = x;
15009 player->switch_y = y;
15011 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15013 if (element == EL_ROBOT_WHEEL)
15015 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
15017 game.robot_wheel_x = x;
15018 game.robot_wheel_y = y;
15019 game.robot_wheel_active = TRUE;
15021 TEST_DrawLevelField(x, y);
15023 else if (element == EL_SP_TERMINAL)
15027 SCAN_PLAYFIELD(xx, yy)
15029 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
15033 else if (Tile[xx][yy] == EL_SP_TERMINAL)
15035 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
15037 ResetGfxAnimation(xx, yy);
15038 TEST_DrawLevelField(xx, yy);
15042 else if (IS_BELT_SWITCH(element))
15044 ToggleBeltSwitch(x, y);
15046 else if (element == EL_SWITCHGATE_SWITCH_UP ||
15047 element == EL_SWITCHGATE_SWITCH_DOWN ||
15048 element == EL_DC_SWITCHGATE_SWITCH_UP ||
15049 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
15051 ToggleSwitchgateSwitch();
15053 else if (element == EL_LIGHT_SWITCH ||
15054 element == EL_LIGHT_SWITCH_ACTIVE)
15056 ToggleLightSwitch(x, y);
15058 else if (element == EL_TIMEGATE_SWITCH ||
15059 element == EL_DC_TIMEGATE_SWITCH)
15061 ActivateTimegateSwitch(x, y);
15063 else if (element == EL_BALLOON_SWITCH_LEFT ||
15064 element == EL_BALLOON_SWITCH_RIGHT ||
15065 element == EL_BALLOON_SWITCH_UP ||
15066 element == EL_BALLOON_SWITCH_DOWN ||
15067 element == EL_BALLOON_SWITCH_NONE ||
15068 element == EL_BALLOON_SWITCH_ANY)
15070 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
15071 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
15072 element == EL_BALLOON_SWITCH_UP ? MV_UP :
15073 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
15074 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
15077 else if (element == EL_LAMP)
15079 Tile[x][y] = EL_LAMP_ACTIVE;
15080 game.lights_still_needed--;
15082 ResetGfxAnimation(x, y);
15083 TEST_DrawLevelField(x, y);
15085 else if (element == EL_TIME_ORB_FULL)
15087 Tile[x][y] = EL_TIME_ORB_EMPTY;
15089 if (level.time > 0 || level.use_time_orb_bug)
15091 TimeLeft += level.time_orb_time;
15092 game.no_level_time_limit = FALSE;
15094 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
15096 DisplayGameControlValues();
15099 ResetGfxAnimation(x, y);
15100 TEST_DrawLevelField(x, y);
15102 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
15103 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15107 game.ball_active = !game.ball_active;
15109 SCAN_PLAYFIELD(xx, yy)
15111 int e = Tile[xx][yy];
15113 if (game.ball_active)
15115 if (e == EL_EMC_MAGIC_BALL)
15116 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
15117 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
15118 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
15122 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
15123 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
15124 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15125 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
15130 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15131 player->index_bit, dig_side);
15133 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15134 player->index_bit, dig_side);
15136 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15137 player->index_bit, dig_side);
15143 if (!PLAYER_SWITCHING(player, x, y))
15145 player->is_switching = TRUE;
15146 player->switch_x = x;
15147 player->switch_y = y;
15149 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
15150 player->index_bit, dig_side);
15151 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15152 player->index_bit, dig_side);
15154 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
15155 player->index_bit, dig_side);
15156 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15157 player->index_bit, dig_side);
15160 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
15161 player->index_bit, dig_side);
15162 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15163 player->index_bit, dig_side);
15165 return MP_NO_ACTION;
15168 player->push_delay = -1;
15170 if (is_player) // function can also be called by EL_PENGUIN
15172 if (Tile[x][y] != element) // really digged/collected something
15174 player->is_collecting = !player->is_digging;
15175 player->is_active = TRUE;
15177 player->last_removed_element = element;
15184 static boolean DigFieldByCE(int x, int y, int digging_element)
15186 int element = Tile[x][y];
15188 if (!IS_FREE(x, y))
15190 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
15191 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
15194 // no element can dig solid indestructible elements
15195 if (IS_INDESTRUCTIBLE(element) &&
15196 !IS_DIGGABLE(element) &&
15197 !IS_COLLECTIBLE(element))
15200 if (AmoebaNr[x][y] &&
15201 (element == EL_AMOEBA_FULL ||
15202 element == EL_BD_AMOEBA ||
15203 element == EL_AMOEBA_GROWING))
15205 AmoebaCnt[AmoebaNr[x][y]]--;
15206 AmoebaCnt2[AmoebaNr[x][y]]--;
15209 if (IS_MOVING(x, y))
15210 RemoveMovingField(x, y);
15214 TEST_DrawLevelField(x, y);
15217 // if digged element was about to explode, prevent the explosion
15218 ExplodeField[x][y] = EX_TYPE_NONE;
15220 PlayLevelSoundAction(x, y, action);
15223 Store[x][y] = EL_EMPTY;
15225 // this makes it possible to leave the removed element again
15226 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15227 Store[x][y] = element;
15232 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15234 int jx = player->jx, jy = player->jy;
15235 int x = jx + dx, y = jy + dy;
15236 int snap_direction = (dx == -1 ? MV_LEFT :
15237 dx == +1 ? MV_RIGHT :
15239 dy == +1 ? MV_DOWN : MV_NONE);
15240 boolean can_continue_snapping = (level.continuous_snapping &&
15241 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15243 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15246 if (!player->active || !IN_LEV_FIELD(x, y))
15254 if (player->MovPos == 0)
15255 player->is_pushing = FALSE;
15257 player->is_snapping = FALSE;
15259 if (player->MovPos == 0)
15261 player->is_moving = FALSE;
15262 player->is_digging = FALSE;
15263 player->is_collecting = FALSE;
15269 // prevent snapping with already pressed snap key when not allowed
15270 if (player->is_snapping && !can_continue_snapping)
15273 player->MovDir = snap_direction;
15275 if (player->MovPos == 0)
15277 player->is_moving = FALSE;
15278 player->is_digging = FALSE;
15279 player->is_collecting = FALSE;
15282 player->is_dropping = FALSE;
15283 player->is_dropping_pressed = FALSE;
15284 player->drop_pressed_delay = 0;
15286 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15289 player->is_snapping = TRUE;
15290 player->is_active = TRUE;
15292 if (player->MovPos == 0)
15294 player->is_moving = FALSE;
15295 player->is_digging = FALSE;
15296 player->is_collecting = FALSE;
15299 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15300 TEST_DrawLevelField(player->last_jx, player->last_jy);
15302 TEST_DrawLevelField(x, y);
15307 static boolean DropElement(struct PlayerInfo *player)
15309 int old_element, new_element;
15310 int dropx = player->jx, dropy = player->jy;
15311 int drop_direction = player->MovDir;
15312 int drop_side = drop_direction;
15313 int drop_element = get_next_dropped_element(player);
15315 /* do not drop an element on top of another element; when holding drop key
15316 pressed without moving, dropped element must move away before the next
15317 element can be dropped (this is especially important if the next element
15318 is dynamite, which can be placed on background for historical reasons) */
15319 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15322 if (IS_THROWABLE(drop_element))
15324 dropx += GET_DX_FROM_DIR(drop_direction);
15325 dropy += GET_DY_FROM_DIR(drop_direction);
15327 if (!IN_LEV_FIELD(dropx, dropy))
15331 old_element = Tile[dropx][dropy]; // old element at dropping position
15332 new_element = drop_element; // default: no change when dropping
15334 // check if player is active, not moving and ready to drop
15335 if (!player->active || player->MovPos || player->drop_delay > 0)
15338 // check if player has anything that can be dropped
15339 if (new_element == EL_UNDEFINED)
15342 // only set if player has anything that can be dropped
15343 player->is_dropping_pressed = TRUE;
15345 // check if drop key was pressed long enough for EM style dynamite
15346 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15349 // check if anything can be dropped at the current position
15350 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15353 // collected custom elements can only be dropped on empty fields
15354 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15357 if (old_element != EL_EMPTY)
15358 Back[dropx][dropy] = old_element; // store old element on this field
15360 ResetGfxAnimation(dropx, dropy);
15361 ResetRandomAnimationValue(dropx, dropy);
15363 if (player->inventory_size > 0 ||
15364 player->inventory_infinite_element != EL_UNDEFINED)
15366 if (player->inventory_size > 0)
15368 player->inventory_size--;
15370 DrawGameDoorValues();
15372 if (new_element == EL_DYNAMITE)
15373 new_element = EL_DYNAMITE_ACTIVE;
15374 else if (new_element == EL_EM_DYNAMITE)
15375 new_element = EL_EM_DYNAMITE_ACTIVE;
15376 else if (new_element == EL_SP_DISK_RED)
15377 new_element = EL_SP_DISK_RED_ACTIVE;
15380 Tile[dropx][dropy] = new_element;
15382 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15383 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15384 el2img(Tile[dropx][dropy]), 0);
15386 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15388 // needed if previous element just changed to "empty" in the last frame
15389 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15391 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15392 player->index_bit, drop_side);
15393 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15395 player->index_bit, drop_side);
15397 TestIfElementTouchesCustomElement(dropx, dropy);
15399 else // player is dropping a dyna bomb
15401 player->dynabombs_left--;
15403 Tile[dropx][dropy] = new_element;
15405 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15406 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15407 el2img(Tile[dropx][dropy]), 0);
15409 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15412 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15413 InitField_WithBug1(dropx, dropy, FALSE);
15415 new_element = Tile[dropx][dropy]; // element might have changed
15417 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15418 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15420 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15421 MovDir[dropx][dropy] = drop_direction;
15423 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15425 // do not cause impact style collision by dropping elements that can fall
15426 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15429 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15430 player->is_dropping = TRUE;
15432 player->drop_pressed_delay = 0;
15433 player->is_dropping_pressed = FALSE;
15435 player->drop_x = dropx;
15436 player->drop_y = dropy;
15441 // ----------------------------------------------------------------------------
15442 // game sound playing functions
15443 // ----------------------------------------------------------------------------
15445 static int *loop_sound_frame = NULL;
15446 static int *loop_sound_volume = NULL;
15448 void InitPlayLevelSound(void)
15450 int num_sounds = getSoundListSize();
15452 checked_free(loop_sound_frame);
15453 checked_free(loop_sound_volume);
15455 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15456 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15459 static void PlayLevelSoundExt(int x, int y, int nr, boolean is_loop_sound)
15461 int sx = SCREENX(x), sy = SCREENY(y);
15462 int volume, stereo_position;
15463 int max_distance = 8;
15464 int type = (is_loop_sound ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15466 if ((!setup.sound_simple && !is_loop_sound) ||
15467 (!setup.sound_loops && is_loop_sound))
15470 if (!IN_LEV_FIELD(x, y) ||
15471 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15472 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15475 volume = SOUND_MAX_VOLUME;
15477 if (!IN_SCR_FIELD(sx, sy))
15479 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15480 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15482 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15485 stereo_position = (SOUND_MAX_LEFT +
15486 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15487 (SCR_FIELDX + 2 * max_distance));
15491 /* This assures that quieter loop sounds do not overwrite louder ones,
15492 while restarting sound volume comparison with each new game frame. */
15494 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15497 loop_sound_volume[nr] = volume;
15498 loop_sound_frame[nr] = FrameCounter;
15501 PlaySoundExt(nr, volume, stereo_position, type);
15504 static void PlayLevelSound(int x, int y, int nr)
15506 PlayLevelSoundExt(x, y, nr, IS_LOOP_SOUND(nr));
15509 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15511 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15512 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15513 y < LEVELY(BY1) ? LEVELY(BY1) :
15514 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15518 static void PlayLevelSoundAction(int x, int y, int action)
15520 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15523 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15525 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15527 if (sound_effect != SND_UNDEFINED)
15528 PlayLevelSound(x, y, sound_effect);
15531 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15534 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15536 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15537 PlayLevelSound(x, y, sound_effect);
15540 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15542 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15544 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15545 PlayLevelSound(x, y, sound_effect);
15548 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15550 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15552 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15553 StopSound(sound_effect);
15556 static int getLevelMusicNr(void)
15558 int level_pos = level_nr - leveldir_current->first_level;
15560 if (levelset.music[level_nr] != MUS_UNDEFINED)
15561 return levelset.music[level_nr]; // from config file
15563 return MAP_NOCONF_MUSIC(level_pos); // from music dir
15566 static void FadeLevelSounds(void)
15571 static void FadeLevelMusic(void)
15573 int music_nr = getLevelMusicNr();
15574 char *curr_music = getCurrentlyPlayingMusicFilename();
15575 char *next_music = getMusicInfoEntryFilename(music_nr);
15577 if (!strEqual(curr_music, next_music))
15581 void FadeLevelSoundsAndMusic(void)
15587 static void PlayLevelMusic(void)
15589 int music_nr = getLevelMusicNr();
15590 char *curr_music = getCurrentlyPlayingMusicFilename();
15591 char *next_music = getMusicInfoEntryFilename(music_nr);
15593 if (!strEqual(curr_music, next_music))
15594 PlayMusicLoop(music_nr);
15597 static int getSoundAction_BD(int sample)
15601 case GD_S_STONE_PUSHING:
15602 case GD_S_MEGA_STONE_PUSHING:
15603 case GD_S_FLYING_STONE_PUSHING:
15604 case GD_S_WAITING_STONE_PUSHING:
15605 case GD_S_CHASING_STONE_PUSHING:
15606 case GD_S_NUT_PUSHING:
15607 case GD_S_NITRO_PACK_PUSHING:
15608 case GD_S_BLADDER_PUSHING:
15609 case GD_S_BOX_PUSHING:
15610 return ACTION_PUSHING;
15612 case GD_S_STONE_FALLING:
15613 case GD_S_MEGA_STONE_FALLING:
15614 case GD_S_FLYING_STONE_FALLING:
15615 case GD_S_NUT_FALLING:
15616 case GD_S_DIRT_BALL_FALLING:
15617 case GD_S_DIRT_LOOSE_FALLING:
15618 case GD_S_NITRO_PACK_FALLING:
15619 case GD_S_FALLING_WALL_FALLING:
15620 return ACTION_FALLING;
15622 case GD_S_STONE_IMPACT:
15623 case GD_S_MEGA_STONE_IMPACT:
15624 case GD_S_FLYING_STONE_IMPACT:
15625 case GD_S_NUT_IMPACT:
15626 case GD_S_DIRT_BALL_IMPACT:
15627 case GD_S_DIRT_LOOSE_IMPACT:
15628 case GD_S_NITRO_PACK_IMPACT:
15629 case GD_S_FALLING_WALL_IMPACT:
15630 return ACTION_IMPACT;
15632 case GD_S_NUT_CRACKING:
15633 return ACTION_BREAKING;
15635 case GD_S_EXPANDING_WALL:
15636 case GD_S_WALL_REAPPEARING:
15639 case GD_S_ACID_SPREADING:
15640 return ACTION_GROWING;
15642 case GD_S_DIAMOND_COLLECTING:
15643 case GD_S_FLYING_DIAMOND_COLLECTING:
15644 case GD_S_SKELETON_COLLECTING:
15645 case GD_S_PNEUMATIC_COLLECTING:
15646 case GD_S_BOMB_COLLECTING:
15647 case GD_S_CLOCK_COLLECTING:
15648 case GD_S_SWEET_COLLECTING:
15649 case GD_S_KEY_COLLECTING:
15650 case GD_S_DIAMOND_KEY_COLLECTING:
15651 return ACTION_COLLECTING;
15653 case GD_S_BOMB_PLACING:
15654 case GD_S_REPLICATOR:
15655 return ACTION_DROPPING;
15657 case GD_S_BLADDER_MOVING:
15658 return ACTION_MOVING;
15660 case GD_S_BLADDER_SPENDER:
15661 case GD_S_BLADDER_CONVERTING:
15662 case GD_S_GRAVITY_CHANGING:
15663 return ACTION_CHANGING;
15665 case GD_S_BITER_EATING:
15666 return ACTION_EATING;
15668 case GD_S_DOOR_OPENING:
15669 case GD_S_CRACKING:
15670 return ACTION_OPENING;
15672 case GD_S_DIRT_WALKING:
15673 return ACTION_DIGGING;
15675 case GD_S_EMPTY_WALKING:
15676 return ACTION_WALKING;
15678 case GD_S_SWITCH_BITER:
15679 case GD_S_SWITCH_CREATURES:
15680 case GD_S_SWITCH_GRAVITY:
15681 case GD_S_SWITCH_EXPANDING:
15682 case GD_S_SWITCH_CONVEYOR:
15683 case GD_S_SWITCH_REPLICATOR:
15684 case GD_S_STIRRING:
15685 return ACTION_ACTIVATING;
15687 case GD_S_TELEPORTER:
15688 return ACTION_PASSING;
15690 case GD_S_EXPLODING:
15691 case GD_S_BOMB_EXPLODING:
15692 case GD_S_GHOST_EXPLODING:
15693 case GD_S_VOODOO_EXPLODING:
15694 case GD_S_NITRO_PACK_EXPLODING:
15695 return ACTION_EXPLODING;
15697 case GD_S_COVERING:
15699 case GD_S_MAGIC_WALL:
15700 case GD_S_PNEUMATIC_HAMMER:
15702 return ACTION_ACTIVE;
15704 case GD_S_DIAMOND_FALLING_RANDOM:
15705 case GD_S_DIAMOND_FALLING_1:
15706 case GD_S_DIAMOND_FALLING_2:
15707 case GD_S_DIAMOND_FALLING_3:
15708 case GD_S_DIAMOND_FALLING_4:
15709 case GD_S_DIAMOND_FALLING_5:
15710 case GD_S_DIAMOND_FALLING_6:
15711 case GD_S_DIAMOND_FALLING_7:
15712 case GD_S_DIAMOND_FALLING_8:
15713 case GD_S_DIAMOND_IMPACT_RANDOM:
15714 case GD_S_DIAMOND_IMPACT_1:
15715 case GD_S_DIAMOND_IMPACT_2:
15716 case GD_S_DIAMOND_IMPACT_3:
15717 case GD_S_DIAMOND_IMPACT_4:
15718 case GD_S_DIAMOND_IMPACT_5:
15719 case GD_S_DIAMOND_IMPACT_6:
15720 case GD_S_DIAMOND_IMPACT_7:
15721 case GD_S_DIAMOND_IMPACT_8:
15722 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15723 case GD_S_FLYING_DIAMOND_FALLING_1:
15724 case GD_S_FLYING_DIAMOND_FALLING_2:
15725 case GD_S_FLYING_DIAMOND_FALLING_3:
15726 case GD_S_FLYING_DIAMOND_FALLING_4:
15727 case GD_S_FLYING_DIAMOND_FALLING_5:
15728 case GD_S_FLYING_DIAMOND_FALLING_6:
15729 case GD_S_FLYING_DIAMOND_FALLING_7:
15730 case GD_S_FLYING_DIAMOND_FALLING_8:
15731 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15732 case GD_S_FLYING_DIAMOND_IMPACT_1:
15733 case GD_S_FLYING_DIAMOND_IMPACT_2:
15734 case GD_S_FLYING_DIAMOND_IMPACT_3:
15735 case GD_S_FLYING_DIAMOND_IMPACT_4:
15736 case GD_S_FLYING_DIAMOND_IMPACT_5:
15737 case GD_S_FLYING_DIAMOND_IMPACT_6:
15738 case GD_S_FLYING_DIAMOND_IMPACT_7:
15739 case GD_S_FLYING_DIAMOND_IMPACT_8:
15740 case GD_S_TIMEOUT_0:
15741 case GD_S_TIMEOUT_1:
15742 case GD_S_TIMEOUT_2:
15743 case GD_S_TIMEOUT_3:
15744 case GD_S_TIMEOUT_4:
15745 case GD_S_TIMEOUT_5:
15746 case GD_S_TIMEOUT_6:
15747 case GD_S_TIMEOUT_7:
15748 case GD_S_TIMEOUT_8:
15749 case GD_S_TIMEOUT_9:
15750 case GD_S_TIMEOUT_10:
15751 case GD_S_BONUS_LIFE:
15752 // trigger special post-processing (and force sound to be non-looping)
15753 return ACTION_OTHER;
15755 case GD_S_AMOEBA_MAGIC:
15756 case GD_S_FINISHED:
15757 // trigger special post-processing (and force sound to be looping)
15758 return ACTION_DEFAULT;
15761 return ACTION_DEFAULT;
15765 static int getSoundEffect_BD(int element_bd, int sample)
15767 int sound_action = getSoundAction_BD(sample);
15768 int sound_effect = element_info[SND_ELEMENT(element_bd)].sound[sound_action];
15772 if (sound_action != ACTION_OTHER &&
15773 sound_action != ACTION_DEFAULT)
15774 return sound_effect;
15776 // special post-processing for some sounds
15779 case GD_S_DIAMOND_FALLING_RANDOM:
15780 case GD_S_DIAMOND_FALLING_1:
15781 case GD_S_DIAMOND_FALLING_2:
15782 case GD_S_DIAMOND_FALLING_3:
15783 case GD_S_DIAMOND_FALLING_4:
15784 case GD_S_DIAMOND_FALLING_5:
15785 case GD_S_DIAMOND_FALLING_6:
15786 case GD_S_DIAMOND_FALLING_7:
15787 case GD_S_DIAMOND_FALLING_8:
15788 nr = (sample == GD_S_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15789 sample - GD_S_DIAMOND_FALLING_1);
15790 sound_effect = SND_BDX_DIAMOND_FALLING_RANDOM_1 + nr;
15792 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15793 sound_effect = SND_BDX_DIAMOND_FALLING;
15796 case GD_S_DIAMOND_IMPACT_RANDOM:
15797 case GD_S_DIAMOND_IMPACT_1:
15798 case GD_S_DIAMOND_IMPACT_2:
15799 case GD_S_DIAMOND_IMPACT_3:
15800 case GD_S_DIAMOND_IMPACT_4:
15801 case GD_S_DIAMOND_IMPACT_5:
15802 case GD_S_DIAMOND_IMPACT_6:
15803 case GD_S_DIAMOND_IMPACT_7:
15804 case GD_S_DIAMOND_IMPACT_8:
15805 nr = (sample == GD_S_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15806 sample - GD_S_DIAMOND_IMPACT_1);
15807 sound_effect = SND_BDX_DIAMOND_IMPACT_RANDOM_1 + nr;
15809 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15810 sound_effect = SND_BDX_DIAMOND_IMPACT;
15813 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15814 case GD_S_FLYING_DIAMOND_FALLING_1:
15815 case GD_S_FLYING_DIAMOND_FALLING_2:
15816 case GD_S_FLYING_DIAMOND_FALLING_3:
15817 case GD_S_FLYING_DIAMOND_FALLING_4:
15818 case GD_S_FLYING_DIAMOND_FALLING_5:
15819 case GD_S_FLYING_DIAMOND_FALLING_6:
15820 case GD_S_FLYING_DIAMOND_FALLING_7:
15821 case GD_S_FLYING_DIAMOND_FALLING_8:
15822 nr = (sample == GD_S_FLYING_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15823 sample - GD_S_FLYING_DIAMOND_FALLING_1);
15824 sound_effect = SND_BDX_FLYING_DIAMOND_FALLING_RANDOM_1 + nr;
15826 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15827 sound_effect = SND_BDX_FLYING_DIAMOND_FALLING;
15830 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15831 case GD_S_FLYING_DIAMOND_IMPACT_1:
15832 case GD_S_FLYING_DIAMOND_IMPACT_2:
15833 case GD_S_FLYING_DIAMOND_IMPACT_3:
15834 case GD_S_FLYING_DIAMOND_IMPACT_4:
15835 case GD_S_FLYING_DIAMOND_IMPACT_5:
15836 case GD_S_FLYING_DIAMOND_IMPACT_6:
15837 case GD_S_FLYING_DIAMOND_IMPACT_7:
15838 case GD_S_FLYING_DIAMOND_IMPACT_8:
15839 nr = (sample == GD_S_FLYING_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15840 sample - GD_S_FLYING_DIAMOND_IMPACT_1);
15841 sound_effect = SND_BDX_FLYING_DIAMOND_IMPACT_RANDOM_1 + nr;
15843 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15844 sound_effect = SND_BDX_FLYING_DIAMOND_IMPACT;
15847 case GD_S_TIMEOUT_0:
15848 case GD_S_TIMEOUT_1:
15849 case GD_S_TIMEOUT_2:
15850 case GD_S_TIMEOUT_3:
15851 case GD_S_TIMEOUT_4:
15852 case GD_S_TIMEOUT_5:
15853 case GD_S_TIMEOUT_6:
15854 case GD_S_TIMEOUT_7:
15855 case GD_S_TIMEOUT_8:
15856 case GD_S_TIMEOUT_9:
15857 case GD_S_TIMEOUT_10:
15858 nr = sample - GD_S_TIMEOUT_0;
15859 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME_0 + nr;
15861 if (getSoundInfoEntryFilename(sound_effect) == NULL && sample != GD_S_TIMEOUT_0)
15862 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME;
15865 case GD_S_BONUS_LIFE:
15866 sound_effect = SND_GAME_HEALTH_BONUS;
15869 case GD_S_AMOEBA_MAGIC:
15870 sound_effect = SND_BDX_AMOEBA_1_OTHER;
15873 case GD_S_FINISHED:
15874 sound_effect = SND_GAME_LEVELTIME_BONUS;
15878 sound_effect = SND_UNDEFINED;
15882 return sound_effect;
15885 void PlayLevelSound_BD(int xx, int yy, int element_bd, int sample)
15887 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15888 int sound_effect = getSoundEffect_BD(element, sample);
15889 int sound_action = getSoundAction_BD(sample);
15890 boolean is_loop_sound = IS_LOOP_SOUND(sound_effect);
15892 int x = xx - offset;
15893 int y = yy - offset;
15895 // some sound actions are always looping in native BD game engine
15896 if (sound_action == ACTION_DEFAULT)
15897 is_loop_sound = TRUE;
15899 // some sound actions are always non-looping in native BD game engine
15900 if (sound_action == ACTION_FALLING ||
15901 sound_action == ACTION_MOVING ||
15902 sound_action == ACTION_OTHER)
15903 is_loop_sound = FALSE;
15905 if (sound_effect != SND_UNDEFINED)
15906 PlayLevelSoundExt(x, y, sound_effect, is_loop_sound);
15909 void StopSound_BD(int element_bd, int sample)
15911 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15912 int sound_effect = getSoundEffect_BD(element, sample);
15914 if (sound_effect != SND_UNDEFINED)
15915 StopSound(sound_effect);
15918 boolean isSoundPlaying_BD(int element_bd, int sample)
15920 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15921 int sound_effect = getSoundEffect_BD(element, sample);
15923 if (sound_effect != SND_UNDEFINED)
15924 return isSoundPlaying(sound_effect);
15929 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15931 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15933 int x = xx - offset;
15934 int y = yy - offset;
15939 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15943 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15947 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15951 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15955 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15959 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15963 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15966 case SOUND_android_clone:
15967 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15970 case SOUND_android_move:
15971 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15975 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15979 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15983 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15986 case SOUND_eater_eat:
15987 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15991 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15994 case SOUND_collect:
15995 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15998 case SOUND_diamond:
15999 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
16003 // !!! CHECK THIS !!!
16005 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
16007 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
16011 case SOUND_wonderfall:
16012 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
16016 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
16020 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
16024 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
16028 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
16032 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16036 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
16040 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16044 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16047 case SOUND_exit_open:
16048 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
16051 case SOUND_exit_leave:
16052 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16055 case SOUND_dynamite:
16056 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16060 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16064 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
16068 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16072 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
16076 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
16080 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
16084 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
16089 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
16091 int element = map_element_SP_to_RND(element_sp);
16092 int action = map_action_SP_to_RND(action_sp);
16093 int offset = (setup.sp_show_border_elements ? 0 : 1);
16094 int x = xx - offset;
16095 int y = yy - offset;
16097 PlayLevelSoundElementAction(x, y, element, action);
16100 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
16102 int element = map_element_MM_to_RND(element_mm);
16103 int action = map_action_MM_to_RND(action_mm);
16105 int x = xx - offset;
16106 int y = yy - offset;
16108 if (!IS_MM_ELEMENT(element))
16109 element = EL_MM_DEFAULT;
16111 PlayLevelSoundElementAction(x, y, element, action);
16114 void PlaySound_MM(int sound_mm)
16116 int sound = map_sound_MM_to_RND(sound_mm);
16118 if (sound == SND_UNDEFINED)
16124 void PlaySoundLoop_MM(int sound_mm)
16126 int sound = map_sound_MM_to_RND(sound_mm);
16128 if (sound == SND_UNDEFINED)
16131 PlaySoundLoop(sound);
16134 void StopSound_MM(int sound_mm)
16136 int sound = map_sound_MM_to_RND(sound_mm);
16138 if (sound == SND_UNDEFINED)
16144 void RaiseScore(int value)
16146 game.score += value;
16148 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
16150 DisplayGameControlValues();
16153 void RaiseScoreElement(int element)
16158 case EL_BD_DIAMOND:
16159 case EL_EMERALD_YELLOW:
16160 case EL_EMERALD_RED:
16161 case EL_EMERALD_PURPLE:
16162 case EL_SP_INFOTRON:
16163 RaiseScore(level.score[SC_EMERALD]);
16166 RaiseScore(level.score[SC_DIAMOND]);
16169 RaiseScore(level.score[SC_CRYSTAL]);
16172 RaiseScore(level.score[SC_PEARL]);
16175 case EL_BD_BUTTERFLY:
16176 case EL_SP_ELECTRON:
16177 RaiseScore(level.score[SC_BUG]);
16180 case EL_BD_FIREFLY:
16181 case EL_SP_SNIKSNAK:
16182 RaiseScore(level.score[SC_SPACESHIP]);
16185 case EL_DARK_YAMYAM:
16186 RaiseScore(level.score[SC_YAMYAM]);
16189 RaiseScore(level.score[SC_ROBOT]);
16192 RaiseScore(level.score[SC_PACMAN]);
16195 RaiseScore(level.score[SC_NUT]);
16198 case EL_EM_DYNAMITE:
16199 case EL_SP_DISK_RED:
16200 case EL_DYNABOMB_INCREASE_NUMBER:
16201 case EL_DYNABOMB_INCREASE_SIZE:
16202 case EL_DYNABOMB_INCREASE_POWER:
16203 RaiseScore(level.score[SC_DYNAMITE]);
16205 case EL_SHIELD_NORMAL:
16206 case EL_SHIELD_DEADLY:
16207 RaiseScore(level.score[SC_SHIELD]);
16209 case EL_EXTRA_TIME:
16210 RaiseScore(level.extra_time_score);
16224 case EL_DC_KEY_WHITE:
16225 RaiseScore(level.score[SC_KEY]);
16228 RaiseScore(element_info[element].collect_score);
16233 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
16235 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
16239 // prevent short reactivation of overlay buttons while closing door
16240 SetOverlayActive(FALSE);
16241 UnmapGameButtons();
16243 // door may still be open due to skipped or envelope style request
16244 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
16247 if (network.enabled)
16249 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
16253 // when using BD game engine, cover screen before fading out
16254 if (!quick_quit && level.game_engine_type == GAME_ENGINE_TYPE_BD)
16255 game_bd.cover_screen = TRUE;
16258 FadeSkipNextFadeIn();
16260 SetGameStatus(GAME_MODE_MAIN);
16265 else // continue playing the game
16267 if (tape.playing && tape.deactivate_display)
16268 TapeDeactivateDisplayOff(TRUE);
16270 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
16272 if (tape.playing && tape.deactivate_display)
16273 TapeDeactivateDisplayOn();
16277 void RequestQuitGame(boolean escape_key_pressed)
16279 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
16280 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
16281 level_editor_test_game);
16282 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
16283 quick_quit || score_info_tape_play);
16285 RequestQuitGameExt(skip_request, quick_quit,
16286 "Do you really want to quit the game?");
16289 static char *getRestartGameMessage(void)
16291 boolean play_again = hasStartedNetworkGame();
16292 static char message[MAX_OUTPUT_LINESIZE];
16293 char *game_over_text = "Game over!";
16294 char *play_again_text = " Play it again?";
16296 if (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
16297 game_mm.game_over_message != NULL)
16298 game_over_text = game_mm.game_over_message;
16300 snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text,
16301 (play_again ? play_again_text : ""));
16306 static void RequestRestartGame(void)
16308 char *message = getRestartGameMessage();
16309 boolean has_started_game = hasStartedNetworkGame();
16310 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
16311 int door_state = DOOR_CLOSE_1;
16313 boolean restart_wanted = (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game);
16315 // if no restart wanted, continue with next level for BD style intermission levels
16316 if (!restart_wanted && !level_editor_test_game && level.bd_intermission)
16318 boolean success = AdvanceToNextLevel();
16320 restart_wanted = (success && setup.auto_play_next_level);
16323 if (restart_wanted)
16325 CloseDoor(door_state);
16327 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16331 // if game was invoked from level editor, also close tape recorder door
16332 if (level_editor_test_game)
16333 door_state = DOOR_CLOSE_ALL;
16335 CloseDoor(door_state);
16337 SetGameStatus(GAME_MODE_MAIN);
16343 boolean CheckRestartGame(void)
16345 static int game_over_delay = 0;
16346 int game_over_delay_value = 50;
16347 boolean game_over = checkGameFailed();
16351 game_over_delay = game_over_delay_value;
16356 if (game_over_delay > 0)
16358 if (game_over_delay == game_over_delay_value / 2)
16359 PlaySound(SND_GAME_LOSING);
16366 // do not ask to play again if request dialog is already active
16367 if (checkRequestActive())
16370 // do not ask to play again if request dialog already handled
16371 if (game.RestartGameRequested)
16374 // do not ask to play again if game was never actually played
16375 if (!game.GamePlayed)
16378 // do not ask to play again if this was disabled in setup menu
16379 if (!setup.ask_on_game_over)
16382 game.RestartGameRequested = TRUE;
16384 RequestRestartGame();
16389 boolean checkGameRunning(void)
16391 if (game_status != GAME_MODE_PLAYING)
16394 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGameRunning_BD())
16400 boolean checkGamePlaying(void)
16402 if (game_status != GAME_MODE_PLAYING)
16405 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGamePlaying_BD())
16411 boolean checkGameSolved(void)
16413 // set for all game engines if level was solved
16414 return game.LevelSolved_GameEnd;
16417 boolean checkGameFailed(void)
16419 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
16420 return (game_bd.game_over && !game_bd.level_solved);
16421 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16422 return (game_em.game_over && !game_em.level_solved);
16423 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16424 return (game_sp.game_over && !game_sp.level_solved);
16425 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16426 return (game_mm.game_over && !game_mm.level_solved);
16427 else // GAME_ENGINE_TYPE_RND
16428 return (game.GameOver && !game.LevelSolved);
16431 boolean checkGameEnded(void)
16433 return (checkGameSolved() || checkGameFailed());
16436 boolean checkRequestActive(void)
16438 return (game.request_active || game.envelope_active || game.any_door_active);
16442 // ----------------------------------------------------------------------------
16443 // random generator functions
16444 // ----------------------------------------------------------------------------
16446 unsigned int InitEngineRandom_RND(int seed)
16448 game.num_random_calls = 0;
16450 return InitEngineRandom(seed);
16453 unsigned int RND(int max)
16457 game.num_random_calls++;
16459 return GetEngineRandom(max);
16466 // ----------------------------------------------------------------------------
16467 // game engine snapshot handling functions
16468 // ----------------------------------------------------------------------------
16470 struct EngineSnapshotInfo
16472 // runtime values for custom element collect score
16473 int collect_score[NUM_CUSTOM_ELEMENTS];
16475 // runtime values for group element choice position
16476 int choice_pos[NUM_GROUP_ELEMENTS];
16478 // runtime values for belt position animations
16479 int belt_graphic[4][NUM_BELT_PARTS];
16480 int belt_anim_mode[4][NUM_BELT_PARTS];
16483 static struct EngineSnapshotInfo engine_snapshot_rnd;
16484 static char *snapshot_level_identifier = NULL;
16485 static int snapshot_level_nr = -1;
16487 static void SaveEngineSnapshotValues_RND(void)
16489 static int belt_base_active_element[4] =
16491 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
16492 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
16493 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
16494 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
16498 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16500 int element = EL_CUSTOM_START + i;
16502 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
16505 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16507 int element = EL_GROUP_START + i;
16509 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
16512 for (i = 0; i < 4; i++)
16514 for (j = 0; j < NUM_BELT_PARTS; j++)
16516 int element = belt_base_active_element[i] + j;
16517 int graphic = el2img(element);
16518 int anim_mode = graphic_info[graphic].anim_mode;
16520 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
16521 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
16526 static void LoadEngineSnapshotValues_RND(void)
16528 unsigned int num_random_calls = game.num_random_calls;
16531 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16533 int element = EL_CUSTOM_START + i;
16535 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
16538 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16540 int element = EL_GROUP_START + i;
16542 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
16545 for (i = 0; i < 4; i++)
16547 for (j = 0; j < NUM_BELT_PARTS; j++)
16549 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
16550 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
16552 graphic_info[graphic].anim_mode = anim_mode;
16556 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16558 InitRND(tape.random_seed);
16559 for (i = 0; i < num_random_calls; i++)
16563 if (game.num_random_calls != num_random_calls)
16565 Error("number of random calls out of sync");
16566 Error("number of random calls should be %d", num_random_calls);
16567 Error("number of random calls is %d", game.num_random_calls);
16569 Fail("this should not happen -- please debug");
16573 void FreeEngineSnapshotSingle(void)
16575 FreeSnapshotSingle();
16577 setString(&snapshot_level_identifier, NULL);
16578 snapshot_level_nr = -1;
16581 void FreeEngineSnapshotList(void)
16583 FreeSnapshotList();
16586 static ListNode *SaveEngineSnapshotBuffers(void)
16588 ListNode *buffers = NULL;
16590 // copy some special values to a structure better suited for the snapshot
16592 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16593 SaveEngineSnapshotValues_RND();
16594 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16595 SaveEngineSnapshotValues_EM();
16596 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16597 SaveEngineSnapshotValues_SP(&buffers);
16598 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16599 SaveEngineSnapshotValues_MM();
16601 // save values stored in special snapshot structure
16603 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16604 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
16605 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16606 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
16607 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16608 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
16609 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16610 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
16612 // save further RND engine values
16614 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
16615 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
16616 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
16618 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
16619 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
16620 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
16621 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16622 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTimeFrames));
16623 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16625 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16626 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16627 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16629 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16631 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16632 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16634 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16635 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16636 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16637 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16638 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16639 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16640 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16641 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16642 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16643 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16644 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16645 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16646 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16647 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16648 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16649 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16650 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16651 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16653 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16654 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16656 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16657 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16658 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16660 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16661 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16663 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16664 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16665 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16666 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16667 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16668 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16670 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16671 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16674 ListNode *node = engine_snapshot_list_rnd;
16677 while (node != NULL)
16679 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16684 Debug("game:playing:SaveEngineSnapshotBuffers",
16685 "size of engine snapshot: %d bytes", num_bytes);
16691 void SaveEngineSnapshotSingle(void)
16693 ListNode *buffers = SaveEngineSnapshotBuffers();
16695 // finally save all snapshot buffers to single snapshot
16696 SaveSnapshotSingle(buffers);
16698 // save level identification information
16699 setString(&snapshot_level_identifier, leveldir_current->identifier);
16700 snapshot_level_nr = level_nr;
16703 boolean CheckSaveEngineSnapshotToList(void)
16705 boolean save_snapshot =
16706 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16707 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16708 game.snapshot.changed_action) ||
16709 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16710 game.snapshot.collected_item));
16712 game.snapshot.changed_action = FALSE;
16713 game.snapshot.collected_item = FALSE;
16714 game.snapshot.save_snapshot = save_snapshot;
16716 return save_snapshot;
16719 void SaveEngineSnapshotToList(void)
16721 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16725 ListNode *buffers = SaveEngineSnapshotBuffers();
16727 // finally save all snapshot buffers to snapshot list
16728 SaveSnapshotToList(buffers);
16731 void SaveEngineSnapshotToListInitial(void)
16733 FreeEngineSnapshotList();
16735 SaveEngineSnapshotToList();
16738 static void LoadEngineSnapshotValues(void)
16740 // restore special values from snapshot structure
16742 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16743 LoadEngineSnapshotValues_RND();
16744 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16745 LoadEngineSnapshotValues_EM();
16746 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16747 LoadEngineSnapshotValues_SP();
16748 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16749 LoadEngineSnapshotValues_MM();
16752 void LoadEngineSnapshotSingle(void)
16754 LoadSnapshotSingle();
16756 LoadEngineSnapshotValues();
16759 static void LoadEngineSnapshot_Undo(int steps)
16761 LoadSnapshotFromList_Older(steps);
16763 LoadEngineSnapshotValues();
16766 static void LoadEngineSnapshot_Redo(int steps)
16768 LoadSnapshotFromList_Newer(steps);
16770 LoadEngineSnapshotValues();
16773 boolean CheckEngineSnapshotSingle(void)
16775 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16776 snapshot_level_nr == level_nr);
16779 boolean CheckEngineSnapshotList(void)
16781 return CheckSnapshotList();
16785 // ---------- new game button stuff -------------------------------------------
16792 boolean *setup_value;
16793 boolean allowed_on_tape;
16794 boolean is_touch_button;
16796 } gamebutton_info[NUM_GAME_BUTTONS] =
16799 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16800 GAME_CTRL_ID_STOP, NULL,
16801 TRUE, FALSE, "stop game"
16804 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16805 GAME_CTRL_ID_PAUSE, NULL,
16806 TRUE, FALSE, "pause game"
16809 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16810 GAME_CTRL_ID_PLAY, NULL,
16811 TRUE, FALSE, "play game"
16814 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16815 GAME_CTRL_ID_UNDO, NULL,
16816 TRUE, FALSE, "undo step"
16819 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16820 GAME_CTRL_ID_REDO, NULL,
16821 TRUE, FALSE, "redo step"
16824 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16825 GAME_CTRL_ID_SAVE, NULL,
16826 TRUE, FALSE, "save game"
16829 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16830 GAME_CTRL_ID_PAUSE2, NULL,
16831 TRUE, FALSE, "pause game"
16834 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16835 GAME_CTRL_ID_LOAD, NULL,
16836 TRUE, FALSE, "load game"
16839 IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart,
16840 GAME_CTRL_ID_RESTART, NULL,
16841 TRUE, FALSE, "restart game"
16844 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16845 GAME_CTRL_ID_PANEL_STOP, NULL,
16846 FALSE, FALSE, "stop game"
16849 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16850 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16851 FALSE, FALSE, "pause game"
16854 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16855 GAME_CTRL_ID_PANEL_PLAY, NULL,
16856 FALSE, FALSE, "play game"
16859 IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart,
16860 GAME_CTRL_ID_PANEL_RESTART, NULL,
16861 FALSE, FALSE, "restart game"
16864 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16865 GAME_CTRL_ID_TOUCH_STOP, NULL,
16866 FALSE, TRUE, "stop game"
16869 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16870 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16871 FALSE, TRUE, "pause game"
16874 IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart,
16875 GAME_CTRL_ID_TOUCH_RESTART, NULL,
16876 FALSE, TRUE, "restart game"
16879 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16880 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16881 TRUE, FALSE, "background music on/off"
16884 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16885 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16886 TRUE, FALSE, "sound loops on/off"
16889 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16890 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16891 TRUE, FALSE, "normal sounds on/off"
16894 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16895 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16896 FALSE, FALSE, "background music on/off"
16899 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16900 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16901 FALSE, FALSE, "sound loops on/off"
16904 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16905 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16906 FALSE, FALSE, "normal sounds on/off"
16910 void CreateGameButtons(void)
16914 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16916 int graphic = gamebutton_info[i].graphic;
16917 struct GraphicInfo *gfx = &graphic_info[graphic];
16918 struct XY *pos = gamebutton_info[i].pos;
16919 struct GadgetInfo *gi;
16922 unsigned int event_mask;
16923 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16924 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16925 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16926 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16927 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16928 int gd_x = gfx->src_x;
16929 int gd_y = gfx->src_y;
16930 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16931 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16932 int gd_xa = gfx->src_x + gfx->active_xoffset;
16933 int gd_ya = gfx->src_y + gfx->active_yoffset;
16934 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16935 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16936 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16937 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16940 // do not use touch buttons if overlay touch buttons are disabled
16941 if (is_touch_button && !setup.touch.overlay_buttons)
16944 if (gfx->bitmap == NULL)
16946 game_gadget[id] = NULL;
16951 if (id == GAME_CTRL_ID_STOP ||
16952 id == GAME_CTRL_ID_PANEL_STOP ||
16953 id == GAME_CTRL_ID_TOUCH_STOP ||
16954 id == GAME_CTRL_ID_PLAY ||
16955 id == GAME_CTRL_ID_PANEL_PLAY ||
16956 id == GAME_CTRL_ID_SAVE ||
16957 id == GAME_CTRL_ID_LOAD ||
16958 id == GAME_CTRL_ID_RESTART ||
16959 id == GAME_CTRL_ID_PANEL_RESTART ||
16960 id == GAME_CTRL_ID_TOUCH_RESTART)
16962 button_type = GD_TYPE_NORMAL_BUTTON;
16964 event_mask = GD_EVENT_RELEASED;
16966 else if (id == GAME_CTRL_ID_UNDO ||
16967 id == GAME_CTRL_ID_REDO)
16969 button_type = GD_TYPE_NORMAL_BUTTON;
16971 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16975 button_type = GD_TYPE_CHECK_BUTTON;
16976 checked = (gamebutton_info[i].setup_value != NULL ?
16977 *gamebutton_info[i].setup_value : FALSE);
16978 event_mask = GD_EVENT_PRESSED;
16981 gi = CreateGadget(GDI_CUSTOM_ID, id,
16982 GDI_IMAGE_ID, graphic,
16983 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16986 GDI_WIDTH, gfx->width,
16987 GDI_HEIGHT, gfx->height,
16988 GDI_TYPE, button_type,
16989 GDI_STATE, GD_BUTTON_UNPRESSED,
16990 GDI_CHECKED, checked,
16991 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16992 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16993 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16994 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16995 GDI_DIRECT_DRAW, FALSE,
16996 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16997 GDI_EVENT_MASK, event_mask,
16998 GDI_CALLBACK_ACTION, HandleGameButtons,
17002 Fail("cannot create gadget");
17004 game_gadget[id] = gi;
17008 void FreeGameButtons(void)
17012 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17013 FreeGadget(game_gadget[i]);
17016 static void UnmapGameButtonsAtSamePosition(int id)
17020 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17022 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
17023 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
17024 UnmapGadget(game_gadget[i]);
17027 static void UnmapGameButtonsAtSamePosition_All(void)
17029 if (setup.show_load_save_buttons)
17031 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
17032 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
17033 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
17035 else if (setup.show_undo_redo_buttons)
17037 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
17038 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
17039 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
17043 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
17044 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
17045 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
17047 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
17048 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
17049 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
17053 void MapLoadSaveButtons(void)
17055 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
17056 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
17058 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
17059 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
17062 void MapUndoRedoButtons(void)
17064 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
17065 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
17067 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
17068 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
17071 void ModifyPauseButtons(void)
17075 GAME_CTRL_ID_PAUSE,
17076 GAME_CTRL_ID_PAUSE2,
17077 GAME_CTRL_ID_PANEL_PAUSE,
17078 GAME_CTRL_ID_TOUCH_PAUSE,
17083 // do not redraw pause button on closed door (may happen when restarting game)
17084 if (!(GetDoorState() & DOOR_OPEN_1))
17087 for (i = 0; ids[i] > -1; i++)
17088 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
17091 static void MapGameButtonsExt(boolean on_tape)
17095 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17097 if ((i == GAME_CTRL_ID_UNDO ||
17098 i == GAME_CTRL_ID_REDO) &&
17099 game_status != GAME_MODE_PLAYING)
17102 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17103 MapGadget(game_gadget[i]);
17106 UnmapGameButtonsAtSamePosition_All();
17108 RedrawGameButtons();
17111 static void UnmapGameButtonsExt(boolean on_tape)
17115 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17116 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17117 UnmapGadget(game_gadget[i]);
17120 static void RedrawGameButtonsExt(boolean on_tape)
17124 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17125 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17126 RedrawGadget(game_gadget[i]);
17129 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
17134 gi->checked = state;
17137 static void RedrawSoundButtonGadget(int id)
17139 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
17140 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
17141 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
17142 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
17143 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
17144 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
17147 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
17148 RedrawGadget(game_gadget[id2]);
17151 void MapGameButtons(void)
17153 MapGameButtonsExt(FALSE);
17156 void UnmapGameButtons(void)
17158 UnmapGameButtonsExt(FALSE);
17161 void RedrawGameButtons(void)
17163 RedrawGameButtonsExt(FALSE);
17166 void MapGameButtonsOnTape(void)
17168 MapGameButtonsExt(TRUE);
17171 void UnmapGameButtonsOnTape(void)
17173 UnmapGameButtonsExt(TRUE);
17176 void RedrawGameButtonsOnTape(void)
17178 RedrawGameButtonsExt(TRUE);
17181 static void GameUndoRedoExt(void)
17183 ClearPlayerAction();
17185 tape.pausing = TRUE;
17188 UpdateAndDisplayGameControlValues();
17190 DrawCompleteVideoDisplay();
17191 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
17192 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
17193 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
17195 ModifyPauseButtons();
17200 static void GameUndo(int steps)
17202 if (!CheckEngineSnapshotList())
17205 int tape_property_bits = tape.property_bits;
17207 LoadEngineSnapshot_Undo(steps);
17209 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17214 static void GameRedo(int steps)
17216 if (!CheckEngineSnapshotList())
17219 int tape_property_bits = tape.property_bits;
17221 LoadEngineSnapshot_Redo(steps);
17223 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17228 static void HandleGameButtonsExt(int id, int button)
17230 static boolean game_undo_executed = FALSE;
17231 int steps = BUTTON_STEPSIZE(button);
17232 boolean handle_game_buttons =
17233 (game_status == GAME_MODE_PLAYING ||
17234 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
17236 if (!handle_game_buttons)
17241 case GAME_CTRL_ID_STOP:
17242 case GAME_CTRL_ID_PANEL_STOP:
17243 case GAME_CTRL_ID_TOUCH_STOP:
17248 case GAME_CTRL_ID_PAUSE:
17249 case GAME_CTRL_ID_PAUSE2:
17250 case GAME_CTRL_ID_PANEL_PAUSE:
17251 case GAME_CTRL_ID_TOUCH_PAUSE:
17252 if (network.enabled && game_status == GAME_MODE_PLAYING)
17255 SendToServer_ContinuePlaying();
17257 SendToServer_PausePlaying();
17260 TapeTogglePause(TAPE_TOGGLE_MANUAL);
17262 game_undo_executed = FALSE;
17266 case GAME_CTRL_ID_PLAY:
17267 case GAME_CTRL_ID_PANEL_PLAY:
17268 if (game_status == GAME_MODE_MAIN)
17270 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
17272 else if (tape.pausing)
17274 if (network.enabled)
17275 SendToServer_ContinuePlaying();
17277 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
17281 case GAME_CTRL_ID_UNDO:
17282 // Important: When using "save snapshot when collecting an item" mode,
17283 // load last (current) snapshot for first "undo" after pressing "pause"
17284 // (else the last-but-one snapshot would be loaded, because the snapshot
17285 // pointer already points to the last snapshot when pressing "pause",
17286 // which is fine for "every step/move" mode, but not for "every collect")
17287 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
17288 !game_undo_executed)
17291 game_undo_executed = TRUE;
17296 case GAME_CTRL_ID_REDO:
17300 case GAME_CTRL_ID_SAVE:
17304 case GAME_CTRL_ID_LOAD:
17308 case GAME_CTRL_ID_RESTART:
17309 case GAME_CTRL_ID_PANEL_RESTART:
17310 case GAME_CTRL_ID_TOUCH_RESTART:
17315 case SOUND_CTRL_ID_MUSIC:
17316 case SOUND_CTRL_ID_PANEL_MUSIC:
17317 if (setup.sound_music)
17319 setup.sound_music = FALSE;
17323 else if (audio.music_available)
17325 setup.sound = setup.sound_music = TRUE;
17327 SetAudioMode(setup.sound);
17329 if (game_status == GAME_MODE_PLAYING)
17333 RedrawSoundButtonGadget(id);
17337 case SOUND_CTRL_ID_LOOPS:
17338 case SOUND_CTRL_ID_PANEL_LOOPS:
17339 if (setup.sound_loops)
17340 setup.sound_loops = FALSE;
17341 else if (audio.loops_available)
17343 setup.sound = setup.sound_loops = TRUE;
17345 SetAudioMode(setup.sound);
17348 RedrawSoundButtonGadget(id);
17352 case SOUND_CTRL_ID_SIMPLE:
17353 case SOUND_CTRL_ID_PANEL_SIMPLE:
17354 if (setup.sound_simple)
17355 setup.sound_simple = FALSE;
17356 else if (audio.sound_available)
17358 setup.sound = setup.sound_simple = TRUE;
17360 SetAudioMode(setup.sound);
17363 RedrawSoundButtonGadget(id);
17372 static void HandleGameButtons(struct GadgetInfo *gi)
17374 HandleGameButtonsExt(gi->custom_id, gi->event.button);
17377 void HandleSoundButtonKeys(Key key)
17379 if (key == setup.shortcut.sound_simple)
17380 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
17381 else if (key == setup.shortcut.sound_loops)
17382 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
17383 else if (key == setup.shortcut.sound_music)
17384 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);