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.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.frames_played :
4868 level.use_step_counter ? TimePlayed :
4869 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4871 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.score :
4872 level.game_engine_type == GAME_ENGINE_TYPE_EM ? game_em.lev->score :
4873 level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.score :
4876 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4877 MM_HEALTH(game_mm.laser_overload_value) :
4880 game.LevelSolved_CountingTime = game.time_final;
4881 game.LevelSolved_CountingScore = game.score_final;
4882 game.LevelSolved_CountingHealth = game.health_final;
4885 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4887 game.LevelSolved_CountingTime = time;
4888 game.LevelSolved_CountingScore = score;
4889 game.LevelSolved_CountingHealth = health;
4891 game_panel_controls[GAME_PANEL_TIME].value = time;
4892 game_panel_controls[GAME_PANEL_SCORE].value = score;
4893 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4895 DisplayGameControlValues();
4898 static void LevelSolved(void)
4900 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4901 game.players_still_needed > 0)
4904 game.LevelSolved = TRUE;
4905 game.GameOver = TRUE;
4909 // needed here to display correct panel values while player walks into exit
4910 LevelSolved_SetFinalGameValues();
4913 static boolean AdvanceToNextLevel(void)
4915 if (setup.increment_levels &&
4916 level_nr < leveldir_current->last_level &&
4919 level_nr++; // advance to next level
4920 TapeErase(); // start with empty tape
4922 if (setup.auto_play_next_level)
4924 scores.continue_playing = TRUE;
4925 scores.next_level_nr = level_nr;
4927 LoadLevel(level_nr);
4929 SaveLevelSetup_SeriesInfo();
4940 static int time_count_steps;
4941 static int time, time_final;
4942 static float score, score_final; // needed for time score < 10 for 10 seconds
4943 static int health, health_final;
4944 static int game_over_delay_1 = 0;
4945 static int game_over_delay_2 = 0;
4946 static int game_over_delay_3 = 0;
4947 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4948 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4950 if (!game.LevelSolved_GameWon)
4954 // do not start end game actions before the player stops moving (to exit)
4955 if (local_player->active && local_player->MovPos)
4958 // calculate final game values after player finished walking into exit
4959 LevelSolved_SetFinalGameValues();
4961 game.LevelSolved_GameWon = TRUE;
4962 game.LevelSolved_SaveTape = tape.recording;
4963 game.LevelSolved_SaveScore = !tape.playing;
4967 LevelStats_incSolved(level_nr);
4969 SaveLevelSetup_SeriesInfo();
4972 if (tape.auto_play) // tape might already be stopped here
4973 tape.auto_play_level_solved = TRUE;
4977 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4978 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4979 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4981 time = time_final = game.time_final;
4982 score = score_final = game.score_final;
4983 health = health_final = game.health_final;
4985 // update game panel values before (delayed) counting of score (if any)
4986 LevelSolved_DisplayFinalGameValues(time, score, health);
4988 // if level has time score defined, calculate new final game values
4991 int time_final_max = 999;
4992 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4993 int time_frames = 0;
4994 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4995 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
5000 time_frames = time_frames_left;
5002 else if (game.no_level_time_limit && TimePlayed < time_final_max)
5004 time_final = time_final_max;
5005 time_frames = time_frames_final_max - time_frames_played;
5008 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
5010 time_count_steps = MAX(1, ABS(time_final - time) / 100);
5012 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
5014 // keep previous values (final values already processed here)
5016 score_final = score;
5018 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
5021 score_final += health * time_score;
5024 game.score_final = score_final;
5025 game.health_final = health_final;
5028 // if not counting score after game, immediately update game panel values
5029 if (level_editor_test_game || !setup.count_score_after_game ||
5030 level.game_engine_type == GAME_ENGINE_TYPE_BD)
5033 score = score_final;
5035 LevelSolved_DisplayFinalGameValues(time, score, health);
5038 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
5040 // check if last player has left the level
5041 if (game.exit_x >= 0 &&
5044 int x = game.exit_x;
5045 int y = game.exit_y;
5046 int element = Tile[x][y];
5048 // close exit door after last player
5049 if ((game.all_players_gone &&
5050 (element == EL_EXIT_OPEN ||
5051 element == EL_SP_EXIT_OPEN ||
5052 element == EL_STEEL_EXIT_OPEN)) ||
5053 element == EL_EM_EXIT_OPEN ||
5054 element == EL_EM_STEEL_EXIT_OPEN)
5058 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
5059 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
5060 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
5061 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
5062 EL_EM_STEEL_EXIT_CLOSING);
5064 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
5067 // player disappears
5068 DrawLevelField(x, y);
5071 for (i = 0; i < MAX_PLAYERS; i++)
5073 struct PlayerInfo *player = &stored_player[i];
5075 if (player->present)
5077 RemovePlayer(player);
5079 // player disappears
5080 DrawLevelField(player->jx, player->jy);
5085 PlaySound(SND_GAME_WINNING);
5088 if (setup.count_score_after_game)
5090 if (time != time_final)
5092 if (game_over_delay_1 > 0)
5094 game_over_delay_1--;
5099 int time_to_go = ABS(time_final - time);
5100 int time_count_dir = (time < time_final ? +1 : -1);
5102 if (time_to_go < time_count_steps)
5103 time_count_steps = 1;
5105 time += time_count_steps * time_count_dir;
5106 score += time_count_steps * time_score;
5108 // set final score to correct rounding differences after counting score
5109 if (time == time_final)
5110 score = score_final;
5112 LevelSolved_DisplayFinalGameValues(time, score, health);
5114 if (time == time_final)
5115 StopSound(SND_GAME_LEVELTIME_BONUS);
5116 else if (setup.sound_loops)
5117 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5119 PlaySound(SND_GAME_LEVELTIME_BONUS);
5124 if (health != health_final)
5126 if (game_over_delay_2 > 0)
5128 game_over_delay_2--;
5133 int health_count_dir = (health < health_final ? +1 : -1);
5135 health += health_count_dir;
5136 score += time_score;
5138 LevelSolved_DisplayFinalGameValues(time, score, health);
5140 if (health == health_final)
5141 StopSound(SND_GAME_LEVELTIME_BONUS);
5142 else if (setup.sound_loops)
5143 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5145 PlaySound(SND_GAME_LEVELTIME_BONUS);
5151 game.panel.active = FALSE;
5153 if (game_over_delay_3 > 0)
5155 game_over_delay_3--;
5165 // used instead of "level_nr" (needed for network games)
5166 int last_level_nr = levelset.level_nr;
5167 boolean tape_saved = FALSE;
5168 boolean game_over = checkGameFailed();
5170 // Important note: This function is not only called after "GameWon()", but also after
5171 // "game over" (if automatically asking for restarting the game is disabled in setup)
5173 // do not handle game end if game over and automatically asking for game restart
5174 if (game_over && setup.ask_on_game_over)
5176 // (this is a special case: player pressed "return" key or fire button shortly before
5177 // automatically asking to restart the game, so skip asking and restart right away)
5179 CloseDoor(DOOR_CLOSE_1);
5181 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5186 // do not handle game end if request dialog is already active
5187 if (checkRequestActive())
5190 if (game.LevelSolved)
5191 game.LevelSolved_GameEnd = TRUE;
5193 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5195 // make sure that request dialog to save tape does not open door again
5196 if (!global.use_envelope_request)
5197 CloseDoor(DOOR_CLOSE_1);
5200 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5202 // set unique basename for score tape (also saved in high score table)
5203 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5206 // if no tape is to be saved, close both doors simultaneously
5207 CloseDoor(DOOR_CLOSE_ALL);
5209 if (level_editor_test_game || score_info_tape_play)
5211 SetGameStatus(GAME_MODE_MAIN);
5218 if (!game.GamePlayed || (!game.LevelSolved_SaveScore && !level.bd_intermission))
5220 SetGameStatus(GAME_MODE_MAIN);
5227 if (level_nr == leveldir_current->handicap_level)
5229 leveldir_current->handicap_level++;
5231 SaveLevelSetup_SeriesInfo();
5234 // save score and score tape before potentially erasing tape below
5235 if (game.LevelSolved_SaveScore)
5236 NewHighScore(last_level_nr, tape_saved);
5238 // increment and load next level (if possible and not configured otherwise)
5239 AdvanceToNextLevel();
5241 if (game.LevelSolved_SaveScore && scores.last_added >= 0 && setup.show_scores_after_game)
5243 SetGameStatus(GAME_MODE_SCORES);
5245 DrawHallOfFame(last_level_nr);
5247 else if (scores.continue_playing)
5249 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5253 SetGameStatus(GAME_MODE_MAIN);
5259 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5260 boolean one_score_entry_per_name)
5264 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5267 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5269 struct ScoreEntry *entry = &list->entry[i];
5270 boolean score_is_better = (new_entry->score > entry->score);
5271 boolean score_is_equal = (new_entry->score == entry->score);
5272 boolean time_is_better = (new_entry->time < entry->time);
5273 boolean time_is_equal = (new_entry->time == entry->time);
5274 boolean better_by_score = (score_is_better ||
5275 (score_is_equal && time_is_better));
5276 boolean better_by_time = (time_is_better ||
5277 (time_is_equal && score_is_better));
5278 boolean is_better = (level.rate_time_over_score ? better_by_time :
5280 boolean entry_is_empty = (entry->score == 0 &&
5283 // prevent adding server score entries if also existing in local score file
5284 // (special case: historic score entries have an empty tape basename entry)
5285 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5286 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5288 // add fields from server score entry not stored in local score entry
5289 // (currently, this means setting platform, version and country fields;
5290 // in rare cases, this may also correct an invalid score value, as
5291 // historic scores might have been truncated to 16-bit values locally)
5292 *entry = *new_entry;
5297 if (is_better || entry_is_empty)
5299 // player has made it to the hall of fame
5301 if (i < MAX_SCORE_ENTRIES - 1)
5303 int m = MAX_SCORE_ENTRIES - 1;
5306 if (one_score_entry_per_name)
5308 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5309 if (strEqual(list->entry[l].name, new_entry->name))
5312 if (m == i) // player's new highscore overwrites his old one
5316 for (l = m; l > i; l--)
5317 list->entry[l] = list->entry[l - 1];
5322 *entry = *new_entry;
5326 else if (one_score_entry_per_name &&
5327 strEqual(entry->name, new_entry->name))
5329 // player already in high score list with better score or time
5335 // special case: new score is beyond the last high score list position
5336 return MAX_SCORE_ENTRIES;
5339 void NewHighScore(int level_nr, boolean tape_saved)
5341 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5342 boolean one_per_name = FALSE;
5344 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5345 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5347 new_entry.score = game.score_final;
5348 new_entry.time = game.score_time_final;
5350 LoadScore(level_nr);
5352 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5354 if (scores.last_added >= MAX_SCORE_ENTRIES)
5356 scores.last_added = MAX_SCORE_ENTRIES - 1;
5357 scores.force_last_added = TRUE;
5359 scores.entry[scores.last_added] = new_entry;
5361 // store last added local score entry (before merging server scores)
5362 scores.last_added_local = scores.last_added;
5367 if (scores.last_added < 0)
5370 SaveScore(level_nr);
5372 // store last added local score entry (before merging server scores)
5373 scores.last_added_local = scores.last_added;
5375 if (!game.LevelSolved_SaveTape)
5378 SaveScoreTape(level_nr);
5380 if (setup.ask_for_using_api_server)
5382 setup.use_api_server =
5383 Request("Upload your score and tape to the high score server?", REQ_ASK);
5385 if (!setup.use_api_server)
5386 Request("Not using high score server! Use setup menu to enable again!",
5389 runtime.use_api_server = setup.use_api_server;
5391 // after asking for using API server once, do not ask again
5392 setup.ask_for_using_api_server = FALSE;
5394 SaveSetup_ServerSetup();
5397 SaveServerScore(level_nr, tape_saved);
5400 void MergeServerScore(void)
5402 struct ScoreEntry last_added_entry;
5403 boolean one_per_name = FALSE;
5406 if (scores.last_added >= 0)
5407 last_added_entry = scores.entry[scores.last_added];
5409 for (i = 0; i < server_scores.num_entries; i++)
5411 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5413 if (pos >= 0 && pos <= scores.last_added)
5414 scores.last_added++;
5417 if (scores.last_added >= MAX_SCORE_ENTRIES)
5419 scores.last_added = MAX_SCORE_ENTRIES - 1;
5420 scores.force_last_added = TRUE;
5422 scores.entry[scores.last_added] = last_added_entry;
5426 static int getElementMoveStepsizeExt(int x, int y, int direction)
5428 int element = Tile[x][y];
5429 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5430 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5431 int horiz_move = (dx != 0);
5432 int sign = (horiz_move ? dx : dy);
5433 int step = sign * element_info[element].move_stepsize;
5435 // special values for move stepsize for spring and things on conveyor belt
5438 if (CAN_FALL(element) &&
5439 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5440 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5441 else if (element == EL_SPRING)
5442 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5448 static int getElementMoveStepsize(int x, int y)
5450 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5453 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5455 if (player->GfxAction != action || player->GfxDir != dir)
5457 player->GfxAction = action;
5458 player->GfxDir = dir;
5460 player->StepFrame = 0;
5464 static void ResetGfxFrame(int x, int y)
5466 // profiling showed that "autotest" spends 10~20% of its time in this function
5467 if (DrawingDeactivatedField())
5470 int element = Tile[x][y];
5471 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5473 if (graphic_info[graphic].anim_global_sync)
5474 GfxFrame[x][y] = FrameCounter;
5475 else if (graphic_info[graphic].anim_global_anim_sync)
5476 GfxFrame[x][y] = getGlobalAnimSyncFrame();
5477 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5478 GfxFrame[x][y] = CustomValue[x][y];
5479 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5480 GfxFrame[x][y] = element_info[element].collect_score;
5481 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5482 GfxFrame[x][y] = ChangeDelay[x][y];
5485 static void ResetGfxAnimation(int x, int y)
5487 GfxAction[x][y] = ACTION_DEFAULT;
5488 GfxDir[x][y] = MovDir[x][y];
5491 ResetGfxFrame(x, y);
5494 static void ResetRandomAnimationValue(int x, int y)
5496 GfxRandom[x][y] = INIT_GFX_RANDOM();
5499 static void InitMovingField(int x, int y, int direction)
5501 int element = Tile[x][y];
5502 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5503 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5506 boolean is_moving_before, is_moving_after;
5508 // check if element was/is moving or being moved before/after mode change
5509 is_moving_before = (WasJustMoving[x][y] != 0);
5510 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5512 // reset animation only for moving elements which change direction of moving
5513 // or which just started or stopped moving
5514 // (else CEs with property "can move" / "not moving" are reset each frame)
5515 if (is_moving_before != is_moving_after ||
5516 direction != MovDir[x][y])
5517 ResetGfxAnimation(x, y);
5519 MovDir[x][y] = direction;
5520 GfxDir[x][y] = direction;
5522 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5523 direction == MV_DOWN && CAN_FALL(element) ?
5524 ACTION_FALLING : ACTION_MOVING);
5526 // this is needed for CEs with property "can move" / "not moving"
5528 if (is_moving_after)
5530 if (Tile[newx][newy] == EL_EMPTY)
5531 Tile[newx][newy] = EL_BLOCKED;
5533 MovDir[newx][newy] = MovDir[x][y];
5535 CustomValue[newx][newy] = CustomValue[x][y];
5537 GfxFrame[newx][newy] = GfxFrame[x][y];
5538 GfxRandom[newx][newy] = GfxRandom[x][y];
5539 GfxAction[newx][newy] = GfxAction[x][y];
5540 GfxDir[newx][newy] = GfxDir[x][y];
5544 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5546 int direction = MovDir[x][y];
5547 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5548 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5554 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5556 int direction = MovDir[x][y];
5557 int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0);
5558 int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0);
5560 *comes_from_x = oldx;
5561 *comes_from_y = oldy;
5564 static int MovingOrBlocked2Element(int x, int y)
5566 int element = Tile[x][y];
5568 if (element == EL_BLOCKED)
5572 Blocked2Moving(x, y, &oldx, &oldy);
5574 return Tile[oldx][oldy];
5580 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5582 // like MovingOrBlocked2Element(), but if element is moving
5583 // and (x, y) is the field the moving element is just leaving,
5584 // return EL_BLOCKED instead of the element value
5585 int element = Tile[x][y];
5587 if (IS_MOVING(x, y))
5589 if (element == EL_BLOCKED)
5593 Blocked2Moving(x, y, &oldx, &oldy);
5594 return Tile[oldx][oldy];
5603 static void RemoveField(int x, int y)
5605 Tile[x][y] = EL_EMPTY;
5611 CustomValue[x][y] = 0;
5614 ChangeDelay[x][y] = 0;
5615 ChangePage[x][y] = -1;
5616 Pushed[x][y] = FALSE;
5618 GfxElement[x][y] = EL_UNDEFINED;
5619 GfxAction[x][y] = ACTION_DEFAULT;
5620 GfxDir[x][y] = MV_NONE;
5623 static void RemoveMovingField(int x, int y)
5625 int oldx = x, oldy = y, newx = x, newy = y;
5626 int element = Tile[x][y];
5627 int next_element = EL_UNDEFINED;
5629 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5632 if (IS_MOVING(x, y))
5634 Moving2Blocked(x, y, &newx, &newy);
5636 if (Tile[newx][newy] != EL_BLOCKED)
5638 // element is moving, but target field is not free (blocked), but
5639 // already occupied by something different (example: acid pool);
5640 // in this case, only remove the moving field, but not the target
5642 RemoveField(oldx, oldy);
5644 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5646 TEST_DrawLevelField(oldx, oldy);
5651 else if (element == EL_BLOCKED)
5653 Blocked2Moving(x, y, &oldx, &oldy);
5654 if (!IS_MOVING(oldx, oldy))
5658 if (element == EL_BLOCKED &&
5659 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5660 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5661 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5662 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5663 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5664 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5665 next_element = get_next_element(Tile[oldx][oldy]);
5667 RemoveField(oldx, oldy);
5668 RemoveField(newx, newy);
5670 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5672 if (next_element != EL_UNDEFINED)
5673 Tile[oldx][oldy] = next_element;
5675 TEST_DrawLevelField(oldx, oldy);
5676 TEST_DrawLevelField(newx, newy);
5679 void DrawDynamite(int x, int y)
5681 int sx = SCREENX(x), sy = SCREENY(y);
5682 int graphic = el2img(Tile[x][y]);
5685 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5688 if (IS_WALKABLE_INSIDE(Back[x][y]))
5692 DrawLevelElement(x, y, Back[x][y]);
5693 else if (Store[x][y])
5694 DrawLevelElement(x, y, Store[x][y]);
5695 else if (game.use_masked_elements)
5696 DrawLevelElement(x, y, EL_EMPTY);
5698 frame = getGraphicAnimationFrameXY(graphic, x, y);
5700 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5701 DrawGraphicThruMask(sx, sy, graphic, frame);
5703 DrawGraphic(sx, sy, graphic, frame);
5706 static void CheckDynamite(int x, int y)
5708 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5712 if (MovDelay[x][y] != 0)
5715 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5721 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5726 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5728 boolean num_checked_players = 0;
5731 for (i = 0; i < MAX_PLAYERS; i++)
5733 if (stored_player[i].active)
5735 int sx = stored_player[i].jx;
5736 int sy = stored_player[i].jy;
5738 if (num_checked_players == 0)
5745 *sx1 = MIN(*sx1, sx);
5746 *sy1 = MIN(*sy1, sy);
5747 *sx2 = MAX(*sx2, sx);
5748 *sy2 = MAX(*sy2, sy);
5751 num_checked_players++;
5756 static boolean checkIfAllPlayersFitToScreen_RND(void)
5758 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5760 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5762 return (sx2 - sx1 < SCR_FIELDX &&
5763 sy2 - sy1 < SCR_FIELDY);
5766 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5768 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5770 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5772 *sx = (sx1 + sx2) / 2;
5773 *sy = (sy1 + sy2) / 2;
5776 static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
5777 boolean center_screen, boolean quick_relocation)
5779 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5780 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5781 boolean no_delay = (tape.warp_forward);
5782 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5783 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5784 int new_scroll_x, new_scroll_y;
5786 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5788 // case 1: quick relocation inside visible screen (without scrolling)
5795 if (!level.shifted_relocation || center_screen)
5797 // relocation _with_ centering of screen
5799 new_scroll_x = SCROLL_POSITION_X(x);
5800 new_scroll_y = SCROLL_POSITION_Y(y);
5804 // relocation _without_ centering of screen
5806 // apply distance between old and new player position to scroll position
5807 int shifted_scroll_x = scroll_x + (x - old_x);
5808 int shifted_scroll_y = scroll_y + (y - old_y);
5810 // make sure that shifted scroll position does not scroll beyond screen
5811 new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
5812 new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
5814 // special case for teleporting from one end of the playfield to the other
5815 // (this kludge prevents the destination area to be shifted by half a tile
5816 // against the source destination for even screen width or screen height;
5817 // probably most useful when used with high "game.forced_scroll_delay_value"
5818 // in combination with "game.forced_scroll_x" and "game.forced_scroll_y")
5819 if (quick_relocation)
5821 if (EVEN(SCR_FIELDX))
5823 // relocate (teleport) between left and right border (half or full)
5824 if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1)
5825 new_scroll_x = SBX_Right;
5826 else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right)
5827 new_scroll_x = SBX_Right - 1;
5828 else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1)
5829 new_scroll_x = SBX_Left;
5830 else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left)
5831 new_scroll_x = SBX_Left + 1;
5834 if (EVEN(SCR_FIELDY))
5836 // relocate (teleport) between top and bottom border (half or full)
5837 if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1)
5838 new_scroll_y = SBY_Lower;
5839 else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower)
5840 new_scroll_y = SBY_Lower - 1;
5841 else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1)
5842 new_scroll_y = SBY_Upper;
5843 else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper)
5844 new_scroll_y = SBY_Upper + 1;
5849 if (quick_relocation)
5851 // case 2: quick relocation (redraw without visible scrolling)
5853 scroll_x = new_scroll_x;
5854 scroll_y = new_scroll_y;
5861 // case 3: visible relocation (with scrolling to new position)
5863 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5865 SetVideoFrameDelay(wait_delay_value);
5867 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5869 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5870 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5872 if (dx == 0 && dy == 0) // no scrolling needed at all
5878 // set values for horizontal/vertical screen scrolling (half tile size)
5879 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5880 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5881 int pos_x = dx * TILEX / 2;
5882 int pos_y = dy * TILEY / 2;
5883 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5884 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5886 ScrollLevel(dx, dy);
5889 // scroll in two steps of half tile size to make things smoother
5890 BlitScreenToBitmapExt_RND(window, fx, fy);
5892 // scroll second step to align at full tile size
5893 BlitScreenToBitmap(window);
5899 SetVideoFrameDelay(frame_delay_value_old);
5902 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5904 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5905 int player_nr = GET_PLAYER_NR(el_player);
5906 struct PlayerInfo *player = &stored_player[player_nr];
5907 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5908 boolean no_delay = (tape.warp_forward);
5909 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5910 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5911 int old_jx = player->jx;
5912 int old_jy = player->jy;
5913 int old_element = Tile[old_jx][old_jy];
5914 int element = Tile[jx][jy];
5915 boolean player_relocated = (old_jx != jx || old_jy != jy);
5917 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5918 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5919 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5920 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5921 int leave_side_horiz = move_dir_horiz;
5922 int leave_side_vert = move_dir_vert;
5923 int enter_side = enter_side_horiz | enter_side_vert;
5924 int leave_side = leave_side_horiz | leave_side_vert;
5926 if (player->buried) // do not reanimate dead player
5929 if (!player_relocated) // no need to relocate the player
5932 if (IS_PLAYER(jx, jy)) // player already placed at new position
5934 RemoveField(jx, jy); // temporarily remove newly placed player
5935 DrawLevelField(jx, jy);
5938 if (player->present)
5940 while (player->MovPos)
5942 ScrollPlayer(player, SCROLL_GO_ON);
5943 ScrollScreen(NULL, SCROLL_GO_ON);
5945 AdvanceFrameAndPlayerCounters(player->index_nr);
5949 BackToFront_WithFrameDelay(wait_delay_value);
5952 DrawPlayer(player); // needed here only to cleanup last field
5953 DrawLevelField(player->jx, player->jy); // remove player graphic
5955 player->is_moving = FALSE;
5958 if (IS_CUSTOM_ELEMENT(old_element))
5959 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5961 player->index_bit, leave_side);
5963 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5965 player->index_bit, leave_side);
5967 Tile[jx][jy] = el_player;
5968 InitPlayerField(jx, jy, el_player, TRUE);
5970 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5971 possible that the relocation target field did not contain a player element,
5972 but a walkable element, to which the new player was relocated -- in this
5973 case, restore that (already initialized!) element on the player field */
5974 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5976 Tile[jx][jy] = element; // restore previously existing element
5979 // only visually relocate centered player
5980 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy,
5981 FALSE, level.instant_relocation);
5983 TestIfPlayerTouchesBadThing(jx, jy);
5984 TestIfPlayerTouchesCustomElement(jx, jy);
5986 if (IS_CUSTOM_ELEMENT(element))
5987 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5988 player->index_bit, enter_side);
5990 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5991 player->index_bit, enter_side);
5993 if (player->is_switching)
5995 /* ensure that relocation while still switching an element does not cause
5996 a new element to be treated as also switched directly after relocation
5997 (this is important for teleporter switches that teleport the player to
5998 a place where another teleporter switch is in the same direction, which
5999 would then incorrectly be treated as immediately switched before the
6000 direction key that caused the switch was released) */
6002 player->switch_x += jx - old_jx;
6003 player->switch_y += jy - old_jy;
6007 static void Explode(int ex, int ey, int phase, int mode)
6013 if (game.explosions_delayed)
6015 ExplodeField[ex][ey] = mode;
6019 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
6021 int center_element = Tile[ex][ey];
6022 int ce_value = CustomValue[ex][ey];
6023 int ce_score = element_info[center_element].collect_score;
6024 int artwork_element, explosion_element; // set these values later
6026 // remove things displayed in background while burning dynamite
6027 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
6030 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
6032 // put moving element to center field (and let it explode there)
6033 center_element = MovingOrBlocked2Element(ex, ey);
6034 RemoveMovingField(ex, ey);
6035 Tile[ex][ey] = center_element;
6038 // now "center_element" is finally determined -- set related values now
6039 artwork_element = center_element; // for custom player artwork
6040 explosion_element = center_element; // for custom player artwork
6042 if (IS_PLAYER(ex, ey))
6044 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
6046 artwork_element = stored_player[player_nr].artwork_element;
6048 if (level.use_explosion_element[player_nr])
6050 explosion_element = level.explosion_element[player_nr];
6051 artwork_element = explosion_element;
6055 if (mode == EX_TYPE_NORMAL ||
6056 mode == EX_TYPE_CENTER ||
6057 mode == EX_TYPE_CROSS)
6058 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
6060 last_phase = element_info[explosion_element].explosion_delay + 1;
6062 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
6064 int xx = x - ex + 1;
6065 int yy = y - ey + 1;
6068 if (!IN_LEV_FIELD(x, y) ||
6069 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
6070 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
6073 element = Tile[x][y];
6075 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
6077 element = MovingOrBlocked2Element(x, y);
6079 if (!IS_EXPLOSION_PROOF(element))
6080 RemoveMovingField(x, y);
6083 // indestructible elements can only explode in center (but not flames)
6084 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
6085 mode == EX_TYPE_BORDER)) ||
6086 element == EL_FLAMES)
6089 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
6090 behaviour, for example when touching a yamyam that explodes to rocks
6091 with active deadly shield, a rock is created under the player !!! */
6092 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
6094 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
6095 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
6096 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
6098 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
6101 if (IS_ACTIVE_BOMB(element))
6103 // re-activate things under the bomb like gate or penguin
6104 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
6111 // save walkable background elements while explosion on same tile
6112 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
6113 (x != ex || y != ey || mode == EX_TYPE_BORDER))
6114 Back[x][y] = element;
6116 // ignite explodable elements reached by other explosion
6117 if (element == EL_EXPLOSION)
6118 element = Store2[x][y];
6120 if (AmoebaNr[x][y] &&
6121 (element == EL_AMOEBA_FULL ||
6122 element == EL_BD_AMOEBA ||
6123 element == EL_AMOEBA_GROWING))
6125 AmoebaCnt[AmoebaNr[x][y]]--;
6126 AmoebaCnt2[AmoebaNr[x][y]]--;
6131 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
6133 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
6135 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
6137 if (PLAYERINFO(ex, ey)->use_murphy)
6138 Store[x][y] = EL_EMPTY;
6141 // !!! check this case -- currently needed for rnd_rado_negundo_v,
6142 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
6143 else if (IS_PLAYER_ELEMENT(center_element))
6144 Store[x][y] = EL_EMPTY;
6145 else if (center_element == EL_YAMYAM)
6146 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
6147 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
6148 Store[x][y] = element_info[center_element].content.e[xx][yy];
6150 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
6151 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
6152 // otherwise) -- FIX THIS !!!
6153 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
6154 Store[x][y] = element_info[element].content.e[1][1];
6156 else if (!CAN_EXPLODE(element))
6157 Store[x][y] = element_info[element].content.e[1][1];
6160 Store[x][y] = EL_EMPTY;
6162 if (IS_CUSTOM_ELEMENT(center_element))
6163 Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value :
6164 Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score :
6165 Store[x][y] >= EL_PREV_CE_8 &&
6166 Store[x][y] <= EL_NEXT_CE_8 ?
6167 RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) :
6170 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
6171 center_element == EL_AMOEBA_TO_DIAMOND)
6172 Store2[x][y] = element;
6174 Tile[x][y] = EL_EXPLOSION;
6175 GfxElement[x][y] = artwork_element;
6177 ExplodePhase[x][y] = 1;
6178 ExplodeDelay[x][y] = last_phase;
6183 if (center_element == EL_YAMYAM)
6184 game.yamyam_content_nr =
6185 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
6197 GfxFrame[x][y] = 0; // restart explosion animation
6199 last_phase = ExplodeDelay[x][y];
6201 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
6203 // this can happen if the player leaves an explosion just in time
6204 if (GfxElement[x][y] == EL_UNDEFINED)
6205 GfxElement[x][y] = EL_EMPTY;
6207 border_element = Store2[x][y];
6208 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6209 border_element = StorePlayer[x][y];
6211 if (phase == element_info[border_element].ignition_delay ||
6212 phase == last_phase)
6214 boolean border_explosion = FALSE;
6216 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6217 !PLAYER_EXPLOSION_PROTECTED(x, y))
6219 KillPlayerUnlessExplosionProtected(x, y);
6220 border_explosion = TRUE;
6222 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6224 Tile[x][y] = Store2[x][y];
6227 border_explosion = TRUE;
6229 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6231 AmoebaToDiamond(x, y);
6233 border_explosion = TRUE;
6236 // if an element just explodes due to another explosion (chain-reaction),
6237 // do not immediately end the new explosion when it was the last frame of
6238 // the explosion (as it would be done in the following "if"-statement!)
6239 if (border_explosion && phase == last_phase)
6243 // this can happen if the player was just killed by an explosion
6244 if (GfxElement[x][y] == EL_UNDEFINED)
6245 GfxElement[x][y] = EL_EMPTY;
6247 if (phase == last_phase)
6251 element = Tile[x][y] = Store[x][y];
6252 Store[x][y] = Store2[x][y] = 0;
6253 GfxElement[x][y] = EL_UNDEFINED;
6255 // player can escape from explosions and might therefore be still alive
6256 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6257 element <= EL_PLAYER_IS_EXPLODING_4)
6259 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6260 int explosion_element = EL_PLAYER_1 + player_nr;
6261 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6262 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6264 if (level.use_explosion_element[player_nr])
6265 explosion_element = level.explosion_element[player_nr];
6267 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6268 element_info[explosion_element].content.e[xx][yy]);
6271 // restore probably existing indestructible background element
6272 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6273 element = Tile[x][y] = Back[x][y];
6276 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6277 GfxDir[x][y] = MV_NONE;
6278 ChangeDelay[x][y] = 0;
6279 ChangePage[x][y] = -1;
6281 CustomValue[x][y] = 0;
6283 InitField_WithBug2(x, y, FALSE);
6285 TEST_DrawLevelField(x, y);
6287 TestIfElementTouchesCustomElement(x, y);
6289 if (GFX_CRUMBLED(element))
6290 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6292 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6293 StorePlayer[x][y] = 0;
6295 if (IS_PLAYER_ELEMENT(element))
6296 RelocatePlayer(x, y, element);
6298 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6300 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6301 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6304 TEST_DrawLevelFieldCrumbled(x, y);
6306 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6308 DrawLevelElement(x, y, Back[x][y]);
6309 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6311 else if (IS_WALKABLE_UNDER(Back[x][y]))
6313 DrawLevelGraphic(x, y, graphic, frame);
6314 DrawLevelElementThruMask(x, y, Back[x][y]);
6316 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6317 DrawLevelGraphic(x, y, graphic, frame);
6321 static void DynaExplode(int ex, int ey)
6324 int dynabomb_element = Tile[ex][ey];
6325 int dynabomb_size = 1;
6326 boolean dynabomb_xl = FALSE;
6327 struct PlayerInfo *player;
6328 struct XY *xy = xy_topdown;
6330 if (IS_ACTIVE_BOMB(dynabomb_element))
6332 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6333 dynabomb_size = player->dynabomb_size;
6334 dynabomb_xl = player->dynabomb_xl;
6335 player->dynabombs_left++;
6338 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6340 for (i = 0; i < NUM_DIRECTIONS; i++)
6342 for (j = 1; j <= dynabomb_size; j++)
6344 int x = ex + j * xy[i].x;
6345 int y = ey + j * xy[i].y;
6348 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6351 element = Tile[x][y];
6353 // do not restart explosions of fields with active bombs
6354 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6357 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6359 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6360 !IS_DIGGABLE(element) && !dynabomb_xl)
6366 void Bang(int x, int y)
6368 int element = MovingOrBlocked2Element(x, y);
6369 int explosion_type = EX_TYPE_NORMAL;
6371 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6373 struct PlayerInfo *player = PLAYERINFO(x, y);
6375 element = Tile[x][y] = player->initial_element;
6377 if (level.use_explosion_element[player->index_nr])
6379 int explosion_element = level.explosion_element[player->index_nr];
6381 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6382 explosion_type = EX_TYPE_CROSS;
6383 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6384 explosion_type = EX_TYPE_CENTER;
6392 case EL_BD_BUTTERFLY:
6395 case EL_DARK_YAMYAM:
6399 RaiseScoreElement(element);
6402 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6403 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6404 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6405 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6406 case EL_DYNABOMB_INCREASE_NUMBER:
6407 case EL_DYNABOMB_INCREASE_SIZE:
6408 case EL_DYNABOMB_INCREASE_POWER:
6409 explosion_type = EX_TYPE_DYNA;
6412 case EL_DC_LANDMINE:
6413 explosion_type = EX_TYPE_CENTER;
6418 case EL_LAMP_ACTIVE:
6419 case EL_AMOEBA_TO_DIAMOND:
6420 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6421 explosion_type = EX_TYPE_CENTER;
6425 if (element_info[element].explosion_type == EXPLODES_CROSS)
6426 explosion_type = EX_TYPE_CROSS;
6427 else if (element_info[element].explosion_type == EXPLODES_1X1)
6428 explosion_type = EX_TYPE_CENTER;
6432 if (explosion_type == EX_TYPE_DYNA)
6435 Explode(x, y, EX_PHASE_START, explosion_type);
6437 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6440 static void SplashAcid(int x, int y)
6442 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6443 (!IN_LEV_FIELD(x - 1, y - 2) ||
6444 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6445 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6447 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6448 (!IN_LEV_FIELD(x + 1, y - 2) ||
6449 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6450 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6452 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6455 static void InitBeltMovement(void)
6457 static int belt_base_element[4] =
6459 EL_CONVEYOR_BELT_1_LEFT,
6460 EL_CONVEYOR_BELT_2_LEFT,
6461 EL_CONVEYOR_BELT_3_LEFT,
6462 EL_CONVEYOR_BELT_4_LEFT
6464 static int belt_base_active_element[4] =
6466 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6467 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6468 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6469 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6474 // set frame order for belt animation graphic according to belt direction
6475 for (i = 0; i < NUM_BELTS; i++)
6479 for (j = 0; j < NUM_BELT_PARTS; j++)
6481 int element = belt_base_active_element[belt_nr] + j;
6482 int graphic_1 = el2img(element);
6483 int graphic_2 = el2panelimg(element);
6485 if (game.belt_dir[i] == MV_LEFT)
6487 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6488 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6492 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6493 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6498 SCAN_PLAYFIELD(x, y)
6500 int element = Tile[x][y];
6502 for (i = 0; i < NUM_BELTS; i++)
6504 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6506 int e_belt_nr = getBeltNrFromBeltElement(element);
6509 if (e_belt_nr == belt_nr)
6511 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6513 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6520 static void ToggleBeltSwitch(int x, int y)
6522 static int belt_base_element[4] =
6524 EL_CONVEYOR_BELT_1_LEFT,
6525 EL_CONVEYOR_BELT_2_LEFT,
6526 EL_CONVEYOR_BELT_3_LEFT,
6527 EL_CONVEYOR_BELT_4_LEFT
6529 static int belt_base_active_element[4] =
6531 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6532 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6533 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6534 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6536 static int belt_base_switch_element[4] =
6538 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6539 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6540 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6541 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6543 static int belt_move_dir[4] =
6551 int element = Tile[x][y];
6552 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6553 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6554 int belt_dir = belt_move_dir[belt_dir_nr];
6557 if (!IS_BELT_SWITCH(element))
6560 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6561 game.belt_dir[belt_nr] = belt_dir;
6563 if (belt_dir_nr == 3)
6566 // set frame order for belt animation graphic according to belt direction
6567 for (i = 0; i < NUM_BELT_PARTS; i++)
6569 int element = belt_base_active_element[belt_nr] + i;
6570 int graphic_1 = el2img(element);
6571 int graphic_2 = el2panelimg(element);
6573 if (belt_dir == MV_LEFT)
6575 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6576 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6580 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6581 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6585 SCAN_PLAYFIELD(xx, yy)
6587 int element = Tile[xx][yy];
6589 if (IS_BELT_SWITCH(element))
6591 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6593 if (e_belt_nr == belt_nr)
6595 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6596 TEST_DrawLevelField(xx, yy);
6599 else if (IS_BELT(element) && belt_dir != MV_NONE)
6601 int e_belt_nr = getBeltNrFromBeltElement(element);
6603 if (e_belt_nr == belt_nr)
6605 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6607 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6608 TEST_DrawLevelField(xx, yy);
6611 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6613 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6615 if (e_belt_nr == belt_nr)
6617 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6619 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6620 TEST_DrawLevelField(xx, yy);
6626 static void ToggleSwitchgateSwitch(void)
6630 game.switchgate_pos = !game.switchgate_pos;
6632 SCAN_PLAYFIELD(xx, yy)
6634 int element = Tile[xx][yy];
6636 if (element == EL_SWITCHGATE_SWITCH_UP)
6638 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6639 TEST_DrawLevelField(xx, yy);
6641 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6643 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6644 TEST_DrawLevelField(xx, yy);
6646 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6648 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6649 TEST_DrawLevelField(xx, yy);
6651 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6653 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6654 TEST_DrawLevelField(xx, yy);
6656 else if (element == EL_SWITCHGATE_OPEN ||
6657 element == EL_SWITCHGATE_OPENING)
6659 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6661 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6663 else if (element == EL_SWITCHGATE_CLOSED ||
6664 element == EL_SWITCHGATE_CLOSING)
6666 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6668 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6673 static int getInvisibleActiveFromInvisibleElement(int element)
6675 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6676 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6677 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6681 static int getInvisibleFromInvisibleActiveElement(int element)
6683 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6684 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6685 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6689 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6693 SCAN_PLAYFIELD(x, y)
6695 int element = Tile[x][y];
6697 if (element == EL_LIGHT_SWITCH &&
6698 game.light_time_left > 0)
6700 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6701 TEST_DrawLevelField(x, y);
6703 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6704 game.light_time_left == 0)
6706 Tile[x][y] = EL_LIGHT_SWITCH;
6707 TEST_DrawLevelField(x, y);
6709 else if (element == EL_EMC_DRIPPER &&
6710 game.light_time_left > 0)
6712 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6713 TEST_DrawLevelField(x, y);
6715 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6716 game.light_time_left == 0)
6718 Tile[x][y] = EL_EMC_DRIPPER;
6719 TEST_DrawLevelField(x, y);
6721 else if (element == EL_INVISIBLE_STEELWALL ||
6722 element == EL_INVISIBLE_WALL ||
6723 element == EL_INVISIBLE_SAND)
6725 if (game.light_time_left > 0)
6726 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6728 TEST_DrawLevelField(x, y);
6730 // uncrumble neighbour fields, if needed
6731 if (element == EL_INVISIBLE_SAND)
6732 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6734 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6735 element == EL_INVISIBLE_WALL_ACTIVE ||
6736 element == EL_INVISIBLE_SAND_ACTIVE)
6738 if (game.light_time_left == 0)
6739 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6741 TEST_DrawLevelField(x, y);
6743 // re-crumble neighbour fields, if needed
6744 if (element == EL_INVISIBLE_SAND)
6745 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6750 static void RedrawAllInvisibleElementsForLenses(void)
6754 SCAN_PLAYFIELD(x, y)
6756 int element = Tile[x][y];
6758 if (element == EL_EMC_DRIPPER &&
6759 game.lenses_time_left > 0)
6761 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6762 TEST_DrawLevelField(x, y);
6764 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6765 game.lenses_time_left == 0)
6767 Tile[x][y] = EL_EMC_DRIPPER;
6768 TEST_DrawLevelField(x, y);
6770 else if (element == EL_INVISIBLE_STEELWALL ||
6771 element == EL_INVISIBLE_WALL ||
6772 element == EL_INVISIBLE_SAND)
6774 if (game.lenses_time_left > 0)
6775 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6777 TEST_DrawLevelField(x, y);
6779 // uncrumble neighbour fields, if needed
6780 if (element == EL_INVISIBLE_SAND)
6781 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6783 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6784 element == EL_INVISIBLE_WALL_ACTIVE ||
6785 element == EL_INVISIBLE_SAND_ACTIVE)
6787 if (game.lenses_time_left == 0)
6788 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6790 TEST_DrawLevelField(x, y);
6792 // re-crumble neighbour fields, if needed
6793 if (element == EL_INVISIBLE_SAND)
6794 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6799 static void RedrawAllInvisibleElementsForMagnifier(void)
6803 SCAN_PLAYFIELD(x, y)
6805 int element = Tile[x][y];
6807 if (element == EL_EMC_FAKE_GRASS &&
6808 game.magnify_time_left > 0)
6810 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6811 TEST_DrawLevelField(x, y);
6813 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6814 game.magnify_time_left == 0)
6816 Tile[x][y] = EL_EMC_FAKE_GRASS;
6817 TEST_DrawLevelField(x, y);
6819 else if (IS_GATE_GRAY(element) &&
6820 game.magnify_time_left > 0)
6822 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6823 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6824 IS_EM_GATE_GRAY(element) ?
6825 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6826 IS_EMC_GATE_GRAY(element) ?
6827 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6828 IS_DC_GATE_GRAY(element) ?
6829 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6831 TEST_DrawLevelField(x, y);
6833 else if (IS_GATE_GRAY_ACTIVE(element) &&
6834 game.magnify_time_left == 0)
6836 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6837 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6838 IS_EM_GATE_GRAY_ACTIVE(element) ?
6839 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6840 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6841 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6842 IS_DC_GATE_GRAY_ACTIVE(element) ?
6843 EL_DC_GATE_WHITE_GRAY :
6845 TEST_DrawLevelField(x, y);
6850 static void ToggleLightSwitch(int x, int y)
6852 int element = Tile[x][y];
6854 game.light_time_left =
6855 (element == EL_LIGHT_SWITCH ?
6856 level.time_light * FRAMES_PER_SECOND : 0);
6858 RedrawAllLightSwitchesAndInvisibleElements();
6861 static void ActivateTimegateSwitch(int x, int y)
6865 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6867 SCAN_PLAYFIELD(xx, yy)
6869 int element = Tile[xx][yy];
6871 if (element == EL_TIMEGATE_CLOSED ||
6872 element == EL_TIMEGATE_CLOSING)
6874 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6875 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6879 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6881 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6882 TEST_DrawLevelField(xx, yy);
6888 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6889 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6892 static void Impact(int x, int y)
6894 boolean last_line = (y == lev_fieldy - 1);
6895 boolean object_hit = FALSE;
6896 boolean impact = (last_line || object_hit);
6897 int element = Tile[x][y];
6898 int smashed = EL_STEELWALL;
6900 if (!last_line) // check if element below was hit
6902 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6905 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6906 MovDir[x][y + 1] != MV_DOWN ||
6907 MovPos[x][y + 1] <= TILEY / 2));
6909 // do not smash moving elements that left the smashed field in time
6910 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6911 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6914 #if USE_QUICKSAND_IMPACT_BUGFIX
6915 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6917 RemoveMovingField(x, y + 1);
6918 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6919 Tile[x][y + 2] = EL_ROCK;
6920 TEST_DrawLevelField(x, y + 2);
6925 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6927 RemoveMovingField(x, y + 1);
6928 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6929 Tile[x][y + 2] = EL_ROCK;
6930 TEST_DrawLevelField(x, y + 2);
6937 smashed = MovingOrBlocked2Element(x, y + 1);
6939 impact = (last_line || object_hit);
6942 if (!last_line && smashed == EL_ACID) // element falls into acid
6944 SplashAcid(x, y + 1);
6948 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6949 // only reset graphic animation if graphic really changes after impact
6951 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6953 ResetGfxAnimation(x, y);
6954 TEST_DrawLevelField(x, y);
6957 if (impact && CAN_EXPLODE_IMPACT(element))
6962 else if (impact && element == EL_PEARL &&
6963 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6965 ResetGfxAnimation(x, y);
6967 Tile[x][y] = EL_PEARL_BREAKING;
6968 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6971 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6973 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6978 if (impact && element == EL_AMOEBA_DROP)
6980 if (object_hit && IS_PLAYER(x, y + 1))
6981 KillPlayerUnlessEnemyProtected(x, y + 1);
6982 else if (object_hit && smashed == EL_PENGUIN)
6986 Tile[x][y] = EL_AMOEBA_GROWING;
6987 Store[x][y] = EL_AMOEBA_WET;
6989 ResetRandomAnimationValue(x, y);
6994 if (object_hit) // check which object was hit
6996 if ((CAN_PASS_MAGIC_WALL(element) &&
6997 (smashed == EL_MAGIC_WALL ||
6998 smashed == EL_BD_MAGIC_WALL)) ||
6999 (CAN_PASS_DC_MAGIC_WALL(element) &&
7000 smashed == EL_DC_MAGIC_WALL))
7003 int activated_magic_wall =
7004 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
7005 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
7006 EL_DC_MAGIC_WALL_ACTIVE);
7008 // activate magic wall / mill
7009 SCAN_PLAYFIELD(xx, yy)
7011 if (Tile[xx][yy] == smashed)
7012 Tile[xx][yy] = activated_magic_wall;
7015 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
7016 game.magic_wall_active = TRUE;
7018 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
7019 SND_MAGIC_WALL_ACTIVATING :
7020 smashed == EL_BD_MAGIC_WALL ?
7021 SND_BD_MAGIC_WALL_ACTIVATING :
7022 SND_DC_MAGIC_WALL_ACTIVATING));
7025 if (IS_PLAYER(x, y + 1))
7027 if (CAN_SMASH_PLAYER(element))
7029 KillPlayerUnlessEnemyProtected(x, y + 1);
7033 else if (smashed == EL_PENGUIN)
7035 if (CAN_SMASH_PLAYER(element))
7041 else if (element == EL_BD_DIAMOND)
7043 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
7049 else if (((element == EL_SP_INFOTRON ||
7050 element == EL_SP_ZONK) &&
7051 (smashed == EL_SP_SNIKSNAK ||
7052 smashed == EL_SP_ELECTRON ||
7053 smashed == EL_SP_DISK_ORANGE)) ||
7054 (element == EL_SP_INFOTRON &&
7055 smashed == EL_SP_DISK_YELLOW))
7060 else if (CAN_SMASH_EVERYTHING(element))
7062 if (IS_CLASSIC_ENEMY(smashed) ||
7063 CAN_EXPLODE_SMASHED(smashed))
7068 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
7070 if (smashed == EL_LAMP ||
7071 smashed == EL_LAMP_ACTIVE)
7076 else if (smashed == EL_NUT)
7078 Tile[x][y + 1] = EL_NUT_BREAKING;
7079 PlayLevelSound(x, y, SND_NUT_BREAKING);
7080 RaiseScoreElement(EL_NUT);
7083 else if (smashed == EL_PEARL)
7085 ResetGfxAnimation(x, y);
7087 Tile[x][y + 1] = EL_PEARL_BREAKING;
7088 PlayLevelSound(x, y, SND_PEARL_BREAKING);
7091 else if (smashed == EL_DIAMOND)
7093 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
7094 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
7097 else if (IS_BELT_SWITCH(smashed))
7099 ToggleBeltSwitch(x, y + 1);
7101 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
7102 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
7103 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
7104 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
7106 ToggleSwitchgateSwitch();
7108 else if (smashed == EL_LIGHT_SWITCH ||
7109 smashed == EL_LIGHT_SWITCH_ACTIVE)
7111 ToggleLightSwitch(x, y + 1);
7115 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7117 CheckElementChangeBySide(x, y + 1, smashed, element,
7118 CE_SWITCHED, CH_SIDE_TOP);
7119 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
7125 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7130 // play sound of magic wall / mill
7132 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7133 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
7134 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
7136 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7137 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
7138 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7139 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
7140 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7141 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
7146 // play sound of object that hits the ground
7147 if (last_line || object_hit)
7148 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
7151 static void TurnRoundExt(int x, int y)
7163 { 0, 0 }, { 0, 0 }, { 0, 0 },
7168 int left, right, back;
7172 { MV_DOWN, MV_UP, MV_RIGHT },
7173 { MV_UP, MV_DOWN, MV_LEFT },
7175 { MV_LEFT, MV_RIGHT, MV_DOWN },
7179 { MV_RIGHT, MV_LEFT, MV_UP }
7182 int element = Tile[x][y];
7183 int move_pattern = element_info[element].move_pattern;
7185 int old_move_dir = MovDir[x][y];
7186 int left_dir = turn[old_move_dir].left;
7187 int right_dir = turn[old_move_dir].right;
7188 int back_dir = turn[old_move_dir].back;
7190 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
7191 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
7192 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
7193 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
7195 int left_x = x + left_dx, left_y = y + left_dy;
7196 int right_x = x + right_dx, right_y = y + right_dy;
7197 int move_x = x + move_dx, move_y = y + move_dy;
7201 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
7203 TestIfBadThingTouchesOtherBadThing(x, y);
7205 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
7206 MovDir[x][y] = right_dir;
7207 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7208 MovDir[x][y] = left_dir;
7210 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7212 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7215 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7217 TestIfBadThingTouchesOtherBadThing(x, y);
7219 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7220 MovDir[x][y] = left_dir;
7221 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7222 MovDir[x][y] = right_dir;
7224 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7226 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7229 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7231 TestIfBadThingTouchesOtherBadThing(x, y);
7233 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7234 MovDir[x][y] = left_dir;
7235 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7236 MovDir[x][y] = right_dir;
7238 if (MovDir[x][y] != old_move_dir)
7241 else if (element == EL_YAMYAM)
7243 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7244 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7246 if (can_turn_left && can_turn_right)
7247 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7248 else if (can_turn_left)
7249 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7250 else if (can_turn_right)
7251 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7253 MovDir[x][y] = back_dir;
7255 MovDelay[x][y] = 16 + 16 * RND(3);
7257 else if (element == EL_DARK_YAMYAM)
7259 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7261 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7264 if (can_turn_left && can_turn_right)
7265 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7266 else if (can_turn_left)
7267 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7268 else if (can_turn_right)
7269 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7271 MovDir[x][y] = back_dir;
7273 MovDelay[x][y] = 16 + 16 * RND(3);
7275 else if (element == EL_PACMAN)
7277 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7278 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7280 if (can_turn_left && can_turn_right)
7281 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7282 else if (can_turn_left)
7283 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7284 else if (can_turn_right)
7285 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7287 MovDir[x][y] = back_dir;
7289 MovDelay[x][y] = 6 + RND(40);
7291 else if (element == EL_PIG)
7293 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7294 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7295 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7296 boolean should_turn_left, should_turn_right, should_move_on;
7298 int rnd = RND(rnd_value);
7300 should_turn_left = (can_turn_left &&
7302 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7303 y + back_dy + left_dy)));
7304 should_turn_right = (can_turn_right &&
7306 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7307 y + back_dy + right_dy)));
7308 should_move_on = (can_move_on &&
7311 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7312 y + move_dy + left_dy) ||
7313 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7314 y + move_dy + right_dy)));
7316 if (should_turn_left || should_turn_right || should_move_on)
7318 if (should_turn_left && should_turn_right && should_move_on)
7319 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7320 rnd < 2 * rnd_value / 3 ? right_dir :
7322 else if (should_turn_left && should_turn_right)
7323 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7324 else if (should_turn_left && should_move_on)
7325 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7326 else if (should_turn_right && should_move_on)
7327 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7328 else if (should_turn_left)
7329 MovDir[x][y] = left_dir;
7330 else if (should_turn_right)
7331 MovDir[x][y] = right_dir;
7332 else if (should_move_on)
7333 MovDir[x][y] = old_move_dir;
7335 else if (can_move_on && rnd > rnd_value / 8)
7336 MovDir[x][y] = old_move_dir;
7337 else if (can_turn_left && can_turn_right)
7338 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7339 else if (can_turn_left && rnd > rnd_value / 8)
7340 MovDir[x][y] = left_dir;
7341 else if (can_turn_right && rnd > rnd_value/8)
7342 MovDir[x][y] = right_dir;
7344 MovDir[x][y] = back_dir;
7346 xx = x + move_xy[MovDir[x][y]].dx;
7347 yy = y + move_xy[MovDir[x][y]].dy;
7349 if (!IN_LEV_FIELD(xx, yy) ||
7350 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7351 MovDir[x][y] = old_move_dir;
7355 else if (element == EL_DRAGON)
7357 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7358 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7359 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7361 int rnd = RND(rnd_value);
7363 if (can_move_on && rnd > rnd_value / 8)
7364 MovDir[x][y] = old_move_dir;
7365 else if (can_turn_left && can_turn_right)
7366 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7367 else if (can_turn_left && rnd > rnd_value / 8)
7368 MovDir[x][y] = left_dir;
7369 else if (can_turn_right && rnd > rnd_value / 8)
7370 MovDir[x][y] = right_dir;
7372 MovDir[x][y] = back_dir;
7374 xx = x + move_xy[MovDir[x][y]].dx;
7375 yy = y + move_xy[MovDir[x][y]].dy;
7377 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7378 MovDir[x][y] = old_move_dir;
7382 else if (element == EL_MOLE)
7384 boolean can_move_on =
7385 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7386 IS_AMOEBOID(Tile[move_x][move_y]) ||
7387 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7390 boolean can_turn_left =
7391 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7392 IS_AMOEBOID(Tile[left_x][left_y])));
7394 boolean can_turn_right =
7395 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7396 IS_AMOEBOID(Tile[right_x][right_y])));
7398 if (can_turn_left && can_turn_right)
7399 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7400 else if (can_turn_left)
7401 MovDir[x][y] = left_dir;
7403 MovDir[x][y] = right_dir;
7406 if (MovDir[x][y] != old_move_dir)
7409 else if (element == EL_BALLOON)
7411 MovDir[x][y] = game.wind_direction;
7414 else if (element == EL_SPRING)
7416 if (MovDir[x][y] & MV_HORIZONTAL)
7418 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7419 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7421 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7422 ResetGfxAnimation(move_x, move_y);
7423 TEST_DrawLevelField(move_x, move_y);
7425 MovDir[x][y] = back_dir;
7427 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7428 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7429 MovDir[x][y] = MV_NONE;
7434 else if (element == EL_ROBOT ||
7435 element == EL_SATELLITE ||
7436 element == EL_PENGUIN ||
7437 element == EL_EMC_ANDROID)
7439 int attr_x = -1, attr_y = -1;
7441 if (game.all_players_gone)
7443 attr_x = game.exit_x;
7444 attr_y = game.exit_y;
7450 for (i = 0; i < MAX_PLAYERS; i++)
7452 struct PlayerInfo *player = &stored_player[i];
7453 int jx = player->jx, jy = player->jy;
7455 if (!player->active)
7459 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7467 if (element == EL_ROBOT &&
7468 game.robot_wheel_x >= 0 &&
7469 game.robot_wheel_y >= 0 &&
7470 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7471 game.engine_version < VERSION_IDENT(3,1,0,0)))
7473 attr_x = game.robot_wheel_x;
7474 attr_y = game.robot_wheel_y;
7477 if (element == EL_PENGUIN)
7480 struct XY *xy = xy_topdown;
7482 for (i = 0; i < NUM_DIRECTIONS; i++)
7484 int ex = x + xy[i].x;
7485 int ey = y + xy[i].y;
7487 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7488 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7489 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7490 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7499 MovDir[x][y] = MV_NONE;
7501 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7502 else if (attr_x > x)
7503 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7505 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7506 else if (attr_y > y)
7507 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7509 if (element == EL_ROBOT)
7513 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7514 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7515 Moving2Blocked(x, y, &newx, &newy);
7517 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7518 MovDelay[x][y] = 8 + 8 * !RND(3);
7520 MovDelay[x][y] = 16;
7522 else if (element == EL_PENGUIN)
7528 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7530 boolean first_horiz = RND(2);
7531 int new_move_dir = MovDir[x][y];
7534 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7535 Moving2Blocked(x, y, &newx, &newy);
7537 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7541 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7542 Moving2Blocked(x, y, &newx, &newy);
7544 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7547 MovDir[x][y] = old_move_dir;
7551 else if (element == EL_SATELLITE)
7557 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7559 boolean first_horiz = RND(2);
7560 int new_move_dir = MovDir[x][y];
7563 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7564 Moving2Blocked(x, y, &newx, &newy);
7566 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7570 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7571 Moving2Blocked(x, y, &newx, &newy);
7573 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7576 MovDir[x][y] = old_move_dir;
7580 else if (element == EL_EMC_ANDROID)
7582 static int check_pos[16] =
7584 -1, // 0 => (invalid)
7587 -1, // 3 => (invalid)
7589 0, // 5 => MV_LEFT | MV_UP
7590 2, // 6 => MV_RIGHT | MV_UP
7591 -1, // 7 => (invalid)
7593 6, // 9 => MV_LEFT | MV_DOWN
7594 4, // 10 => MV_RIGHT | MV_DOWN
7595 -1, // 11 => (invalid)
7596 -1, // 12 => (invalid)
7597 -1, // 13 => (invalid)
7598 -1, // 14 => (invalid)
7599 -1, // 15 => (invalid)
7607 { -1, -1, MV_LEFT | MV_UP },
7609 { +1, -1, MV_RIGHT | MV_UP },
7610 { +1, 0, MV_RIGHT },
7611 { +1, +1, MV_RIGHT | MV_DOWN },
7613 { -1, +1, MV_LEFT | MV_DOWN },
7616 int start_pos, check_order;
7617 boolean can_clone = FALSE;
7620 // check if there is any free field around current position
7621 for (i = 0; i < 8; i++)
7623 int newx = x + check_xy[i].dx;
7624 int newy = y + check_xy[i].dy;
7626 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7634 if (can_clone) // randomly find an element to clone
7638 start_pos = check_pos[RND(8)];
7639 check_order = (RND(2) ? -1 : +1);
7641 for (i = 0; i < 8; i++)
7643 int pos_raw = start_pos + i * check_order;
7644 int pos = (pos_raw + 8) % 8;
7645 int newx = x + check_xy[pos].dx;
7646 int newy = y + check_xy[pos].dy;
7648 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7650 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7651 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7653 Store[x][y] = Tile[newx][newy];
7662 if (can_clone) // randomly find a direction to move
7666 start_pos = check_pos[RND(8)];
7667 check_order = (RND(2) ? -1 : +1);
7669 for (i = 0; i < 8; i++)
7671 int pos_raw = start_pos + i * check_order;
7672 int pos = (pos_raw + 8) % 8;
7673 int newx = x + check_xy[pos].dx;
7674 int newy = y + check_xy[pos].dy;
7675 int new_move_dir = check_xy[pos].dir;
7677 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7679 MovDir[x][y] = new_move_dir;
7680 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7689 if (can_clone) // cloning and moving successful
7692 // cannot clone -- try to move towards player
7694 start_pos = check_pos[MovDir[x][y] & 0x0f];
7695 check_order = (RND(2) ? -1 : +1);
7697 for (i = 0; i < 3; i++)
7699 // first check start_pos, then previous/next or (next/previous) pos
7700 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7701 int pos = (pos_raw + 8) % 8;
7702 int newx = x + check_xy[pos].dx;
7703 int newy = y + check_xy[pos].dy;
7704 int new_move_dir = check_xy[pos].dir;
7706 if (IS_PLAYER(newx, newy))
7709 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7711 MovDir[x][y] = new_move_dir;
7712 MovDelay[x][y] = level.android_move_time * 8 + 1;
7719 else if (move_pattern == MV_TURNING_LEFT ||
7720 move_pattern == MV_TURNING_RIGHT ||
7721 move_pattern == MV_TURNING_LEFT_RIGHT ||
7722 move_pattern == MV_TURNING_RIGHT_LEFT ||
7723 move_pattern == MV_TURNING_RANDOM ||
7724 move_pattern == MV_ALL_DIRECTIONS)
7726 boolean can_turn_left =
7727 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7728 boolean can_turn_right =
7729 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y);
7731 if (element_info[element].move_stepsize == 0) // "not moving"
7734 if (move_pattern == MV_TURNING_LEFT)
7735 MovDir[x][y] = left_dir;
7736 else if (move_pattern == MV_TURNING_RIGHT)
7737 MovDir[x][y] = right_dir;
7738 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7739 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7740 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7741 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7742 else if (move_pattern == MV_TURNING_RANDOM)
7743 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7744 can_turn_right && !can_turn_left ? right_dir :
7745 RND(2) ? left_dir : right_dir);
7746 else if (can_turn_left && can_turn_right)
7747 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7748 else if (can_turn_left)
7749 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7750 else if (can_turn_right)
7751 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7753 MovDir[x][y] = back_dir;
7755 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7757 else if (move_pattern == MV_HORIZONTAL ||
7758 move_pattern == MV_VERTICAL)
7760 if (move_pattern & old_move_dir)
7761 MovDir[x][y] = back_dir;
7762 else if (move_pattern == MV_HORIZONTAL)
7763 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7764 else if (move_pattern == MV_VERTICAL)
7765 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7767 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7769 else if (move_pattern & MV_ANY_DIRECTION)
7771 MovDir[x][y] = move_pattern;
7772 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7774 else if (move_pattern & MV_WIND_DIRECTION)
7776 MovDir[x][y] = game.wind_direction;
7777 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7779 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7781 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7782 MovDir[x][y] = left_dir;
7783 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7784 MovDir[x][y] = right_dir;
7786 if (MovDir[x][y] != old_move_dir)
7787 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7789 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7791 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7792 MovDir[x][y] = right_dir;
7793 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7794 MovDir[x][y] = left_dir;
7796 if (MovDir[x][y] != old_move_dir)
7797 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7799 else if (move_pattern == MV_TOWARDS_PLAYER ||
7800 move_pattern == MV_AWAY_FROM_PLAYER)
7802 int attr_x = -1, attr_y = -1;
7804 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7806 if (game.all_players_gone)
7808 attr_x = game.exit_x;
7809 attr_y = game.exit_y;
7815 for (i = 0; i < MAX_PLAYERS; i++)
7817 struct PlayerInfo *player = &stored_player[i];
7818 int jx = player->jx, jy = player->jy;
7820 if (!player->active)
7824 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7832 MovDir[x][y] = MV_NONE;
7834 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7835 else if (attr_x > x)
7836 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7838 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7839 else if (attr_y > y)
7840 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7842 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7844 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7846 boolean first_horiz = RND(2);
7847 int new_move_dir = MovDir[x][y];
7849 if (element_info[element].move_stepsize == 0) // "not moving"
7851 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7852 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7858 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7859 Moving2Blocked(x, y, &newx, &newy);
7861 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7865 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7866 Moving2Blocked(x, y, &newx, &newy);
7868 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7871 MovDir[x][y] = old_move_dir;
7874 else if (move_pattern == MV_WHEN_PUSHED ||
7875 move_pattern == MV_WHEN_DROPPED)
7877 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7878 MovDir[x][y] = MV_NONE;
7882 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7884 struct XY *test_xy = xy_topdown;
7885 static int test_dir[4] =
7892 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7893 int move_preference = -1000000; // start with very low preference
7894 int new_move_dir = MV_NONE;
7895 int start_test = RND(4);
7898 for (i = 0; i < NUM_DIRECTIONS; i++)
7900 int j = (start_test + i) % 4;
7901 int move_dir = test_dir[j];
7902 int move_dir_preference;
7904 xx = x + test_xy[j].x;
7905 yy = y + test_xy[j].y;
7907 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7908 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7910 new_move_dir = move_dir;
7915 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7918 move_dir_preference = -1 * RunnerVisit[xx][yy];
7919 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7920 move_dir_preference = PlayerVisit[xx][yy];
7922 if (move_dir_preference > move_preference)
7924 // prefer field that has not been visited for the longest time
7925 move_preference = move_dir_preference;
7926 new_move_dir = move_dir;
7928 else if (move_dir_preference == move_preference &&
7929 move_dir == old_move_dir)
7931 // prefer last direction when all directions are preferred equally
7932 move_preference = move_dir_preference;
7933 new_move_dir = move_dir;
7937 MovDir[x][y] = new_move_dir;
7938 if (old_move_dir != new_move_dir)
7939 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7943 static void TurnRound(int x, int y)
7945 int direction = MovDir[x][y];
7949 GfxDir[x][y] = MovDir[x][y];
7951 if (direction != MovDir[x][y])
7955 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7957 ResetGfxFrame(x, y);
7960 static boolean JustBeingPushed(int x, int y)
7964 for (i = 0; i < MAX_PLAYERS; i++)
7966 struct PlayerInfo *player = &stored_player[i];
7968 if (player->active && player->is_pushing && player->MovPos)
7970 int next_jx = player->jx + (player->jx - player->last_jx);
7971 int next_jy = player->jy + (player->jy - player->last_jy);
7973 if (x == next_jx && y == next_jy)
7981 static void StartMoving(int x, int y)
7983 boolean started_moving = FALSE; // some elements can fall _and_ move
7984 int element = Tile[x][y];
7989 if (MovDelay[x][y] == 0)
7990 GfxAction[x][y] = ACTION_DEFAULT;
7992 if (CAN_FALL(element) && y < lev_fieldy - 1)
7994 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7995 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7996 if (JustBeingPushed(x, y))
7999 if (element == EL_QUICKSAND_FULL)
8001 if (IS_FREE(x, y + 1))
8003 InitMovingField(x, y, MV_DOWN);
8004 started_moving = TRUE;
8006 Tile[x][y] = EL_QUICKSAND_EMPTYING;
8007 #if USE_QUICKSAND_BD_ROCK_BUGFIX
8008 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
8009 Store[x][y] = EL_ROCK;
8011 Store[x][y] = EL_ROCK;
8014 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
8016 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8018 if (!MovDelay[x][y])
8020 MovDelay[x][y] = TILEY + 1;
8022 ResetGfxAnimation(x, y);
8023 ResetGfxAnimation(x, y + 1);
8028 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
8029 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8036 Tile[x][y] = EL_QUICKSAND_EMPTY;
8037 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8038 Store[x][y + 1] = Store[x][y];
8041 PlayLevelSoundAction(x, y, ACTION_FILLING);
8043 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8045 if (!MovDelay[x][y])
8047 MovDelay[x][y] = TILEY + 1;
8049 ResetGfxAnimation(x, y);
8050 ResetGfxAnimation(x, y + 1);
8055 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
8056 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8063 Tile[x][y] = EL_QUICKSAND_EMPTY;
8064 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8065 Store[x][y + 1] = Store[x][y];
8068 PlayLevelSoundAction(x, y, ACTION_FILLING);
8071 else if (element == EL_QUICKSAND_FAST_FULL)
8073 if (IS_FREE(x, y + 1))
8075 InitMovingField(x, y, MV_DOWN);
8076 started_moving = TRUE;
8078 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
8079 #if USE_QUICKSAND_BD_ROCK_BUGFIX
8080 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
8081 Store[x][y] = EL_ROCK;
8083 Store[x][y] = EL_ROCK;
8086 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
8088 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8090 if (!MovDelay[x][y])
8092 MovDelay[x][y] = TILEY + 1;
8094 ResetGfxAnimation(x, y);
8095 ResetGfxAnimation(x, y + 1);
8100 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8101 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8108 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8109 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8110 Store[x][y + 1] = Store[x][y];
8113 PlayLevelSoundAction(x, y, ACTION_FILLING);
8115 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8117 if (!MovDelay[x][y])
8119 MovDelay[x][y] = TILEY + 1;
8121 ResetGfxAnimation(x, y);
8122 ResetGfxAnimation(x, y + 1);
8127 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8128 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8135 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8136 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8137 Store[x][y + 1] = Store[x][y];
8140 PlayLevelSoundAction(x, y, ACTION_FILLING);
8143 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8144 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8146 InitMovingField(x, y, MV_DOWN);
8147 started_moving = TRUE;
8149 Tile[x][y] = EL_QUICKSAND_FILLING;
8150 Store[x][y] = element;
8152 PlayLevelSoundAction(x, y, ACTION_FILLING);
8154 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8155 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8157 InitMovingField(x, y, MV_DOWN);
8158 started_moving = TRUE;
8160 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
8161 Store[x][y] = element;
8163 PlayLevelSoundAction(x, y, ACTION_FILLING);
8165 else if (element == EL_MAGIC_WALL_FULL)
8167 if (IS_FREE(x, y + 1))
8169 InitMovingField(x, y, MV_DOWN);
8170 started_moving = TRUE;
8172 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
8173 Store[x][y] = EL_CHANGED(Store[x][y]);
8175 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
8177 if (!MovDelay[x][y])
8178 MovDelay[x][y] = TILEY / 4 + 1;
8187 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
8188 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
8189 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8193 else if (element == EL_BD_MAGIC_WALL_FULL)
8195 if (IS_FREE(x, y + 1))
8197 InitMovingField(x, y, MV_DOWN);
8198 started_moving = TRUE;
8200 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8201 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8203 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8205 if (!MovDelay[x][y])
8206 MovDelay[x][y] = TILEY / 4 + 1;
8215 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8216 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8217 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8221 else if (element == EL_DC_MAGIC_WALL_FULL)
8223 if (IS_FREE(x, y + 1))
8225 InitMovingField(x, y, MV_DOWN);
8226 started_moving = TRUE;
8228 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8229 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8231 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8233 if (!MovDelay[x][y])
8234 MovDelay[x][y] = TILEY / 4 + 1;
8243 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8244 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8245 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8249 else if ((CAN_PASS_MAGIC_WALL(element) &&
8250 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8251 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8252 (CAN_PASS_DC_MAGIC_WALL(element) &&
8253 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8256 InitMovingField(x, y, MV_DOWN);
8257 started_moving = TRUE;
8260 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8261 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8262 EL_DC_MAGIC_WALL_FILLING);
8263 Store[x][y] = element;
8265 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8267 SplashAcid(x, y + 1);
8269 InitMovingField(x, y, MV_DOWN);
8270 started_moving = TRUE;
8272 Store[x][y] = EL_ACID;
8275 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8276 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8277 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8278 CAN_FALL(element) && WasJustFalling[x][y] &&
8279 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8281 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8282 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8283 (Tile[x][y + 1] == EL_BLOCKED)))
8285 /* this is needed for a special case not covered by calling "Impact()"
8286 from "ContinueMoving()": if an element moves to a tile directly below
8287 another element which was just falling on that tile (which was empty
8288 in the previous frame), the falling element above would just stop
8289 instead of smashing the element below (in previous version, the above
8290 element was just checked for "moving" instead of "falling", resulting
8291 in incorrect smashes caused by horizontal movement of the above
8292 element; also, the case of the player being the element to smash was
8293 simply not covered here... :-/ ) */
8295 CheckCollision[x][y] = 0;
8296 CheckImpact[x][y] = 0;
8300 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8302 if (MovDir[x][y] == MV_NONE)
8304 InitMovingField(x, y, MV_DOWN);
8305 started_moving = TRUE;
8308 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8310 if (WasJustFalling[x][y]) // prevent animation from being restarted
8311 MovDir[x][y] = MV_DOWN;
8313 InitMovingField(x, y, MV_DOWN);
8314 started_moving = TRUE;
8316 else if (element == EL_AMOEBA_DROP)
8318 Tile[x][y] = EL_AMOEBA_GROWING;
8319 Store[x][y] = EL_AMOEBA_WET;
8321 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8322 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8323 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8324 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8326 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8327 (IS_FREE(x - 1, y + 1) ||
8328 Tile[x - 1][y + 1] == EL_ACID));
8329 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8330 (IS_FREE(x + 1, y + 1) ||
8331 Tile[x + 1][y + 1] == EL_ACID));
8332 boolean can_fall_any = (can_fall_left || can_fall_right);
8333 boolean can_fall_both = (can_fall_left && can_fall_right);
8334 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8336 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8338 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8339 can_fall_right = FALSE;
8340 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8341 can_fall_left = FALSE;
8342 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8343 can_fall_right = FALSE;
8344 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8345 can_fall_left = FALSE;
8347 can_fall_any = (can_fall_left || can_fall_right);
8348 can_fall_both = FALSE;
8353 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8354 can_fall_right = FALSE; // slip down on left side
8356 can_fall_left = !(can_fall_right = RND(2));
8358 can_fall_both = FALSE;
8363 // if not determined otherwise, prefer left side for slipping down
8364 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8365 started_moving = TRUE;
8368 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8370 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8371 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8372 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8373 int belt_dir = game.belt_dir[belt_nr];
8375 if ((belt_dir == MV_LEFT && left_is_free) ||
8376 (belt_dir == MV_RIGHT && right_is_free))
8378 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8380 InitMovingField(x, y, belt_dir);
8381 started_moving = TRUE;
8383 Pushed[x][y] = TRUE;
8384 Pushed[nextx][y] = TRUE;
8386 GfxAction[x][y] = ACTION_DEFAULT;
8390 MovDir[x][y] = 0; // if element was moving, stop it
8395 // not "else if" because of elements that can fall and move (EL_SPRING)
8396 if (CAN_MOVE(element) && !started_moving)
8398 int move_pattern = element_info[element].move_pattern;
8401 Moving2Blocked(x, y, &newx, &newy);
8403 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8406 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8407 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8409 WasJustMoving[x][y] = 0;
8410 CheckCollision[x][y] = 0;
8412 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8414 if (Tile[x][y] != element) // element has changed
8418 if (!MovDelay[x][y]) // start new movement phase
8420 // all objects that can change their move direction after each step
8421 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8423 if (element != EL_YAMYAM &&
8424 element != EL_DARK_YAMYAM &&
8425 element != EL_PACMAN &&
8426 !(move_pattern & MV_ANY_DIRECTION) &&
8427 move_pattern != MV_TURNING_LEFT &&
8428 move_pattern != MV_TURNING_RIGHT &&
8429 move_pattern != MV_TURNING_LEFT_RIGHT &&
8430 move_pattern != MV_TURNING_RIGHT_LEFT &&
8431 move_pattern != MV_TURNING_RANDOM)
8435 if (MovDelay[x][y] && (element == EL_BUG ||
8436 element == EL_SPACESHIP ||
8437 element == EL_SP_SNIKSNAK ||
8438 element == EL_SP_ELECTRON ||
8439 element == EL_MOLE))
8440 TEST_DrawLevelField(x, y);
8444 if (MovDelay[x][y]) // wait some time before next movement
8448 if (element == EL_ROBOT ||
8449 element == EL_YAMYAM ||
8450 element == EL_DARK_YAMYAM)
8452 DrawLevelElementAnimationIfNeeded(x, y, element);
8453 PlayLevelSoundAction(x, y, ACTION_WAITING);
8455 else if (element == EL_SP_ELECTRON)
8456 DrawLevelElementAnimationIfNeeded(x, y, element);
8457 else if (element == EL_DRAGON)
8460 int dir = MovDir[x][y];
8461 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8462 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8463 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8464 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8465 dir == MV_UP ? IMG_FLAMES_1_UP :
8466 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8467 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8469 GfxAction[x][y] = ACTION_ATTACKING;
8471 if (IS_PLAYER(x, y))
8472 DrawPlayerField(x, y);
8474 TEST_DrawLevelField(x, y);
8476 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8478 for (i = 1; i <= 3; i++)
8480 int xx = x + i * dx;
8481 int yy = y + i * dy;
8482 int sx = SCREENX(xx);
8483 int sy = SCREENY(yy);
8484 int flame_graphic = graphic + (i - 1);
8486 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8491 int flamed = MovingOrBlocked2Element(xx, yy);
8493 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8496 RemoveMovingField(xx, yy);
8498 ChangeDelay[xx][yy] = 0;
8500 Tile[xx][yy] = EL_FLAMES;
8502 if (IN_SCR_FIELD(sx, sy))
8504 TEST_DrawLevelFieldCrumbled(xx, yy);
8505 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8510 if (Tile[xx][yy] == EL_FLAMES)
8511 Tile[xx][yy] = EL_EMPTY;
8512 TEST_DrawLevelField(xx, yy);
8517 if (MovDelay[x][y]) // element still has to wait some time
8519 PlayLevelSoundAction(x, y, ACTION_WAITING);
8525 // now make next step
8527 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8529 if (DONT_COLLIDE_WITH(element) &&
8530 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8531 !PLAYER_ENEMY_PROTECTED(newx, newy))
8533 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8538 else if (CAN_MOVE_INTO_ACID(element) &&
8539 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8540 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8541 (MovDir[x][y] == MV_DOWN ||
8542 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8544 SplashAcid(newx, newy);
8545 Store[x][y] = EL_ACID;
8547 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8549 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8550 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8551 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8552 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8555 TEST_DrawLevelField(x, y);
8557 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8558 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8559 DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0);
8561 game.friends_still_needed--;
8562 if (!game.friends_still_needed &&
8564 game.all_players_gone)
8569 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8571 if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING)
8572 TEST_DrawLevelField(newx, newy);
8574 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8576 else if (!IS_FREE(newx, newy))
8578 GfxAction[x][y] = ACTION_WAITING;
8580 if (IS_PLAYER(x, y))
8581 DrawPlayerField(x, y);
8583 TEST_DrawLevelField(x, y);
8588 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8590 if (IS_FOOD_PIG(Tile[newx][newy]))
8592 if (IS_MOVING(newx, newy))
8593 RemoveMovingField(newx, newy);
8596 Tile[newx][newy] = EL_EMPTY;
8597 TEST_DrawLevelField(newx, newy);
8600 PlayLevelSound(x, y, SND_PIG_DIGGING);
8602 else if (!IS_FREE(newx, newy))
8604 if (IS_PLAYER(x, y))
8605 DrawPlayerField(x, y);
8607 TEST_DrawLevelField(x, y);
8612 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8614 if (Store[x][y] != EL_EMPTY)
8616 boolean can_clone = FALSE;
8619 // check if element to clone is still there
8620 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8622 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8630 // cannot clone or target field not free anymore -- do not clone
8631 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8632 Store[x][y] = EL_EMPTY;
8635 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8637 if (IS_MV_DIAGONAL(MovDir[x][y]))
8639 int diagonal_move_dir = MovDir[x][y];
8640 int stored = Store[x][y];
8641 int change_delay = 8;
8644 // android is moving diagonally
8646 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8648 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8649 GfxElement[x][y] = EL_EMC_ANDROID;
8650 GfxAction[x][y] = ACTION_SHRINKING;
8651 GfxDir[x][y] = diagonal_move_dir;
8652 ChangeDelay[x][y] = change_delay;
8654 if (Store[x][y] == EL_EMPTY)
8655 Store[x][y] = GfxElementEmpty[x][y];
8657 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8660 DrawLevelGraphicAnimation(x, y, graphic);
8661 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8663 if (Tile[newx][newy] == EL_ACID)
8665 SplashAcid(newx, newy);
8670 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8672 Store[newx][newy] = EL_EMC_ANDROID;
8673 GfxElement[newx][newy] = EL_EMC_ANDROID;
8674 GfxAction[newx][newy] = ACTION_GROWING;
8675 GfxDir[newx][newy] = diagonal_move_dir;
8676 ChangeDelay[newx][newy] = change_delay;
8678 graphic = el_act_dir2img(GfxElement[newx][newy],
8679 GfxAction[newx][newy], GfxDir[newx][newy]);
8681 DrawLevelGraphicAnimation(newx, newy, graphic);
8682 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8688 Tile[newx][newy] = EL_EMPTY;
8689 TEST_DrawLevelField(newx, newy);
8691 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8694 else if (!IS_FREE(newx, newy))
8699 else if (IS_CUSTOM_ELEMENT(element) &&
8700 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8702 if (!DigFieldByCE(newx, newy, element))
8705 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8707 RunnerVisit[x][y] = FrameCounter;
8708 PlayerVisit[x][y] /= 8; // expire player visit path
8711 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8713 if (!IS_FREE(newx, newy))
8715 if (IS_PLAYER(x, y))
8716 DrawPlayerField(x, y);
8718 TEST_DrawLevelField(x, y);
8724 boolean wanna_flame = !RND(10);
8725 int dx = newx - x, dy = newy - y;
8726 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8727 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8728 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8729 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8730 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8731 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8734 IS_CLASSIC_ENEMY(element1) ||
8735 IS_CLASSIC_ENEMY(element2)) &&
8736 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8737 element1 != EL_FLAMES && element2 != EL_FLAMES)
8739 ResetGfxAnimation(x, y);
8740 GfxAction[x][y] = ACTION_ATTACKING;
8742 if (IS_PLAYER(x, y))
8743 DrawPlayerField(x, y);
8745 TEST_DrawLevelField(x, y);
8747 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8749 MovDelay[x][y] = 50;
8751 Tile[newx][newy] = EL_FLAMES;
8752 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8753 Tile[newx1][newy1] = EL_FLAMES;
8754 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8755 Tile[newx2][newy2] = EL_FLAMES;
8761 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8762 Tile[newx][newy] == EL_DIAMOND)
8764 if (IS_MOVING(newx, newy))
8765 RemoveMovingField(newx, newy);
8768 Tile[newx][newy] = EL_EMPTY;
8769 TEST_DrawLevelField(newx, newy);
8772 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8774 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8775 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8777 if (AmoebaNr[newx][newy])
8779 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8780 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8781 Tile[newx][newy] == EL_BD_AMOEBA)
8782 AmoebaCnt[AmoebaNr[newx][newy]]--;
8785 if (IS_MOVING(newx, newy))
8787 RemoveMovingField(newx, newy);
8791 Tile[newx][newy] = EL_EMPTY;
8792 TEST_DrawLevelField(newx, newy);
8795 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8797 else if ((element == EL_PACMAN || element == EL_MOLE)
8798 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8800 if (AmoebaNr[newx][newy])
8802 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8803 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8804 Tile[newx][newy] == EL_BD_AMOEBA)
8805 AmoebaCnt[AmoebaNr[newx][newy]]--;
8808 if (element == EL_MOLE)
8810 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8811 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8813 ResetGfxAnimation(x, y);
8814 GfxAction[x][y] = ACTION_DIGGING;
8815 TEST_DrawLevelField(x, y);
8817 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8819 return; // wait for shrinking amoeba
8821 else // element == EL_PACMAN
8823 Tile[newx][newy] = EL_EMPTY;
8824 TEST_DrawLevelField(newx, newy);
8825 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8828 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8829 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8830 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8832 // wait for shrinking amoeba to completely disappear
8835 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8837 // object was running against a wall
8841 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8842 DrawLevelElementAnimation(x, y, element);
8844 if (DONT_TOUCH(element))
8845 TestIfBadThingTouchesPlayer(x, y);
8850 InitMovingField(x, y, MovDir[x][y]);
8852 PlayLevelSoundAction(x, y, ACTION_MOVING);
8856 ContinueMoving(x, y);
8859 void ContinueMoving(int x, int y)
8861 int element = Tile[x][y];
8862 struct ElementInfo *ei = &element_info[element];
8863 int direction = MovDir[x][y];
8864 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8865 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8866 int newx = x + dx, newy = y + dy;
8867 int stored = Store[x][y];
8868 int stored_new = Store[newx][newy];
8869 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8870 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8871 boolean last_line = (newy == lev_fieldy - 1);
8872 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8874 if (pushed_by_player) // special case: moving object pushed by player
8876 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos));
8878 else if (use_step_delay) // special case: moving object has step delay
8880 if (!MovDelay[x][y])
8881 MovPos[x][y] += getElementMoveStepsize(x, y);
8886 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8890 TEST_DrawLevelField(x, y);
8892 return; // element is still waiting
8895 else // normal case: generically moving object
8897 MovPos[x][y] += getElementMoveStepsize(x, y);
8900 if (ABS(MovPos[x][y]) < TILEX)
8902 TEST_DrawLevelField(x, y);
8904 return; // element is still moving
8907 // element reached destination field
8909 Tile[x][y] = EL_EMPTY;
8910 Tile[newx][newy] = element;
8911 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8913 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8915 element = Tile[newx][newy] = EL_ACID;
8917 else if (element == EL_MOLE)
8919 Tile[x][y] = EL_SAND;
8921 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8923 else if (element == EL_QUICKSAND_FILLING)
8925 element = Tile[newx][newy] = get_next_element(element);
8926 Store[newx][newy] = Store[x][y];
8928 else if (element == EL_QUICKSAND_EMPTYING)
8930 Tile[x][y] = get_next_element(element);
8931 element = Tile[newx][newy] = Store[x][y];
8933 else if (element == EL_QUICKSAND_FAST_FILLING)
8935 element = Tile[newx][newy] = get_next_element(element);
8936 Store[newx][newy] = Store[x][y];
8938 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8940 Tile[x][y] = get_next_element(element);
8941 element = Tile[newx][newy] = Store[x][y];
8943 else if (element == EL_MAGIC_WALL_FILLING)
8945 element = Tile[newx][newy] = get_next_element(element);
8946 if (!game.magic_wall_active)
8947 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8948 Store[newx][newy] = Store[x][y];
8950 else if (element == EL_MAGIC_WALL_EMPTYING)
8952 Tile[x][y] = get_next_element(element);
8953 if (!game.magic_wall_active)
8954 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8955 element = Tile[newx][newy] = Store[x][y];
8957 InitField(newx, newy, FALSE);
8959 else if (element == EL_BD_MAGIC_WALL_FILLING)
8961 element = Tile[newx][newy] = get_next_element(element);
8962 if (!game.magic_wall_active)
8963 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8964 Store[newx][newy] = Store[x][y];
8966 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8968 Tile[x][y] = get_next_element(element);
8969 if (!game.magic_wall_active)
8970 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8971 element = Tile[newx][newy] = Store[x][y];
8973 InitField(newx, newy, FALSE);
8975 else if (element == EL_DC_MAGIC_WALL_FILLING)
8977 element = Tile[newx][newy] = get_next_element(element);
8978 if (!game.magic_wall_active)
8979 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8980 Store[newx][newy] = Store[x][y];
8982 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8984 Tile[x][y] = get_next_element(element);
8985 if (!game.magic_wall_active)
8986 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8987 element = Tile[newx][newy] = Store[x][y];
8989 InitField(newx, newy, FALSE);
8991 else if (element == EL_AMOEBA_DROPPING)
8993 Tile[x][y] = get_next_element(element);
8994 element = Tile[newx][newy] = Store[x][y];
8996 else if (element == EL_SOKOBAN_OBJECT)
8999 Tile[x][y] = Back[x][y];
9001 if (Back[newx][newy])
9002 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
9004 Back[x][y] = Back[newx][newy] = 0;
9007 Store[x][y] = EL_EMPTY;
9012 MovDelay[newx][newy] = 0;
9014 if (CAN_CHANGE_OR_HAS_ACTION(element))
9016 // copy element change control values to new field
9017 ChangeDelay[newx][newy] = ChangeDelay[x][y];
9018 ChangePage[newx][newy] = ChangePage[x][y];
9019 ChangeCount[newx][newy] = ChangeCount[x][y];
9020 ChangeEvent[newx][newy] = ChangeEvent[x][y];
9023 CustomValue[newx][newy] = CustomValue[x][y];
9025 ChangeDelay[x][y] = 0;
9026 ChangePage[x][y] = -1;
9027 ChangeCount[x][y] = 0;
9028 ChangeEvent[x][y] = -1;
9030 CustomValue[x][y] = 0;
9032 // copy animation control values to new field
9033 GfxFrame[newx][newy] = GfxFrame[x][y];
9034 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
9035 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
9036 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
9038 Pushed[x][y] = Pushed[newx][newy] = FALSE;
9040 // some elements can leave other elements behind after moving
9041 if (ei->move_leave_element != EL_EMPTY &&
9042 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
9043 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
9045 int move_leave_element = ei->move_leave_element;
9047 // this makes it possible to leave the removed element again
9048 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
9049 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
9051 Tile[x][y] = move_leave_element;
9053 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
9054 MovDir[x][y] = direction;
9056 InitField(x, y, FALSE);
9058 if (GFX_CRUMBLED(Tile[x][y]))
9059 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
9061 if (IS_PLAYER_ELEMENT(move_leave_element))
9062 RelocatePlayer(x, y, move_leave_element);
9065 // do this after checking for left-behind element
9066 ResetGfxAnimation(x, y); // reset animation values for old field
9068 if (!CAN_MOVE(element) ||
9069 (CAN_FALL(element) && direction == MV_DOWN &&
9070 (element == EL_SPRING ||
9071 element_info[element].move_pattern == MV_WHEN_PUSHED ||
9072 element_info[element].move_pattern == MV_WHEN_DROPPED)))
9073 GfxDir[x][y] = MovDir[newx][newy] = 0;
9075 TEST_DrawLevelField(x, y);
9076 TEST_DrawLevelField(newx, newy);
9078 Stop[newx][newy] = TRUE; // ignore this element until the next frame
9080 // prevent pushed element from moving on in pushed direction
9081 if (pushed_by_player && CAN_MOVE(element) &&
9082 element_info[element].move_pattern & MV_ANY_DIRECTION &&
9083 !(element_info[element].move_pattern & direction))
9084 TurnRound(newx, newy);
9086 // prevent elements on conveyor belt from moving on in last direction
9087 if (pushed_by_conveyor && CAN_FALL(element) &&
9088 direction & MV_HORIZONTAL)
9089 MovDir[newx][newy] = 0;
9091 if (!pushed_by_player)
9093 int nextx = newx + dx, nexty = newy + dy;
9094 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
9096 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
9098 if (CAN_FALL(element) && direction == MV_DOWN)
9099 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
9101 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
9102 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
9104 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
9105 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
9108 if (DONT_TOUCH(element)) // object may be nasty to player or others
9110 TestIfBadThingTouchesPlayer(newx, newy);
9111 TestIfBadThingTouchesFriend(newx, newy);
9113 if (!IS_CUSTOM_ELEMENT(element))
9114 TestIfBadThingTouchesOtherBadThing(newx, newy);
9116 else if (element == EL_PENGUIN)
9117 TestIfFriendTouchesBadThing(newx, newy);
9119 if (DONT_GET_HIT_BY(element))
9121 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
9124 // give the player one last chance (one more frame) to move away
9125 if (CAN_FALL(element) && direction == MV_DOWN &&
9126 (last_line || (!IS_FREE(x, newy + 1) &&
9127 (!IS_PLAYER(x, newy + 1) ||
9128 game.engine_version < VERSION_IDENT(3,1,1,0)))))
9131 if (pushed_by_player && !game.use_change_when_pushing_bug)
9133 int push_side = MV_DIR_OPPOSITE(direction);
9134 struct PlayerInfo *player = PLAYERINFO(x, y);
9136 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
9137 player->index_bit, push_side);
9138 CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X,
9139 player->index_bit, push_side);
9142 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
9143 MovDelay[newx][newy] = 1;
9145 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
9147 TestIfElementTouchesCustomElement(x, y); // empty or new element
9148 TestIfElementHitsCustomElement(newx, newy, direction);
9149 TestIfPlayerTouchesCustomElement(newx, newy);
9150 TestIfElementTouchesCustomElement(newx, newy);
9152 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
9153 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
9154 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
9155 MV_DIR_OPPOSITE(direction));
9158 int AmoebaNeighbourNr(int ax, int ay)
9161 int element = Tile[ax][ay];
9163 struct XY *xy = xy_topdown;
9165 for (i = 0; i < NUM_DIRECTIONS; i++)
9167 int x = ax + xy[i].x;
9168 int y = ay + xy[i].y;
9170 if (!IN_LEV_FIELD(x, y))
9173 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
9174 group_nr = AmoebaNr[x][y];
9180 static void AmoebaMerge(int ax, int ay)
9182 int i, x, y, xx, yy;
9183 int new_group_nr = AmoebaNr[ax][ay];
9184 struct XY *xy = xy_topdown;
9186 if (new_group_nr == 0)
9189 for (i = 0; i < NUM_DIRECTIONS; i++)
9194 if (!IN_LEV_FIELD(x, y))
9197 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9198 Tile[x][y] == EL_BD_AMOEBA ||
9199 Tile[x][y] == EL_AMOEBA_DEAD) &&
9200 AmoebaNr[x][y] != new_group_nr)
9202 int old_group_nr = AmoebaNr[x][y];
9204 if (old_group_nr == 0)
9207 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9208 AmoebaCnt[old_group_nr] = 0;
9209 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9210 AmoebaCnt2[old_group_nr] = 0;
9212 SCAN_PLAYFIELD(xx, yy)
9214 if (AmoebaNr[xx][yy] == old_group_nr)
9215 AmoebaNr[xx][yy] = new_group_nr;
9221 void AmoebaToDiamond(int ax, int ay)
9225 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9227 int group_nr = AmoebaNr[ax][ay];
9232 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9233 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9239 SCAN_PLAYFIELD(x, y)
9241 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9244 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9248 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9249 SND_AMOEBA_TURNING_TO_GEM :
9250 SND_AMOEBA_TURNING_TO_ROCK));
9255 struct XY *xy = xy_topdown;
9257 for (i = 0; i < NUM_DIRECTIONS; i++)
9262 if (!IN_LEV_FIELD(x, y))
9265 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9267 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9268 SND_AMOEBA_TURNING_TO_GEM :
9269 SND_AMOEBA_TURNING_TO_ROCK));
9276 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9279 int group_nr = AmoebaNr[ax][ay];
9280 boolean done = FALSE;
9285 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9286 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9292 SCAN_PLAYFIELD(x, y)
9294 if (AmoebaNr[x][y] == group_nr &&
9295 (Tile[x][y] == EL_AMOEBA_DEAD ||
9296 Tile[x][y] == EL_BD_AMOEBA ||
9297 Tile[x][y] == EL_AMOEBA_GROWING))
9300 Tile[x][y] = new_element;
9301 InitField(x, y, FALSE);
9302 TEST_DrawLevelField(x, y);
9308 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9309 SND_BD_AMOEBA_TURNING_TO_ROCK :
9310 SND_BD_AMOEBA_TURNING_TO_GEM));
9313 static void AmoebaGrowing(int x, int y)
9315 static DelayCounter sound_delay = { 0 };
9317 if (!MovDelay[x][y]) // start new growing cycle
9321 if (DelayReached(&sound_delay))
9323 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9324 sound_delay.value = 30;
9328 if (MovDelay[x][y]) // wait some time before growing bigger
9331 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9333 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9334 6 - MovDelay[x][y]);
9336 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9339 if (!MovDelay[x][y])
9341 Tile[x][y] = Store[x][y];
9343 TEST_DrawLevelField(x, y);
9348 static void AmoebaShrinking(int x, int y)
9350 static DelayCounter sound_delay = { 0 };
9352 if (!MovDelay[x][y]) // start new shrinking cycle
9356 if (DelayReached(&sound_delay))
9357 sound_delay.value = 30;
9360 if (MovDelay[x][y]) // wait some time before shrinking
9363 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9365 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9366 6 - MovDelay[x][y]);
9368 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9371 if (!MovDelay[x][y])
9373 Tile[x][y] = EL_EMPTY;
9374 TEST_DrawLevelField(x, y);
9376 // don't let mole enter this field in this cycle;
9377 // (give priority to objects falling to this field from above)
9383 static void AmoebaReproduce(int ax, int ay)
9386 int element = Tile[ax][ay];
9387 int graphic = el2img(element);
9388 int newax = ax, neway = ay;
9389 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9390 struct XY *xy = xy_topdown;
9392 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9394 Tile[ax][ay] = EL_AMOEBA_DEAD;
9395 TEST_DrawLevelField(ax, ay);
9399 if (IS_ANIMATED(graphic))
9400 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9402 if (!MovDelay[ax][ay]) // start making new amoeba field
9403 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9405 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9408 if (MovDelay[ax][ay])
9412 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9415 int x = ax + xy[start].x;
9416 int y = ay + xy[start].y;
9418 if (!IN_LEV_FIELD(x, y))
9421 if (IS_FREE(x, y) ||
9422 CAN_GROW_INTO(Tile[x][y]) ||
9423 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9424 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9430 if (newax == ax && neway == ay)
9433 else // normal or "filled" (BD style) amoeba
9436 boolean waiting_for_player = FALSE;
9438 for (i = 0; i < NUM_DIRECTIONS; i++)
9440 int j = (start + i) % 4;
9441 int x = ax + xy[j].x;
9442 int y = ay + xy[j].y;
9444 if (!IN_LEV_FIELD(x, y))
9447 if (IS_FREE(x, y) ||
9448 CAN_GROW_INTO(Tile[x][y]) ||
9449 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9450 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9456 else if (IS_PLAYER(x, y))
9457 waiting_for_player = TRUE;
9460 if (newax == ax && neway == ay) // amoeba cannot grow
9462 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9464 Tile[ax][ay] = EL_AMOEBA_DEAD;
9465 TEST_DrawLevelField(ax, ay);
9466 AmoebaCnt[AmoebaNr[ax][ay]]--;
9468 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9470 if (element == EL_AMOEBA_FULL)
9471 AmoebaToDiamond(ax, ay);
9472 else if (element == EL_BD_AMOEBA)
9473 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9478 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9480 // amoeba gets larger by growing in some direction
9482 int new_group_nr = AmoebaNr[ax][ay];
9485 if (new_group_nr == 0)
9487 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9489 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9495 AmoebaNr[newax][neway] = new_group_nr;
9496 AmoebaCnt[new_group_nr]++;
9497 AmoebaCnt2[new_group_nr]++;
9499 // if amoeba touches other amoeba(s) after growing, unify them
9500 AmoebaMerge(newax, neway);
9502 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9504 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9510 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9511 (neway == lev_fieldy - 1 && newax != ax))
9513 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9514 Store[newax][neway] = element;
9516 else if (neway == ay || element == EL_EMC_DRIPPER)
9518 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9520 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9524 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9525 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9526 Store[ax][ay] = EL_AMOEBA_DROP;
9527 ContinueMoving(ax, ay);
9531 TEST_DrawLevelField(newax, neway);
9534 static void Life(int ax, int ay)
9538 int element = Tile[ax][ay];
9539 int graphic = el2img(element);
9540 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9542 boolean changed = FALSE;
9544 if (IS_ANIMATED(graphic))
9545 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9550 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9551 MovDelay[ax][ay] = life_time;
9553 if (MovDelay[ax][ay]) // wait some time before next cycle
9556 if (MovDelay[ax][ay])
9560 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9562 int xx = ax + x1, yy = ay + y1;
9563 int old_element = Tile[xx][yy];
9564 int num_neighbours = 0;
9566 if (!IN_LEV_FIELD(xx, yy))
9569 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9571 int x = xx + x2, y = yy + y2;
9573 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9576 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9577 boolean is_neighbour = FALSE;
9579 if (level.use_life_bugs)
9581 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9582 (IS_FREE(x, y) && Stop[x][y]));
9585 (Last[x][y] == element || is_player_cell);
9591 boolean is_free = FALSE;
9593 if (level.use_life_bugs)
9594 is_free = (IS_FREE(xx, yy));
9596 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9598 if (xx == ax && yy == ay) // field in the middle
9600 if (num_neighbours < life_parameter[0] ||
9601 num_neighbours > life_parameter[1])
9603 Tile[xx][yy] = EL_EMPTY;
9604 if (Tile[xx][yy] != old_element)
9605 TEST_DrawLevelField(xx, yy);
9606 Stop[xx][yy] = TRUE;
9610 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9611 { // free border field
9612 if (num_neighbours >= life_parameter[2] &&
9613 num_neighbours <= life_parameter[3])
9615 Tile[xx][yy] = element;
9616 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9617 if (Tile[xx][yy] != old_element)
9618 TEST_DrawLevelField(xx, yy);
9619 Stop[xx][yy] = TRUE;
9626 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9627 SND_GAME_OF_LIFE_GROWING);
9630 static void InitRobotWheel(int x, int y)
9632 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9635 static void RunRobotWheel(int x, int y)
9637 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9640 static void StopRobotWheel(int x, int y)
9642 if (game.robot_wheel_x == x &&
9643 game.robot_wheel_y == y)
9645 game.robot_wheel_x = -1;
9646 game.robot_wheel_y = -1;
9647 game.robot_wheel_active = FALSE;
9651 static void InitTimegateWheel(int x, int y)
9653 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9656 static void RunTimegateWheel(int x, int y)
9658 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9661 static void InitMagicBallDelay(int x, int y)
9663 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9666 static void ActivateMagicBall(int bx, int by)
9670 if (level.ball_random)
9672 int pos_border = RND(8); // select one of the eight border elements
9673 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9674 int xx = pos_content % 3;
9675 int yy = pos_content / 3;
9680 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9681 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9685 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9687 int xx = x - bx + 1;
9688 int yy = y - by + 1;
9690 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9691 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9695 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9698 static void CheckExit(int x, int y)
9700 if (game.gems_still_needed > 0 ||
9701 game.sokoban_fields_still_needed > 0 ||
9702 game.sokoban_objects_still_needed > 0 ||
9703 game.lights_still_needed > 0)
9705 int element = Tile[x][y];
9706 int graphic = el2img(element);
9708 if (IS_ANIMATED(graphic))
9709 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9714 // do not re-open exit door closed after last player
9715 if (game.all_players_gone)
9718 Tile[x][y] = EL_EXIT_OPENING;
9720 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9723 static void CheckExitEM(int x, int y)
9725 if (game.gems_still_needed > 0 ||
9726 game.sokoban_fields_still_needed > 0 ||
9727 game.sokoban_objects_still_needed > 0 ||
9728 game.lights_still_needed > 0)
9730 int element = Tile[x][y];
9731 int graphic = el2img(element);
9733 if (IS_ANIMATED(graphic))
9734 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9739 // do not re-open exit door closed after last player
9740 if (game.all_players_gone)
9743 Tile[x][y] = EL_EM_EXIT_OPENING;
9745 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9748 static void CheckExitSteel(int x, int y)
9750 if (game.gems_still_needed > 0 ||
9751 game.sokoban_fields_still_needed > 0 ||
9752 game.sokoban_objects_still_needed > 0 ||
9753 game.lights_still_needed > 0)
9755 int element = Tile[x][y];
9756 int graphic = el2img(element);
9758 if (IS_ANIMATED(graphic))
9759 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9764 // do not re-open exit door closed after last player
9765 if (game.all_players_gone)
9768 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9770 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9773 static void CheckExitSteelEM(int x, int y)
9775 if (game.gems_still_needed > 0 ||
9776 game.sokoban_fields_still_needed > 0 ||
9777 game.sokoban_objects_still_needed > 0 ||
9778 game.lights_still_needed > 0)
9780 int element = Tile[x][y];
9781 int graphic = el2img(element);
9783 if (IS_ANIMATED(graphic))
9784 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9789 // do not re-open exit door closed after last player
9790 if (game.all_players_gone)
9793 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9795 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9798 static void CheckExitSP(int x, int y)
9800 if (game.gems_still_needed > 0)
9802 int element = Tile[x][y];
9803 int graphic = el2img(element);
9805 if (IS_ANIMATED(graphic))
9806 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9811 // do not re-open exit door closed after last player
9812 if (game.all_players_gone)
9815 Tile[x][y] = EL_SP_EXIT_OPENING;
9817 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9820 static void CloseAllOpenTimegates(void)
9824 SCAN_PLAYFIELD(x, y)
9826 int element = Tile[x][y];
9828 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9830 Tile[x][y] = EL_TIMEGATE_CLOSING;
9832 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9837 static void DrawTwinkleOnField(int x, int y)
9839 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9842 if (Tile[x][y] == EL_BD_DIAMOND)
9845 if (MovDelay[x][y] == 0) // next animation frame
9846 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9848 if (MovDelay[x][y] != 0) // wait some time before next frame
9852 DrawLevelElementAnimation(x, y, Tile[x][y]);
9854 if (MovDelay[x][y] != 0)
9856 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9857 10 - MovDelay[x][y]);
9859 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9864 static void WallGrowing(int x, int y)
9868 if (!MovDelay[x][y]) // next animation frame
9869 MovDelay[x][y] = 3 * delay;
9871 if (MovDelay[x][y]) // wait some time before next frame
9875 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9877 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9878 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9880 DrawLevelGraphic(x, y, graphic, frame);
9883 if (!MovDelay[x][y])
9885 if (MovDir[x][y] == MV_LEFT)
9887 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9888 TEST_DrawLevelField(x - 1, y);
9890 else if (MovDir[x][y] == MV_RIGHT)
9892 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9893 TEST_DrawLevelField(x + 1, y);
9895 else if (MovDir[x][y] == MV_UP)
9897 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9898 TEST_DrawLevelField(x, y - 1);
9902 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9903 TEST_DrawLevelField(x, y + 1);
9906 Tile[x][y] = Store[x][y];
9908 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9909 TEST_DrawLevelField(x, y);
9914 static void CheckWallGrowing(int ax, int ay)
9916 int element = Tile[ax][ay];
9917 int graphic = el2img(element);
9918 boolean free_top = FALSE;
9919 boolean free_bottom = FALSE;
9920 boolean free_left = FALSE;
9921 boolean free_right = FALSE;
9922 boolean stop_top = FALSE;
9923 boolean stop_bottom = FALSE;
9924 boolean stop_left = FALSE;
9925 boolean stop_right = FALSE;
9926 boolean new_wall = FALSE;
9928 boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9929 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9930 element == EL_EXPANDABLE_STEELWALL_ANY);
9932 boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9933 element == EL_EXPANDABLE_WALL_ANY ||
9934 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9935 element == EL_EXPANDABLE_STEELWALL_ANY);
9937 boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9938 element == EL_EXPANDABLE_WALL_ANY ||
9939 element == EL_EXPANDABLE_WALL ||
9940 element == EL_BD_EXPANDABLE_WALL ||
9941 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9942 element == EL_EXPANDABLE_STEELWALL_ANY);
9944 boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9945 element == EL_EXPANDABLE_STEELWALL_VERTICAL);
9947 boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9948 element == EL_EXPANDABLE_WALL ||
9949 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL);
9951 int wall_growing = (is_steelwall ?
9952 EL_EXPANDABLE_STEELWALL_GROWING :
9953 EL_EXPANDABLE_WALL_GROWING);
9955 int gfx_wall_growing_up = (is_steelwall ?
9956 IMG_EXPANDABLE_STEELWALL_GROWING_UP :
9957 IMG_EXPANDABLE_WALL_GROWING_UP);
9958 int gfx_wall_growing_down = (is_steelwall ?
9959 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN :
9960 IMG_EXPANDABLE_WALL_GROWING_DOWN);
9961 int gfx_wall_growing_left = (is_steelwall ?
9962 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT :
9963 IMG_EXPANDABLE_WALL_GROWING_LEFT);
9964 int gfx_wall_growing_right = (is_steelwall ?
9965 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT :
9966 IMG_EXPANDABLE_WALL_GROWING_RIGHT);
9968 if (IS_ANIMATED(graphic))
9969 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9971 if (!MovDelay[ax][ay]) // start building new wall
9972 MovDelay[ax][ay] = 6;
9974 if (MovDelay[ax][ay]) // wait some time before building new wall
9977 if (MovDelay[ax][ay])
9981 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9983 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9985 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9987 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9994 Tile[ax][ay - 1] = wall_growing;
9995 Store[ax][ay - 1] = element;
9996 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9998 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9999 DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0);
10006 Tile[ax][ay + 1] = wall_growing;
10007 Store[ax][ay + 1] = element;
10008 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
10010 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
10011 DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0);
10017 if (grow_horizontal)
10021 Tile[ax - 1][ay] = wall_growing;
10022 Store[ax - 1][ay] = element;
10023 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
10025 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
10026 DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0);
10033 Tile[ax + 1][ay] = wall_growing;
10034 Store[ax + 1][ay] = element;
10035 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
10037 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
10038 DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0);
10044 if (element == EL_EXPANDABLE_WALL && (free_left || free_right))
10045 TEST_DrawLevelField(ax, ay);
10047 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
10049 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
10050 stop_bottom = TRUE;
10051 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
10053 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
10056 if (((stop_top && stop_bottom) || stop_horizontal) &&
10057 ((stop_left && stop_right) || stop_vertical))
10058 Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL);
10061 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
10064 static void CheckForDragon(int x, int y)
10067 boolean dragon_found = FALSE;
10068 struct XY *xy = xy_topdown;
10070 for (i = 0; i < NUM_DIRECTIONS; i++)
10072 for (j = 0; j < 4; j++)
10074 int xx = x + j * xy[i].x;
10075 int yy = y + j * xy[i].y;
10077 if (IN_LEV_FIELD(xx, yy) &&
10078 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
10080 if (Tile[xx][yy] == EL_DRAGON)
10081 dragon_found = TRUE;
10090 for (i = 0; i < NUM_DIRECTIONS; i++)
10092 for (j = 0; j < 3; j++)
10094 int xx = x + j * xy[i].x;
10095 int yy = y + j * xy[i].y;
10097 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
10099 Tile[xx][yy] = EL_EMPTY;
10100 TEST_DrawLevelField(xx, yy);
10109 static void InitBuggyBase(int x, int y)
10111 int element = Tile[x][y];
10112 int activating_delay = FRAMES_PER_SECOND / 4;
10114 ChangeDelay[x][y] =
10115 (element == EL_SP_BUGGY_BASE ?
10116 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10117 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10119 element == EL_SP_BUGGY_BASE_ACTIVE ?
10120 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10123 static void WarnBuggyBase(int x, int y)
10126 struct XY *xy = xy_topdown;
10128 for (i = 0; i < NUM_DIRECTIONS; i++)
10130 int xx = x + xy[i].x;
10131 int yy = y + xy[i].y;
10133 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10135 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10142 static void InitTrap(int x, int y)
10144 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10147 static void ActivateTrap(int x, int y)
10149 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10152 static void ChangeActiveTrap(int x, int y)
10154 int graphic = IMG_TRAP_ACTIVE;
10156 // if new animation frame was drawn, correct crumbled sand border
10157 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10158 TEST_DrawLevelFieldCrumbled(x, y);
10161 static int getSpecialActionElement(int element, int number, int base_element)
10163 return (element != EL_EMPTY ? element :
10164 number != -1 ? base_element + number - 1 :
10168 static int getModifiedActionNumber(int value_old, int operator, int operand,
10169 int value_min, int value_max)
10171 int value_new = (operator == CA_MODE_SET ? operand :
10172 operator == CA_MODE_ADD ? value_old + operand :
10173 operator == CA_MODE_SUBTRACT ? value_old - operand :
10174 operator == CA_MODE_MULTIPLY ? value_old * operand :
10175 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10176 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10179 return (value_new < value_min ? value_min :
10180 value_new > value_max ? value_max :
10184 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10186 struct ElementInfo *ei = &element_info[element];
10187 struct ElementChangeInfo *change = &ei->change_page[page];
10188 int target_element = change->target_element;
10189 int action_type = change->action_type;
10190 int action_mode = change->action_mode;
10191 int action_arg = change->action_arg;
10192 int action_element = change->action_element;
10195 if (!change->has_action)
10198 // ---------- determine action paramater values -----------------------------
10200 int level_time_value =
10201 (level.time > 0 ? TimeLeft :
10204 int action_arg_element_raw =
10205 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10206 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10207 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10208 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10209 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10210 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10211 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10213 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10215 int action_arg_direction =
10216 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10217 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10218 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10219 change->actual_trigger_side :
10220 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10221 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10224 int action_arg_number_min =
10225 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10228 int action_arg_number_max =
10229 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10230 action_type == CA_SET_LEVEL_GEMS ? 999 :
10231 action_type == CA_SET_LEVEL_TIME ? 9999 :
10232 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10233 action_type == CA_SET_CE_VALUE ? 9999 :
10234 action_type == CA_SET_CE_SCORE ? 9999 :
10237 int action_arg_number_reset =
10238 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10239 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10240 action_type == CA_SET_LEVEL_TIME ? level.time :
10241 action_type == CA_SET_LEVEL_SCORE ? 0 :
10242 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10243 action_type == CA_SET_CE_SCORE ? 0 :
10246 int action_arg_number =
10247 (action_arg <= CA_ARG_MAX ? action_arg :
10248 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10249 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10250 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10251 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10252 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10253 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10254 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10255 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10256 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10257 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10258 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10259 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10260 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10261 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10262 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10263 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10264 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10265 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10266 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10267 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10268 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10271 int action_arg_number_old =
10272 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10273 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10274 action_type == CA_SET_LEVEL_SCORE ? game.score :
10275 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10276 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10279 int action_arg_number_new =
10280 getModifiedActionNumber(action_arg_number_old,
10281 action_mode, action_arg_number,
10282 action_arg_number_min, action_arg_number_max);
10284 int trigger_player_bits =
10285 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10286 change->actual_trigger_player_bits : change->trigger_player);
10288 int action_arg_player_bits =
10289 (action_arg >= CA_ARG_PLAYER_1 &&
10290 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10291 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10292 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10295 // ---------- execute action -----------------------------------------------
10297 switch (action_type)
10304 // ---------- level actions ----------------------------------------------
10306 case CA_RESTART_LEVEL:
10308 game.restart_level = TRUE;
10313 case CA_SHOW_ENVELOPE:
10315 int element = getSpecialActionElement(action_arg_element,
10316 action_arg_number, EL_ENVELOPE_1);
10318 if (IS_ENVELOPE(element))
10319 local_player->show_envelope = element;
10324 case CA_SET_LEVEL_TIME:
10326 if (level.time > 0) // only modify limited time value
10328 TimeLeft = action_arg_number_new;
10330 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10332 DisplayGameControlValues();
10334 if (!TimeLeft && game.time_limit)
10335 for (i = 0; i < MAX_PLAYERS; i++)
10336 KillPlayer(&stored_player[i]);
10342 case CA_SET_LEVEL_SCORE:
10344 game.score = action_arg_number_new;
10346 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10348 DisplayGameControlValues();
10353 case CA_SET_LEVEL_GEMS:
10355 game.gems_still_needed = action_arg_number_new;
10357 game.snapshot.collected_item = TRUE;
10359 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10361 DisplayGameControlValues();
10366 case CA_SET_LEVEL_WIND:
10368 game.wind_direction = action_arg_direction;
10373 case CA_SET_LEVEL_RANDOM_SEED:
10375 // ensure that setting a new random seed while playing is predictable
10376 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10381 // ---------- player actions ---------------------------------------------
10383 case CA_MOVE_PLAYER:
10384 case CA_MOVE_PLAYER_NEW:
10386 // automatically move to the next field in specified direction
10387 for (i = 0; i < MAX_PLAYERS; i++)
10388 if (trigger_player_bits & (1 << i))
10389 if (action_type == CA_MOVE_PLAYER ||
10390 stored_player[i].MovPos == 0)
10391 stored_player[i].programmed_action = action_arg_direction;
10396 case CA_EXIT_PLAYER:
10398 for (i = 0; i < MAX_PLAYERS; i++)
10399 if (action_arg_player_bits & (1 << i))
10400 ExitPlayer(&stored_player[i]);
10402 if (game.players_still_needed == 0)
10408 case CA_KILL_PLAYER:
10410 for (i = 0; i < MAX_PLAYERS; i++)
10411 if (action_arg_player_bits & (1 << i))
10412 KillPlayer(&stored_player[i]);
10417 case CA_SET_PLAYER_KEYS:
10419 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10420 int element = getSpecialActionElement(action_arg_element,
10421 action_arg_number, EL_KEY_1);
10423 if (IS_KEY(element))
10425 for (i = 0; i < MAX_PLAYERS; i++)
10427 if (trigger_player_bits & (1 << i))
10429 stored_player[i].key[KEY_NR(element)] = key_state;
10431 DrawGameDoorValues();
10439 case CA_SET_PLAYER_SPEED:
10441 for (i = 0; i < MAX_PLAYERS; i++)
10443 if (trigger_player_bits & (1 << i))
10445 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10447 if (action_arg == CA_ARG_SPEED_FASTER &&
10448 stored_player[i].cannot_move)
10450 action_arg_number = STEPSIZE_VERY_SLOW;
10452 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10453 action_arg == CA_ARG_SPEED_FASTER)
10455 action_arg_number = 2;
10456 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10459 else if (action_arg == CA_ARG_NUMBER_RESET)
10461 action_arg_number = level.initial_player_stepsize[i];
10465 getModifiedActionNumber(move_stepsize,
10468 action_arg_number_min,
10469 action_arg_number_max);
10471 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10478 case CA_SET_PLAYER_SHIELD:
10480 for (i = 0; i < MAX_PLAYERS; i++)
10482 if (trigger_player_bits & (1 << i))
10484 if (action_arg == CA_ARG_SHIELD_OFF)
10486 stored_player[i].shield_normal_time_left = 0;
10487 stored_player[i].shield_deadly_time_left = 0;
10489 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10491 stored_player[i].shield_normal_time_left = 999999;
10493 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10495 stored_player[i].shield_normal_time_left = 999999;
10496 stored_player[i].shield_deadly_time_left = 999999;
10504 case CA_SET_PLAYER_GRAVITY:
10506 for (i = 0; i < MAX_PLAYERS; i++)
10508 if (trigger_player_bits & (1 << i))
10510 stored_player[i].gravity =
10511 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10512 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10513 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10514 stored_player[i].gravity);
10521 case CA_SET_PLAYER_ARTWORK:
10523 for (i = 0; i < MAX_PLAYERS; i++)
10525 if (trigger_player_bits & (1 << i))
10527 int artwork_element = action_arg_element;
10529 if (action_arg == CA_ARG_ELEMENT_RESET)
10531 (level.use_artwork_element[i] ? level.artwork_element[i] :
10532 stored_player[i].element_nr);
10534 if (stored_player[i].artwork_element != artwork_element)
10535 stored_player[i].Frame = 0;
10537 stored_player[i].artwork_element = artwork_element;
10539 SetPlayerWaiting(&stored_player[i], FALSE);
10541 // set number of special actions for bored and sleeping animation
10542 stored_player[i].num_special_action_bored =
10543 get_num_special_action(artwork_element,
10544 ACTION_BORING_1, ACTION_BORING_LAST);
10545 stored_player[i].num_special_action_sleeping =
10546 get_num_special_action(artwork_element,
10547 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10554 case CA_SET_PLAYER_INVENTORY:
10556 for (i = 0; i < MAX_PLAYERS; i++)
10558 struct PlayerInfo *player = &stored_player[i];
10561 if (trigger_player_bits & (1 << i))
10563 int inventory_element = action_arg_element;
10565 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10566 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10567 action_arg == CA_ARG_ELEMENT_ACTION)
10569 int element = inventory_element;
10570 int collect_count = element_info[element].collect_count_initial;
10572 if (!IS_CUSTOM_ELEMENT(element))
10575 if (collect_count == 0)
10576 player->inventory_infinite_element = element;
10578 for (k = 0; k < collect_count; k++)
10579 if (player->inventory_size < MAX_INVENTORY_SIZE)
10580 player->inventory_element[player->inventory_size++] =
10583 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10584 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10585 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10587 if (player->inventory_infinite_element != EL_UNDEFINED &&
10588 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10589 action_arg_element_raw))
10590 player->inventory_infinite_element = EL_UNDEFINED;
10592 for (k = 0, j = 0; j < player->inventory_size; j++)
10594 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10595 action_arg_element_raw))
10596 player->inventory_element[k++] = player->inventory_element[j];
10599 player->inventory_size = k;
10601 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10603 if (player->inventory_size > 0)
10605 for (j = 0; j < player->inventory_size - 1; j++)
10606 player->inventory_element[j] = player->inventory_element[j + 1];
10608 player->inventory_size--;
10611 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10613 if (player->inventory_size > 0)
10614 player->inventory_size--;
10616 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10618 player->inventory_infinite_element = EL_UNDEFINED;
10619 player->inventory_size = 0;
10621 else if (action_arg == CA_ARG_INVENTORY_RESET)
10623 player->inventory_infinite_element = EL_UNDEFINED;
10624 player->inventory_size = 0;
10626 if (level.use_initial_inventory[i])
10628 for (j = 0; j < level.initial_inventory_size[i]; j++)
10630 int element = level.initial_inventory_content[i][j];
10631 int collect_count = element_info[element].collect_count_initial;
10633 if (!IS_CUSTOM_ELEMENT(element))
10636 if (collect_count == 0)
10637 player->inventory_infinite_element = element;
10639 for (k = 0; k < collect_count; k++)
10640 if (player->inventory_size < MAX_INVENTORY_SIZE)
10641 player->inventory_element[player->inventory_size++] =
10652 // ---------- CE actions -------------------------------------------------
10654 case CA_SET_CE_VALUE:
10656 int last_ce_value = CustomValue[x][y];
10658 CustomValue[x][y] = action_arg_number_new;
10660 if (CustomValue[x][y] != last_ce_value)
10662 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10663 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10665 if (CustomValue[x][y] == 0)
10667 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10668 ChangeCount[x][y] = 0; // allow at least one more change
10670 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10671 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10678 case CA_SET_CE_SCORE:
10680 int last_ce_score = ei->collect_score;
10682 ei->collect_score = action_arg_number_new;
10684 if (ei->collect_score != last_ce_score)
10686 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10687 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10689 if (ei->collect_score == 0)
10693 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10694 ChangeCount[x][y] = 0; // allow at least one more change
10696 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10697 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10700 This is a very special case that seems to be a mixture between
10701 CheckElementChange() and CheckTriggeredElementChange(): while
10702 the first one only affects single elements that are triggered
10703 directly, the second one affects multiple elements in the playfield
10704 that are triggered indirectly by another element. This is a third
10705 case: Changing the CE score always affects multiple identical CEs,
10706 so every affected CE must be checked, not only the single CE for
10707 which the CE score was changed in the first place (as every instance
10708 of that CE shares the same CE score, and therefore also can change)!
10710 SCAN_PLAYFIELD(xx, yy)
10712 if (Tile[xx][yy] == element)
10713 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10714 CE_SCORE_GETS_ZERO);
10722 case CA_SET_CE_ARTWORK:
10724 int artwork_element = action_arg_element;
10725 boolean reset_frame = FALSE;
10728 if (action_arg == CA_ARG_ELEMENT_RESET)
10729 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10732 if (ei->gfx_element != artwork_element)
10733 reset_frame = TRUE;
10735 ei->gfx_element = artwork_element;
10737 SCAN_PLAYFIELD(xx, yy)
10739 if (Tile[xx][yy] == element)
10743 ResetGfxAnimation(xx, yy);
10744 ResetRandomAnimationValue(xx, yy);
10747 TEST_DrawLevelField(xx, yy);
10754 // ---------- engine actions ---------------------------------------------
10756 case CA_SET_ENGINE_SCAN_MODE:
10758 InitPlayfieldScanMode(action_arg);
10768 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10770 int old_element = Tile[x][y];
10771 int new_element = GetElementFromGroupElement(element);
10772 int previous_move_direction = MovDir[x][y];
10773 int last_ce_value = CustomValue[x][y];
10774 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10775 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10776 boolean add_player_onto_element = (new_element_is_player &&
10777 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10778 IS_WALKABLE(old_element));
10780 if (!add_player_onto_element)
10782 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10783 RemoveMovingField(x, y);
10787 Tile[x][y] = new_element;
10789 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10790 MovDir[x][y] = previous_move_direction;
10792 if (element_info[new_element].use_last_ce_value)
10793 CustomValue[x][y] = last_ce_value;
10795 InitField_WithBug1(x, y, FALSE);
10797 new_element = Tile[x][y]; // element may have changed
10799 ResetGfxAnimation(x, y);
10800 ResetRandomAnimationValue(x, y);
10802 TEST_DrawLevelField(x, y);
10804 if (GFX_CRUMBLED(new_element))
10805 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10807 if (old_element == EL_EXPLOSION)
10809 Store[x][y] = Store2[x][y] = 0;
10811 // check if new element replaces an exploding player, requiring cleanup
10812 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
10813 StorePlayer[x][y] = 0;
10816 // check if element under the player changes from accessible to unaccessible
10817 // (needed for special case of dropping element which then changes)
10818 // (must be checked after creating new element for walkable group elements)
10819 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10820 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10822 KillPlayer(PLAYERINFO(x, y));
10828 // "ChangeCount" not set yet to allow "entered by player" change one time
10829 if (new_element_is_player)
10830 RelocatePlayer(x, y, new_element);
10833 ChangeCount[x][y]++; // count number of changes in the same frame
10835 TestIfBadThingTouchesPlayer(x, y);
10836 TestIfPlayerTouchesCustomElement(x, y);
10837 TestIfElementTouchesCustomElement(x, y);
10840 static void CreateField(int x, int y, int element)
10842 CreateFieldExt(x, y, element, FALSE);
10845 static void CreateElementFromChange(int x, int y, int element)
10847 element = GET_VALID_RUNTIME_ELEMENT(element);
10849 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10851 int old_element = Tile[x][y];
10853 // prevent changed element from moving in same engine frame
10854 // unless both old and new element can either fall or move
10855 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10856 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10860 CreateFieldExt(x, y, element, TRUE);
10863 static boolean ChangeElement(int x, int y, int element, int page)
10865 struct ElementInfo *ei = &element_info[element];
10866 struct ElementChangeInfo *change = &ei->change_page[page];
10867 int ce_value = CustomValue[x][y];
10868 int ce_score = ei->collect_score;
10869 int target_element;
10870 int old_element = Tile[x][y];
10872 // always use default change event to prevent running into a loop
10873 if (ChangeEvent[x][y] == -1)
10874 ChangeEvent[x][y] = CE_DELAY;
10876 if (ChangeEvent[x][y] == CE_DELAY)
10878 // reset actual trigger element, trigger player and action element
10879 change->actual_trigger_element = EL_EMPTY;
10880 change->actual_trigger_player = EL_EMPTY;
10881 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10882 change->actual_trigger_side = CH_SIDE_NONE;
10883 change->actual_trigger_ce_value = 0;
10884 change->actual_trigger_ce_score = 0;
10885 change->actual_trigger_x = -1;
10886 change->actual_trigger_y = -1;
10889 // do not change elements more than a specified maximum number of changes
10890 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10893 ChangeCount[x][y]++; // count number of changes in the same frame
10895 if (ei->has_anim_event)
10896 HandleGlobalAnimEventByElementChange(element, page, x, y,
10897 change->actual_trigger_x,
10898 change->actual_trigger_y);
10900 if (change->explode)
10907 if (change->use_target_content)
10909 boolean complete_replace = TRUE;
10910 boolean can_replace[3][3];
10913 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10916 boolean is_walkable;
10917 boolean is_diggable;
10918 boolean is_collectible;
10919 boolean is_removable;
10920 boolean is_destructible;
10921 int ex = x + xx - 1;
10922 int ey = y + yy - 1;
10923 int content_element = change->target_content.e[xx][yy];
10926 can_replace[xx][yy] = TRUE;
10928 if (ex == x && ey == y) // do not check changing element itself
10931 if (content_element == EL_EMPTY_SPACE)
10933 can_replace[xx][yy] = FALSE; // do not replace border with space
10938 if (!IN_LEV_FIELD(ex, ey))
10940 can_replace[xx][yy] = FALSE;
10941 complete_replace = FALSE;
10948 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10949 e = MovingOrBlocked2Element(ex, ey);
10951 is_empty = (IS_FREE(ex, ey) ||
10952 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10954 is_walkable = (is_empty || IS_WALKABLE(e));
10955 is_diggable = (is_empty || IS_DIGGABLE(e));
10956 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10957 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10958 is_removable = (is_diggable || is_collectible);
10960 can_replace[xx][yy] =
10961 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10962 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10963 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10964 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10965 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10966 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10967 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10969 if (!can_replace[xx][yy])
10970 complete_replace = FALSE;
10973 if (!change->only_if_complete || complete_replace)
10975 boolean something_has_changed = FALSE;
10977 if (change->only_if_complete && change->use_random_replace &&
10978 RND(100) < change->random_percentage)
10981 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10983 int ex = x + xx - 1;
10984 int ey = y + yy - 1;
10985 int content_element;
10987 if (can_replace[xx][yy] && (!change->use_random_replace ||
10988 RND(100) < change->random_percentage))
10990 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10991 RemoveMovingField(ex, ey);
10993 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10995 content_element = change->target_content.e[xx][yy];
10996 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10997 ce_value, ce_score);
10999 CreateElementFromChange(ex, ey, target_element);
11001 something_has_changed = TRUE;
11003 // for symmetry reasons, freeze newly created border elements
11004 if (ex != x || ey != y)
11005 Stop[ex][ey] = TRUE; // no more moving in this frame
11009 if (something_has_changed)
11011 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
11012 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
11018 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
11019 ce_value, ce_score);
11021 if (element == EL_DIAGONAL_GROWING ||
11022 element == EL_DIAGONAL_SHRINKING)
11024 target_element = Store[x][y];
11026 Store[x][y] = EL_EMPTY;
11029 // special case: element changes to player (and may be kept if walkable)
11030 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
11031 CreateElementFromChange(x, y, EL_EMPTY);
11033 CreateElementFromChange(x, y, target_element);
11035 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
11036 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
11039 // this uses direct change before indirect change
11040 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
11045 static void HandleElementChange(int x, int y, int page)
11047 int element = MovingOrBlocked2Element(x, y);
11048 struct ElementInfo *ei = &element_info[element];
11049 struct ElementChangeInfo *change = &ei->change_page[page];
11050 boolean handle_action_before_change = FALSE;
11053 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
11054 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
11056 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
11057 x, y, element, element_info[element].token_name);
11058 Debug("game:playing:HandleElementChange", "This should never happen!");
11062 // this can happen with classic bombs on walkable, changing elements
11063 if (!CAN_CHANGE_OR_HAS_ACTION(element))
11068 if (ChangeDelay[x][y] == 0) // initialize element change
11070 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
11072 if (change->can_change)
11074 // !!! not clear why graphic animation should be reset at all here !!!
11075 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
11076 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
11079 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
11081 When using an animation frame delay of 1 (this only happens with
11082 "sp_zonk.moving.left/right" in the classic graphics), the default
11083 (non-moving) animation shows wrong animation frames (while the
11084 moving animation, like "sp_zonk.moving.left/right", is correct,
11085 so this graphical bug never shows up with the classic graphics).
11086 For an animation with 4 frames, this causes wrong frames 0,0,1,2
11087 be drawn instead of the correct frames 0,1,2,3. This is caused by
11088 "GfxFrame[][]" being reset *twice* (in two successive frames) after
11089 an element change: First when the change delay ("ChangeDelay[][]")
11090 counter has reached zero after decrementing, then a second time in
11091 the next frame (after "GfxFrame[][]" was already incremented) when
11092 "ChangeDelay[][]" is reset to the initial delay value again.
11094 This causes frame 0 to be drawn twice, while the last frame won't
11095 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
11097 As some animations may already be cleverly designed around this bug
11098 (at least the "Snake Bite" snake tail animation does this), it cannot
11099 simply be fixed here without breaking such existing animations.
11100 Unfortunately, it cannot easily be detected if a graphics set was
11101 designed "before" or "after" the bug was fixed. As a workaround,
11102 a new graphics set option "game.graphics_engine_version" was added
11103 to be able to specify the game's major release version for which the
11104 graphics set was designed, which can then be used to decide if the
11105 bugfix should be used (version 4 and above) or not (version 3 or
11106 below, or if no version was specified at all, as with old sets).
11108 (The wrong/fixed animation frames can be tested with the test level set
11109 "test_gfxframe" and level "000", which contains a specially prepared
11110 custom element at level position (x/y) == (11/9) which uses the zonk
11111 animation mentioned above. Using "game.graphics_engine_version: 4"
11112 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
11113 This can also be seen from the debug output for this test element.)
11116 // when a custom element is about to change (for example by change delay),
11117 // do not reset graphic animation when the custom element is moving
11118 if (game.graphics_engine_version < 4 &&
11121 ResetGfxAnimation(x, y);
11122 ResetRandomAnimationValue(x, y);
11125 if (change->pre_change_function)
11126 change->pre_change_function(x, y);
11130 ChangeDelay[x][y]--;
11132 if (ChangeDelay[x][y] != 0) // continue element change
11134 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11136 // also needed if CE can not change, but has CE delay with CE action
11137 if (IS_ANIMATED(graphic))
11138 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11140 if (change->can_change)
11142 if (change->change_function)
11143 change->change_function(x, y);
11146 else // finish element change
11148 if (ChangePage[x][y] != -1) // remember page from delayed change
11150 page = ChangePage[x][y];
11151 ChangePage[x][y] = -1;
11153 change = &ei->change_page[page];
11156 if (IS_MOVING(x, y)) // never change a running system ;-)
11158 ChangeDelay[x][y] = 1; // try change after next move step
11159 ChangePage[x][y] = page; // remember page to use for change
11164 // special case: set new level random seed before changing element
11165 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11166 handle_action_before_change = TRUE;
11168 if (change->has_action && handle_action_before_change)
11169 ExecuteCustomElementAction(x, y, element, page);
11171 if (change->can_change)
11173 if (ChangeElement(x, y, element, page))
11175 if (change->post_change_function)
11176 change->post_change_function(x, y);
11180 if (change->has_action && !handle_action_before_change)
11181 ExecuteCustomElementAction(x, y, element, page);
11185 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11186 int trigger_element,
11188 int trigger_player,
11192 boolean change_done_any = FALSE;
11193 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11196 if (!(trigger_events[trigger_element][trigger_event]))
11199 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11201 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11203 int element = EL_CUSTOM_START + i;
11204 boolean change_done = FALSE;
11207 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11208 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11211 for (p = 0; p < element_info[element].num_change_pages; p++)
11213 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11215 if (change->can_change_or_has_action &&
11216 change->has_event[trigger_event] &&
11217 change->trigger_side & trigger_side &&
11218 change->trigger_player & trigger_player &&
11219 change->trigger_page & trigger_page_bits &&
11220 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11222 change->actual_trigger_element = trigger_element;
11223 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11224 change->actual_trigger_player_bits = trigger_player;
11225 change->actual_trigger_side = trigger_side;
11226 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11227 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11228 change->actual_trigger_x = trigger_x;
11229 change->actual_trigger_y = trigger_y;
11231 if ((change->can_change && !change_done) || change->has_action)
11235 SCAN_PLAYFIELD(x, y)
11237 if (Tile[x][y] == element)
11239 if (change->can_change && !change_done)
11241 // if element already changed in this frame, not only prevent
11242 // another element change (checked in ChangeElement()), but
11243 // also prevent additional element actions for this element
11245 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11246 !level.use_action_after_change_bug)
11249 ChangeDelay[x][y] = 1;
11250 ChangeEvent[x][y] = trigger_event;
11252 HandleElementChange(x, y, p);
11254 else if (change->has_action)
11256 // if element already changed in this frame, not only prevent
11257 // another element change (checked in ChangeElement()), but
11258 // also prevent additional element actions for this element
11260 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11261 !level.use_action_after_change_bug)
11264 ExecuteCustomElementAction(x, y, element, p);
11265 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11270 if (change->can_change)
11272 change_done = TRUE;
11273 change_done_any = TRUE;
11280 RECURSION_LOOP_DETECTION_END();
11282 return change_done_any;
11285 static boolean CheckElementChangeExt(int x, int y,
11287 int trigger_element,
11289 int trigger_player,
11292 boolean change_done = FALSE;
11295 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11296 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11299 if (Tile[x][y] == EL_BLOCKED)
11301 Blocked2Moving(x, y, &x, &y);
11302 element = Tile[x][y];
11305 // check if element has already changed or is about to change after moving
11306 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11307 Tile[x][y] != element) ||
11309 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11310 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11311 ChangePage[x][y] != -1)))
11314 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11316 for (p = 0; p < element_info[element].num_change_pages; p++)
11318 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11320 /* check trigger element for all events where the element that is checked
11321 for changing interacts with a directly adjacent element -- this is
11322 different to element changes that affect other elements to change on the
11323 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11324 boolean check_trigger_element =
11325 (trigger_event == CE_NEXT_TO_X ||
11326 trigger_event == CE_TOUCHING_X ||
11327 trigger_event == CE_HITTING_X ||
11328 trigger_event == CE_HIT_BY_X ||
11329 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11331 if (change->can_change_or_has_action &&
11332 change->has_event[trigger_event] &&
11333 change->trigger_side & trigger_side &&
11334 change->trigger_player & trigger_player &&
11335 (!check_trigger_element ||
11336 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11338 change->actual_trigger_element = trigger_element;
11339 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11340 change->actual_trigger_player_bits = trigger_player;
11341 change->actual_trigger_side = trigger_side;
11342 change->actual_trigger_ce_value = CustomValue[x][y];
11343 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11344 change->actual_trigger_x = x;
11345 change->actual_trigger_y = y;
11347 // special case: trigger element not at (x,y) position for some events
11348 if (check_trigger_element)
11360 { 0, 0 }, { 0, 0 }, { 0, 0 },
11364 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11365 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11367 change->actual_trigger_ce_value = CustomValue[xx][yy];
11368 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11369 change->actual_trigger_x = xx;
11370 change->actual_trigger_y = yy;
11373 if (change->can_change && !change_done)
11375 ChangeDelay[x][y] = 1;
11376 ChangeEvent[x][y] = trigger_event;
11378 HandleElementChange(x, y, p);
11380 change_done = TRUE;
11382 else if (change->has_action)
11384 ExecuteCustomElementAction(x, y, element, p);
11385 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11390 RECURSION_LOOP_DETECTION_END();
11392 return change_done;
11395 static void PlayPlayerSound(struct PlayerInfo *player)
11397 int jx = player->jx, jy = player->jy;
11398 int sound_element = player->artwork_element;
11399 int last_action = player->last_action_waiting;
11400 int action = player->action_waiting;
11402 if (player->is_waiting)
11404 if (action != last_action)
11405 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11407 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11411 if (action != last_action)
11412 StopSound(element_info[sound_element].sound[last_action]);
11414 if (last_action == ACTION_SLEEPING)
11415 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11419 static void PlayAllPlayersSound(void)
11423 for (i = 0; i < MAX_PLAYERS; i++)
11424 if (stored_player[i].active)
11425 PlayPlayerSound(&stored_player[i]);
11428 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11430 boolean last_waiting = player->is_waiting;
11431 int move_dir = player->MovDir;
11433 player->dir_waiting = move_dir;
11434 player->last_action_waiting = player->action_waiting;
11438 if (!last_waiting) // not waiting -> waiting
11440 player->is_waiting = TRUE;
11442 player->frame_counter_bored =
11444 game.player_boring_delay_fixed +
11445 GetSimpleRandom(game.player_boring_delay_random);
11446 player->frame_counter_sleeping =
11448 game.player_sleeping_delay_fixed +
11449 GetSimpleRandom(game.player_sleeping_delay_random);
11451 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11454 if (game.player_sleeping_delay_fixed +
11455 game.player_sleeping_delay_random > 0 &&
11456 player->anim_delay_counter == 0 &&
11457 player->post_delay_counter == 0 &&
11458 FrameCounter >= player->frame_counter_sleeping)
11459 player->is_sleeping = TRUE;
11460 else if (game.player_boring_delay_fixed +
11461 game.player_boring_delay_random > 0 &&
11462 FrameCounter >= player->frame_counter_bored)
11463 player->is_bored = TRUE;
11465 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11466 player->is_bored ? ACTION_BORING :
11469 if (player->is_sleeping && player->use_murphy)
11471 // special case for sleeping Murphy when leaning against non-free tile
11473 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11474 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11475 !IS_MOVING(player->jx - 1, player->jy)))
11476 move_dir = MV_LEFT;
11477 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11478 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11479 !IS_MOVING(player->jx + 1, player->jy)))
11480 move_dir = MV_RIGHT;
11482 player->is_sleeping = FALSE;
11484 player->dir_waiting = move_dir;
11487 if (player->is_sleeping)
11489 if (player->num_special_action_sleeping > 0)
11491 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11493 int last_special_action = player->special_action_sleeping;
11494 int num_special_action = player->num_special_action_sleeping;
11495 int special_action =
11496 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11497 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11498 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11499 last_special_action + 1 : ACTION_SLEEPING);
11500 int special_graphic =
11501 el_act_dir2img(player->artwork_element, special_action, move_dir);
11503 player->anim_delay_counter =
11504 graphic_info[special_graphic].anim_delay_fixed +
11505 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11506 player->post_delay_counter =
11507 graphic_info[special_graphic].post_delay_fixed +
11508 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11510 player->special_action_sleeping = special_action;
11513 if (player->anim_delay_counter > 0)
11515 player->action_waiting = player->special_action_sleeping;
11516 player->anim_delay_counter--;
11518 else if (player->post_delay_counter > 0)
11520 player->post_delay_counter--;
11524 else if (player->is_bored)
11526 if (player->num_special_action_bored > 0)
11528 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11530 int special_action =
11531 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11532 int special_graphic =
11533 el_act_dir2img(player->artwork_element, special_action, move_dir);
11535 player->anim_delay_counter =
11536 graphic_info[special_graphic].anim_delay_fixed +
11537 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11538 player->post_delay_counter =
11539 graphic_info[special_graphic].post_delay_fixed +
11540 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11542 player->special_action_bored = special_action;
11545 if (player->anim_delay_counter > 0)
11547 player->action_waiting = player->special_action_bored;
11548 player->anim_delay_counter--;
11550 else if (player->post_delay_counter > 0)
11552 player->post_delay_counter--;
11557 else if (last_waiting) // waiting -> not waiting
11559 player->is_waiting = FALSE;
11560 player->is_bored = FALSE;
11561 player->is_sleeping = FALSE;
11563 player->frame_counter_bored = -1;
11564 player->frame_counter_sleeping = -1;
11566 player->anim_delay_counter = 0;
11567 player->post_delay_counter = 0;
11569 player->dir_waiting = player->MovDir;
11570 player->action_waiting = ACTION_DEFAULT;
11572 player->special_action_bored = ACTION_DEFAULT;
11573 player->special_action_sleeping = ACTION_DEFAULT;
11577 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11579 if ((!player->is_moving && player->was_moving) ||
11580 (player->MovPos == 0 && player->was_moving) ||
11581 (player->is_snapping && !player->was_snapping) ||
11582 (player->is_dropping && !player->was_dropping))
11584 if (!CheckSaveEngineSnapshotToList())
11587 player->was_moving = FALSE;
11588 player->was_snapping = TRUE;
11589 player->was_dropping = TRUE;
11593 if (player->is_moving)
11594 player->was_moving = TRUE;
11596 if (!player->is_snapping)
11597 player->was_snapping = FALSE;
11599 if (!player->is_dropping)
11600 player->was_dropping = FALSE;
11603 static struct MouseActionInfo mouse_action_last = { 0 };
11604 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11605 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11608 CheckSaveEngineSnapshotToList();
11610 mouse_action_last = mouse_action;
11613 static void CheckSingleStepMode(struct PlayerInfo *player)
11615 if (tape.single_step && tape.recording && !tape.pausing)
11617 // as it is called "single step mode", just return to pause mode when the
11618 // player stopped moving after one tile (or never starts moving at all)
11619 // (reverse logic needed here in case single step mode used in team mode)
11620 if (player->is_moving ||
11621 player->is_pushing ||
11622 player->is_dropping_pressed ||
11623 player->effective_mouse_action.button)
11624 game.enter_single_step_mode = FALSE;
11627 CheckSaveEngineSnapshot(player);
11630 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11632 int left = player_action & JOY_LEFT;
11633 int right = player_action & JOY_RIGHT;
11634 int up = player_action & JOY_UP;
11635 int down = player_action & JOY_DOWN;
11636 int button1 = player_action & JOY_BUTTON_1;
11637 int button2 = player_action & JOY_BUTTON_2;
11638 int dx = (left ? -1 : right ? 1 : 0);
11639 int dy = (up ? -1 : down ? 1 : 0);
11641 if (!player->active || tape.pausing)
11647 SnapField(player, dx, dy);
11651 DropElement(player);
11653 MovePlayer(player, dx, dy);
11656 CheckSingleStepMode(player);
11658 SetPlayerWaiting(player, FALSE);
11660 return player_action;
11664 // no actions for this player (no input at player's configured device)
11666 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11667 SnapField(player, 0, 0);
11668 CheckGravityMovementWhenNotMoving(player);
11670 if (player->MovPos == 0)
11671 SetPlayerWaiting(player, TRUE);
11673 if (player->MovPos == 0) // needed for tape.playing
11674 player->is_moving = FALSE;
11676 player->is_dropping = FALSE;
11677 player->is_dropping_pressed = FALSE;
11678 player->drop_pressed_delay = 0;
11680 CheckSingleStepMode(player);
11686 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11689 if (!tape.use_mouse_actions)
11692 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11693 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11694 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11697 static void SetTapeActionFromMouseAction(byte *tape_action,
11698 struct MouseActionInfo *mouse_action)
11700 if (!tape.use_mouse_actions)
11703 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11704 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11705 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11708 static void CheckLevelSolved(void)
11710 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11712 if (game_bd.level_solved &&
11713 !game_bd.game_over) // game won
11717 game_bd.game_over = TRUE;
11719 game.all_players_gone = TRUE;
11722 if (game_bd.game_over) // game lost
11723 game.all_players_gone = TRUE;
11725 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11727 if (game_em.level_solved &&
11728 !game_em.game_over) // game won
11732 game_em.game_over = TRUE;
11734 game.all_players_gone = TRUE;
11737 if (game_em.game_over) // game lost
11738 game.all_players_gone = TRUE;
11740 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11742 if (game_sp.level_solved &&
11743 !game_sp.game_over) // game won
11747 game_sp.game_over = TRUE;
11749 game.all_players_gone = TRUE;
11752 if (game_sp.game_over) // game lost
11753 game.all_players_gone = TRUE;
11755 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11757 if (game_mm.level_solved &&
11758 !game_mm.game_over) // game won
11762 game_mm.game_over = TRUE;
11764 game.all_players_gone = TRUE;
11767 if (game_mm.game_over) // game lost
11768 game.all_players_gone = TRUE;
11772 static void PlayTimeoutSound(int seconds_left)
11774 // will be played directly by BD engine (for classic bonus time sounds)
11775 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && checkBonusTime_BD())
11778 // try to use individual "running out of time" sound for each second left
11779 int sound = SND_GAME_RUNNING_OUT_OF_TIME_0 - seconds_left;
11781 // if special sound per second not defined, use default sound
11782 if (getSoundInfoEntryFilename(sound) == NULL)
11783 sound = SND_GAME_RUNNING_OUT_OF_TIME;
11785 // if out of time, but player still alive, play special "timeout" sound, if defined
11786 if (seconds_left == 0 && !checkGameFailed())
11787 if (getSoundInfoEntryFilename(SND_GAME_TIMEOUT) != NULL)
11788 sound = SND_GAME_TIMEOUT;
11793 static void CheckLevelTime_StepCounter(void)
11803 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11804 PlayTimeoutSound(TimeLeft);
11806 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11808 DisplayGameControlValues();
11810 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11811 for (i = 0; i < MAX_PLAYERS; i++)
11812 KillPlayer(&stored_player[i]);
11814 else if (game.no_level_time_limit && !game.all_players_gone)
11816 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11818 DisplayGameControlValues();
11822 static void CheckLevelTime(void)
11824 int frames_per_second = FRAMES_PER_SECOND;
11827 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11829 // level time may be running slower in native BD engine
11830 frames_per_second = getFramesPerSecond_BD();
11832 // if native engine time changed, force main engine time change
11833 if (getTimeLeft_BD() < TimeLeft)
11834 TimeFrames = frames_per_second;
11836 // if last second running, wait for native engine time to exactly reach zero
11837 if (getTimeLeft_BD() == 1 && TimeLeft == 1)
11838 TimeFrames = frames_per_second - 1;
11840 // needed to store final time after solving game (before counting down remaining time)
11841 SetTimeFrames_BD(TimePlayed * FRAMES_PER_SECOND + TimeFrames);
11844 if (TimeFrames >= frames_per_second)
11848 for (i = 0; i < MAX_PLAYERS; i++)
11850 struct PlayerInfo *player = &stored_player[i];
11852 if (SHIELD_ON(player))
11854 player->shield_normal_time_left--;
11856 if (player->shield_deadly_time_left > 0)
11857 player->shield_deadly_time_left--;
11861 if (!game.LevelSolved && !level.use_step_counter)
11869 if (TimeLeft <= 10 && game.time_limit)
11870 PlayTimeoutSound(TimeLeft);
11872 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11873 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11875 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11877 if (!TimeLeft && game.time_limit)
11879 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11881 if (game_bd.game->cave->player_state == GD_PL_LIVING)
11882 game_bd.game->cave->player_state = GD_PL_TIMEOUT;
11884 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11886 game_em.lev->killed_out_of_time = TRUE;
11890 for (i = 0; i < MAX_PLAYERS; i++)
11891 KillPlayer(&stored_player[i]);
11895 else if (game.no_level_time_limit && !game.all_players_gone)
11897 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11900 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11904 if (TapeTimeFrames >= FRAMES_PER_SECOND)
11906 TapeTimeFrames = 0;
11909 if (tape.recording || tape.playing)
11910 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11913 if (tape.recording || tape.playing)
11914 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11916 UpdateAndDisplayGameControlValues();
11919 void AdvanceFrameAndPlayerCounters(int player_nr)
11923 // handle game and tape time differently for native BD game engine
11925 // tape time is running in native BD engine even if player is not hatched yet
11926 if (!checkGameRunning())
11929 // advance frame counters (global frame counter and tape time frame counter)
11933 // level time is running in native BD engine after player is being hatched
11934 if (!checkGamePlaying())
11937 // advance time frame counter (used to control available time to solve level)
11940 // advance player counters (counters for move delay, move animation etc.)
11941 for (i = 0; i < MAX_PLAYERS; i++)
11943 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11944 int move_delay_value = stored_player[i].move_delay_value;
11945 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11947 if (!advance_player_counters) // not all players may be affected
11950 if (move_frames == 0) // less than one move per game frame
11952 int stepsize = TILEX / move_delay_value;
11953 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11954 int count = (stored_player[i].is_moving ?
11955 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11957 if (count % delay == 0)
11961 stored_player[i].Frame += move_frames;
11963 if (stored_player[i].MovPos != 0)
11964 stored_player[i].StepFrame += move_frames;
11966 if (stored_player[i].move_delay > 0)
11967 stored_player[i].move_delay--;
11969 // due to bugs in previous versions, counter must count up, not down
11970 if (stored_player[i].push_delay != -1)
11971 stored_player[i].push_delay++;
11973 if (stored_player[i].drop_delay > 0)
11974 stored_player[i].drop_delay--;
11976 if (stored_player[i].is_dropping_pressed)
11977 stored_player[i].drop_pressed_delay++;
11981 void AdvanceFrameCounter(void)
11986 void AdvanceGfxFrame(void)
11990 SCAN_PLAYFIELD(x, y)
11996 static void HandleMouseAction(struct MouseActionInfo *mouse_action,
11997 struct MouseActionInfo *mouse_action_last)
11999 if (mouse_action->button)
12001 int new_button = (mouse_action->button && mouse_action_last->button == 0);
12002 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button);
12003 int x = mouse_action->lx;
12004 int y = mouse_action->ly;
12005 int element = Tile[x][y];
12009 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12010 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12014 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12015 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12018 if (level.use_step_counter)
12020 boolean counted_click = FALSE;
12022 // element clicked that can change when clicked/pressed
12023 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
12024 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
12025 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
12026 counted_click = TRUE;
12028 // element clicked that can trigger change when clicked/pressed
12029 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
12030 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
12031 counted_click = TRUE;
12033 if (new_button && counted_click)
12034 CheckLevelTime_StepCounter();
12039 void StartGameActions(boolean init_network_game, boolean record_tape,
12042 unsigned int new_random_seed = InitRND(random_seed);
12045 TapeStartRecording(new_random_seed);
12047 if (setup.auto_pause_on_start && !tape.pausing)
12048 TapeTogglePause(TAPE_TOGGLE_MANUAL);
12050 if (init_network_game)
12052 SendToServer_LevelFile();
12053 SendToServer_StartPlaying();
12061 static void GameActionsExt(void)
12064 static unsigned int game_frame_delay = 0;
12066 unsigned int game_frame_delay_value;
12067 byte *recorded_player_action;
12068 byte summarized_player_action = 0;
12069 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
12072 // detect endless loops, caused by custom element programming
12073 if (recursion_loop_detected && recursion_loop_depth == 0)
12075 char *message = getStringCat3("Internal Error! Element ",
12076 EL_NAME(recursion_loop_element),
12077 " caused endless loop! Quit the game?");
12079 Warn("element '%s' caused endless loop in game engine",
12080 EL_NAME(recursion_loop_element));
12082 RequestQuitGameExt(program.headless, level_editor_test_game, message);
12084 recursion_loop_detected = FALSE; // if game should be continued
12091 if (game.restart_level)
12092 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
12094 CheckLevelSolved();
12096 if (game.LevelSolved && !game.LevelSolved_GameEnd)
12099 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
12102 if (game_status != GAME_MODE_PLAYING) // status might have changed
12105 game_frame_delay_value =
12106 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
12108 if (tape.playing && tape.warp_forward && !tape.pausing)
12109 game_frame_delay_value = 0;
12111 SetVideoFrameDelay(game_frame_delay_value);
12113 // (de)activate virtual buttons depending on current game status
12114 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
12116 if (game.all_players_gone) // if no players there to be controlled anymore
12117 SetOverlayActive(FALSE);
12118 else if (!tape.playing) // if game continues after tape stopped playing
12119 SetOverlayActive(TRUE);
12124 // ---------- main game synchronization point ----------
12126 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12128 Debug("game:playing:skip", "skip == %d", skip);
12131 // ---------- main game synchronization point ----------
12133 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12137 if (network_playing && !network_player_action_received)
12139 // try to get network player actions in time
12141 // last chance to get network player actions without main loop delay
12142 HandleNetworking();
12144 // game was quit by network peer
12145 if (game_status != GAME_MODE_PLAYING)
12148 // check if network player actions still missing and game still running
12149 if (!network_player_action_received && !checkGameEnded())
12150 return; // failed to get network player actions in time
12152 // do not yet reset "network_player_action_received" (for tape.pausing)
12158 // at this point we know that we really continue executing the game
12160 network_player_action_received = FALSE;
12162 // when playing tape, read previously recorded player input from tape data
12163 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
12165 local_player->effective_mouse_action = local_player->mouse_action;
12167 if (recorded_player_action != NULL)
12168 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
12169 recorded_player_action);
12171 // TapePlayAction() may return NULL when toggling to "pause before death"
12175 if (tape.set_centered_player)
12177 game.centered_player_nr_next = tape.centered_player_nr_next;
12178 game.set_centered_player = TRUE;
12181 for (i = 0; i < MAX_PLAYERS; i++)
12183 summarized_player_action |= stored_player[i].action;
12185 if (!network_playing && (game.team_mode || tape.playing))
12186 stored_player[i].effective_action = stored_player[i].action;
12189 if (network_playing && !checkGameEnded())
12190 SendToServer_MovePlayer(summarized_player_action);
12192 // summarize all actions at local players mapped input device position
12193 // (this allows using different input devices in single player mode)
12194 if (!network.enabled && !game.team_mode)
12195 stored_player[map_player_action[local_player->index_nr]].effective_action =
12196 summarized_player_action;
12198 // summarize all actions at centered player in local team mode
12199 if (tape.recording &&
12200 setup.team_mode && !network.enabled &&
12201 setup.input_on_focus &&
12202 game.centered_player_nr != -1)
12204 for (i = 0; i < MAX_PLAYERS; i++)
12205 stored_player[map_player_action[i]].effective_action =
12206 (i == game.centered_player_nr ? summarized_player_action : 0);
12209 if (recorded_player_action != NULL)
12210 for (i = 0; i < MAX_PLAYERS; i++)
12211 stored_player[i].effective_action = recorded_player_action[i];
12213 for (i = 0; i < MAX_PLAYERS; i++)
12215 tape_action[i] = stored_player[i].effective_action;
12217 /* (this may happen in the RND game engine if a player was not present on
12218 the playfield on level start, but appeared later from a custom element */
12219 if (setup.team_mode &&
12222 !tape.player_participates[i])
12223 tape.player_participates[i] = TRUE;
12226 SetTapeActionFromMouseAction(tape_action,
12227 &local_player->effective_mouse_action);
12229 // only record actions from input devices, but not programmed actions
12230 if (tape.recording)
12231 TapeRecordAction(tape_action);
12233 // remember if game was played (especially after tape stopped playing)
12234 if (!tape.playing && summarized_player_action && !checkGameFailed())
12235 game.GamePlayed = TRUE;
12237 #if USE_NEW_PLAYER_ASSIGNMENTS
12238 // !!! also map player actions in single player mode !!!
12239 // if (game.team_mode)
12242 byte mapped_action[MAX_PLAYERS];
12244 #if DEBUG_PLAYER_ACTIONS
12245 for (i = 0; i < MAX_PLAYERS; i++)
12246 DebugContinued("", "%d, ", stored_player[i].effective_action);
12249 for (i = 0; i < MAX_PLAYERS; i++)
12250 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
12252 for (i = 0; i < MAX_PLAYERS; i++)
12253 stored_player[i].effective_action = mapped_action[i];
12255 #if DEBUG_PLAYER_ACTIONS
12256 DebugContinued("", "=> ");
12257 for (i = 0; i < MAX_PLAYERS; i++)
12258 DebugContinued("", "%d, ", stored_player[i].effective_action);
12259 DebugContinued("game:playing:player", "\n");
12262 #if DEBUG_PLAYER_ACTIONS
12265 for (i = 0; i < MAX_PLAYERS; i++)
12266 DebugContinued("", "%d, ", stored_player[i].effective_action);
12267 DebugContinued("game:playing:player", "\n");
12272 for (i = 0; i < MAX_PLAYERS; i++)
12274 // allow engine snapshot in case of changed movement attempt
12275 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12276 (stored_player[i].effective_action & KEY_MOTION))
12277 game.snapshot.changed_action = TRUE;
12279 // allow engine snapshot in case of snapping/dropping attempt
12280 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12281 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12282 game.snapshot.changed_action = TRUE;
12284 game.snapshot.last_action[i] = stored_player[i].effective_action;
12287 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
12289 GameActions_BD_Main();
12291 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12293 GameActions_EM_Main();
12295 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12297 GameActions_SP_Main();
12299 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12301 GameActions_MM_Main();
12305 GameActions_RND_Main();
12308 BlitScreenToBitmap(backbuffer);
12310 CheckLevelSolved();
12313 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12315 if (global.show_frames_per_second)
12317 static unsigned int fps_counter = 0;
12318 static int fps_frames = 0;
12319 unsigned int fps_delay_ms = Counter() - fps_counter;
12323 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12325 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12328 fps_counter = Counter();
12330 // always draw FPS to screen after FPS value was updated
12331 redraw_mask |= REDRAW_FPS;
12334 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12335 if (GetDrawDeactivationMask() == REDRAW_NONE)
12336 redraw_mask |= REDRAW_FPS;
12340 static void GameActions_CheckSaveEngineSnapshot(void)
12342 if (!game.snapshot.save_snapshot)
12345 // clear flag for saving snapshot _before_ saving snapshot
12346 game.snapshot.save_snapshot = FALSE;
12348 SaveEngineSnapshotToList();
12351 void GameActions(void)
12355 GameActions_CheckSaveEngineSnapshot();
12358 void GameActions_BD_Main(void)
12360 byte effective_action[MAX_PLAYERS];
12363 for (i = 0; i < MAX_PLAYERS; i++)
12364 effective_action[i] = stored_player[i].effective_action;
12366 GameActions_BD(effective_action);
12369 void GameActions_EM_Main(void)
12371 byte effective_action[MAX_PLAYERS];
12374 for (i = 0; i < MAX_PLAYERS; i++)
12375 effective_action[i] = stored_player[i].effective_action;
12377 GameActions_EM(effective_action);
12380 void GameActions_SP_Main(void)
12382 byte effective_action[MAX_PLAYERS];
12385 for (i = 0; i < MAX_PLAYERS; i++)
12386 effective_action[i] = stored_player[i].effective_action;
12388 GameActions_SP(effective_action);
12390 for (i = 0; i < MAX_PLAYERS; i++)
12392 if (stored_player[i].force_dropping)
12393 stored_player[i].action |= KEY_BUTTON_DROP;
12395 stored_player[i].force_dropping = FALSE;
12399 void GameActions_MM_Main(void)
12403 GameActions_MM(local_player->effective_mouse_action);
12406 void GameActions_RND_Main(void)
12411 void GameActions_RND(void)
12413 static struct MouseActionInfo mouse_action_last = { 0 };
12414 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12415 int magic_wall_x = 0, magic_wall_y = 0;
12416 int i, x, y, element, graphic, last_gfx_frame;
12418 InitPlayfieldScanModeVars();
12420 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12422 SCAN_PLAYFIELD(x, y)
12424 ChangeCount[x][y] = 0;
12425 ChangeEvent[x][y] = -1;
12429 if (game.set_centered_player)
12431 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12433 // switching to "all players" only possible if all players fit to screen
12434 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12436 game.centered_player_nr_next = game.centered_player_nr;
12437 game.set_centered_player = FALSE;
12440 // do not switch focus to non-existing (or non-active) player
12441 if (game.centered_player_nr_next >= 0 &&
12442 !stored_player[game.centered_player_nr_next].active)
12444 game.centered_player_nr_next = game.centered_player_nr;
12445 game.set_centered_player = FALSE;
12449 if (game.set_centered_player &&
12450 ScreenMovPos == 0) // screen currently aligned at tile position
12454 if (game.centered_player_nr_next == -1)
12456 setScreenCenteredToAllPlayers(&sx, &sy);
12460 sx = stored_player[game.centered_player_nr_next].jx;
12461 sy = stored_player[game.centered_player_nr_next].jy;
12464 game.centered_player_nr = game.centered_player_nr_next;
12465 game.set_centered_player = FALSE;
12467 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12468 DrawGameDoorValues();
12471 // check single step mode (set flag and clear again if any player is active)
12472 game.enter_single_step_mode =
12473 (tape.single_step && tape.recording && !tape.pausing);
12475 for (i = 0; i < MAX_PLAYERS; i++)
12477 int actual_player_action = stored_player[i].effective_action;
12480 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12481 - rnd_equinox_tetrachloride 048
12482 - rnd_equinox_tetrachloride_ii 096
12483 - rnd_emanuel_schmieg 002
12484 - doctor_sloan_ww 001, 020
12486 if (stored_player[i].MovPos == 0)
12487 CheckGravityMovement(&stored_player[i]);
12490 // overwrite programmed action with tape action
12491 if (stored_player[i].programmed_action)
12492 actual_player_action = stored_player[i].programmed_action;
12494 PlayerActions(&stored_player[i], actual_player_action);
12496 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12499 // single step pause mode may already have been toggled by "ScrollPlayer()"
12500 if (game.enter_single_step_mode && !tape.pausing)
12501 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12503 ScrollScreen(NULL, SCROLL_GO_ON);
12505 /* for backwards compatibility, the following code emulates a fixed bug that
12506 occured when pushing elements (causing elements that just made their last
12507 pushing step to already (if possible) make their first falling step in the
12508 same game frame, which is bad); this code is also needed to use the famous
12509 "spring push bug" which is used in older levels and might be wanted to be
12510 used also in newer levels, but in this case the buggy pushing code is only
12511 affecting the "spring" element and no other elements */
12513 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12515 for (i = 0; i < MAX_PLAYERS; i++)
12517 struct PlayerInfo *player = &stored_player[i];
12518 int x = player->jx;
12519 int y = player->jy;
12521 if (player->active && player->is_pushing && player->is_moving &&
12523 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12524 Tile[x][y] == EL_SPRING))
12526 ContinueMoving(x, y);
12528 // continue moving after pushing (this is actually a bug)
12529 if (!IS_MOVING(x, y))
12530 Stop[x][y] = FALSE;
12535 SCAN_PLAYFIELD(x, y)
12537 Last[x][y] = Tile[x][y];
12539 ChangeCount[x][y] = 0;
12540 ChangeEvent[x][y] = -1;
12542 // this must be handled before main playfield loop
12543 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12546 if (MovDelay[x][y] <= 0)
12550 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12553 if (MovDelay[x][y] <= 0)
12555 int element = Store[x][y];
12556 int move_direction = MovDir[x][y];
12557 int player_index_bit = Store2[x][y];
12563 TEST_DrawLevelField(x, y);
12565 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12567 if (IS_ENVELOPE(element))
12568 local_player->show_envelope = element;
12573 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12575 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12577 Debug("game:playing:GameActions_RND", "This should never happen!");
12579 ChangePage[x][y] = -1;
12583 Stop[x][y] = FALSE;
12584 if (WasJustMoving[x][y] > 0)
12585 WasJustMoving[x][y]--;
12586 if (WasJustFalling[x][y] > 0)
12587 WasJustFalling[x][y]--;
12588 if (CheckCollision[x][y] > 0)
12589 CheckCollision[x][y]--;
12590 if (CheckImpact[x][y] > 0)
12591 CheckImpact[x][y]--;
12595 /* reset finished pushing action (not done in ContinueMoving() to allow
12596 continuous pushing animation for elements with zero push delay) */
12597 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12599 ResetGfxAnimation(x, y);
12600 TEST_DrawLevelField(x, y);
12604 if (IS_BLOCKED(x, y))
12608 Blocked2Moving(x, y, &oldx, &oldy);
12609 if (!IS_MOVING(oldx, oldy))
12611 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12612 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12613 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12614 Debug("game:playing:GameActions_RND", "This should never happen!");
12620 HandleMouseAction(&mouse_action, &mouse_action_last);
12622 SCAN_PLAYFIELD(x, y)
12624 element = Tile[x][y];
12625 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12626 last_gfx_frame = GfxFrame[x][y];
12628 if (element == EL_EMPTY)
12629 graphic = el2img(GfxElementEmpty[x][y]);
12631 ResetGfxFrame(x, y);
12633 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12634 DrawLevelGraphicAnimation(x, y, graphic);
12636 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12637 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12638 ResetRandomAnimationValue(x, y);
12640 SetRandomAnimationValue(x, y);
12642 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12644 if (IS_INACTIVE(element))
12646 if (IS_ANIMATED(graphic))
12647 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12652 // this may take place after moving, so 'element' may have changed
12653 if (IS_CHANGING(x, y) &&
12654 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12656 int page = element_info[element].event_page_nr[CE_DELAY];
12658 HandleElementChange(x, y, page);
12660 element = Tile[x][y];
12661 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12664 CheckNextToConditions(x, y);
12666 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12670 element = Tile[x][y];
12671 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12673 if (IS_ANIMATED(graphic) &&
12674 !IS_MOVING(x, y) &&
12676 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12678 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12679 TEST_DrawTwinkleOnField(x, y);
12681 else if (element == EL_ACID)
12684 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12686 else if ((element == EL_EXIT_OPEN ||
12687 element == EL_EM_EXIT_OPEN ||
12688 element == EL_SP_EXIT_OPEN ||
12689 element == EL_STEEL_EXIT_OPEN ||
12690 element == EL_EM_STEEL_EXIT_OPEN ||
12691 element == EL_SP_TERMINAL ||
12692 element == EL_SP_TERMINAL_ACTIVE ||
12693 element == EL_EXTRA_TIME ||
12694 element == EL_SHIELD_NORMAL ||
12695 element == EL_SHIELD_DEADLY) &&
12696 IS_ANIMATED(graphic))
12697 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12698 else if (IS_MOVING(x, y))
12699 ContinueMoving(x, y);
12700 else if (IS_ACTIVE_BOMB(element))
12701 CheckDynamite(x, y);
12702 else if (element == EL_AMOEBA_GROWING)
12703 AmoebaGrowing(x, y);
12704 else if (element == EL_AMOEBA_SHRINKING)
12705 AmoebaShrinking(x, y);
12707 #if !USE_NEW_AMOEBA_CODE
12708 else if (IS_AMOEBALIVE(element))
12709 AmoebaReproduce(x, y);
12712 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12714 else if (element == EL_EXIT_CLOSED)
12716 else if (element == EL_EM_EXIT_CLOSED)
12718 else if (element == EL_STEEL_EXIT_CLOSED)
12719 CheckExitSteel(x, y);
12720 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12721 CheckExitSteelEM(x, y);
12722 else if (element == EL_SP_EXIT_CLOSED)
12724 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12725 element == EL_EXPANDABLE_STEELWALL_GROWING)
12727 else if (element == EL_EXPANDABLE_WALL ||
12728 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12729 element == EL_EXPANDABLE_WALL_VERTICAL ||
12730 element == EL_EXPANDABLE_WALL_ANY ||
12731 element == EL_BD_EXPANDABLE_WALL ||
12732 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12733 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12734 element == EL_EXPANDABLE_STEELWALL_ANY)
12735 CheckWallGrowing(x, y);
12736 else if (element == EL_FLAMES)
12737 CheckForDragon(x, y);
12738 else if (element == EL_EXPLOSION)
12739 ; // drawing of correct explosion animation is handled separately
12740 else if (element == EL_ELEMENT_SNAPPING ||
12741 element == EL_DIAGONAL_SHRINKING ||
12742 element == EL_DIAGONAL_GROWING)
12744 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]);
12746 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12748 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12749 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12751 if (IS_BELT_ACTIVE(element))
12752 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12754 if (game.magic_wall_active)
12756 int jx = local_player->jx, jy = local_player->jy;
12758 // play the element sound at the position nearest to the player
12759 if ((element == EL_MAGIC_WALL_FULL ||
12760 element == EL_MAGIC_WALL_ACTIVE ||
12761 element == EL_MAGIC_WALL_EMPTYING ||
12762 element == EL_BD_MAGIC_WALL_FULL ||
12763 element == EL_BD_MAGIC_WALL_ACTIVE ||
12764 element == EL_BD_MAGIC_WALL_EMPTYING ||
12765 element == EL_DC_MAGIC_WALL_FULL ||
12766 element == EL_DC_MAGIC_WALL_ACTIVE ||
12767 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12768 ABS(x - jx) + ABS(y - jy) <
12769 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12777 #if USE_NEW_AMOEBA_CODE
12778 // new experimental amoeba growth stuff
12779 if (!(FrameCounter % 8))
12781 static unsigned int random = 1684108901;
12783 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12785 x = RND(lev_fieldx);
12786 y = RND(lev_fieldy);
12787 element = Tile[x][y];
12789 if (!IS_PLAYER(x, y) &&
12790 (element == EL_EMPTY ||
12791 CAN_GROW_INTO(element) ||
12792 element == EL_QUICKSAND_EMPTY ||
12793 element == EL_QUICKSAND_FAST_EMPTY ||
12794 element == EL_ACID_SPLASH_LEFT ||
12795 element == EL_ACID_SPLASH_RIGHT))
12797 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12798 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12799 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12800 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12801 Tile[x][y] = EL_AMOEBA_DROP;
12804 random = random * 129 + 1;
12809 game.explosions_delayed = FALSE;
12811 SCAN_PLAYFIELD(x, y)
12813 element = Tile[x][y];
12815 if (ExplodeField[x][y])
12816 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12817 else if (element == EL_EXPLOSION)
12818 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12820 ExplodeField[x][y] = EX_TYPE_NONE;
12823 game.explosions_delayed = TRUE;
12825 if (game.magic_wall_active)
12827 if (!(game.magic_wall_time_left % 4))
12829 int element = Tile[magic_wall_x][magic_wall_y];
12831 if (element == EL_BD_MAGIC_WALL_FULL ||
12832 element == EL_BD_MAGIC_WALL_ACTIVE ||
12833 element == EL_BD_MAGIC_WALL_EMPTYING)
12834 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12835 else if (element == EL_DC_MAGIC_WALL_FULL ||
12836 element == EL_DC_MAGIC_WALL_ACTIVE ||
12837 element == EL_DC_MAGIC_WALL_EMPTYING)
12838 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12840 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12843 if (game.magic_wall_time_left > 0)
12845 game.magic_wall_time_left--;
12847 if (!game.magic_wall_time_left)
12849 SCAN_PLAYFIELD(x, y)
12851 element = Tile[x][y];
12853 if (element == EL_MAGIC_WALL_ACTIVE ||
12854 element == EL_MAGIC_WALL_FULL)
12856 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12857 TEST_DrawLevelField(x, y);
12859 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12860 element == EL_BD_MAGIC_WALL_FULL)
12862 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12863 TEST_DrawLevelField(x, y);
12865 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12866 element == EL_DC_MAGIC_WALL_FULL)
12868 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12869 TEST_DrawLevelField(x, y);
12873 game.magic_wall_active = FALSE;
12878 if (game.light_time_left > 0)
12880 game.light_time_left--;
12882 if (game.light_time_left == 0)
12883 RedrawAllLightSwitchesAndInvisibleElements();
12886 if (game.timegate_time_left > 0)
12888 game.timegate_time_left--;
12890 if (game.timegate_time_left == 0)
12891 CloseAllOpenTimegates();
12894 if (game.lenses_time_left > 0)
12896 game.lenses_time_left--;
12898 if (game.lenses_time_left == 0)
12899 RedrawAllInvisibleElementsForLenses();
12902 if (game.magnify_time_left > 0)
12904 game.magnify_time_left--;
12906 if (game.magnify_time_left == 0)
12907 RedrawAllInvisibleElementsForMagnifier();
12910 for (i = 0; i < MAX_PLAYERS; i++)
12912 struct PlayerInfo *player = &stored_player[i];
12914 if (SHIELD_ON(player))
12916 if (player->shield_deadly_time_left)
12917 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12918 else if (player->shield_normal_time_left)
12919 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12923 #if USE_DELAYED_GFX_REDRAW
12924 SCAN_PLAYFIELD(x, y)
12926 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12928 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12929 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12931 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12932 DrawLevelField(x, y);
12934 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12935 DrawLevelFieldCrumbled(x, y);
12937 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12938 DrawLevelFieldCrumbledNeighbours(x, y);
12940 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12941 DrawTwinkleOnField(x, y);
12944 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12949 PlayAllPlayersSound();
12951 for (i = 0; i < MAX_PLAYERS; i++)
12953 struct PlayerInfo *player = &stored_player[i];
12955 if (player->show_envelope != 0 && (!player->active ||
12956 player->MovPos == 0))
12958 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12960 player->show_envelope = 0;
12964 // use random number generator in every frame to make it less predictable
12965 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12968 mouse_action_last = mouse_action;
12971 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12973 int min_x = x, min_y = y, max_x = x, max_y = y;
12974 int scr_fieldx = getScreenFieldSizeX();
12975 int scr_fieldy = getScreenFieldSizeY();
12978 for (i = 0; i < MAX_PLAYERS; i++)
12980 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12982 if (!stored_player[i].active || &stored_player[i] == player)
12985 min_x = MIN(min_x, jx);
12986 min_y = MIN(min_y, jy);
12987 max_x = MAX(max_x, jx);
12988 max_y = MAX(max_y, jy);
12991 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12994 static boolean AllPlayersInVisibleScreen(void)
12998 for (i = 0; i < MAX_PLAYERS; i++)
13000 int jx = stored_player[i].jx, jy = stored_player[i].jy;
13002 if (!stored_player[i].active)
13005 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13012 void ScrollLevel(int dx, int dy)
13014 int scroll_offset = 2 * TILEX_VAR;
13017 BlitBitmap(drawto_field, drawto_field,
13018 FX + TILEX_VAR * (dx == -1) - scroll_offset,
13019 FY + TILEY_VAR * (dy == -1) - scroll_offset,
13020 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
13021 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
13022 FX + TILEX_VAR * (dx == 1) - scroll_offset,
13023 FY + TILEY_VAR * (dy == 1) - scroll_offset);
13027 x = (dx == 1 ? BX1 : BX2);
13028 for (y = BY1; y <= BY2; y++)
13029 DrawScreenField(x, y);
13034 y = (dy == 1 ? BY1 : BY2);
13035 for (x = BX1; x <= BX2; x++)
13036 DrawScreenField(x, y);
13039 redraw_mask |= REDRAW_FIELD;
13042 static boolean canFallDown(struct PlayerInfo *player)
13044 int jx = player->jx, jy = player->jy;
13046 return (IN_LEV_FIELD(jx, jy + 1) &&
13047 (IS_FREE(jx, jy + 1) ||
13048 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
13049 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
13050 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
13053 static boolean canPassField(int x, int y, int move_dir)
13055 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13056 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13057 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13058 int nextx = x + dx;
13059 int nexty = y + dy;
13060 int element = Tile[x][y];
13062 return (IS_PASSABLE_FROM(element, opposite_dir) &&
13063 !CAN_MOVE(element) &&
13064 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
13065 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
13066 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
13069 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
13071 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13072 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13073 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13077 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
13078 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
13079 (IS_DIGGABLE(Tile[newx][newy]) ||
13080 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
13081 canPassField(newx, newy, move_dir)));
13084 static void CheckGravityMovement(struct PlayerInfo *player)
13086 if (player->gravity && !player->programmed_action)
13088 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
13089 int move_dir_vertical = player->effective_action & MV_VERTICAL;
13090 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
13091 int jx = player->jx, jy = player->jy;
13092 boolean player_is_moving_to_valid_field =
13093 (!player_is_snapping &&
13094 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
13095 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
13096 boolean player_can_fall_down = canFallDown(player);
13098 if (player_can_fall_down &&
13099 !player_is_moving_to_valid_field)
13100 player->programmed_action = MV_DOWN;
13104 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
13106 return CheckGravityMovement(player);
13108 if (player->gravity && !player->programmed_action)
13110 int jx = player->jx, jy = player->jy;
13111 boolean field_under_player_is_free =
13112 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
13113 boolean player_is_standing_on_valid_field =
13114 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
13115 (IS_WALKABLE(Tile[jx][jy]) &&
13116 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
13118 if (field_under_player_is_free && !player_is_standing_on_valid_field)
13119 player->programmed_action = MV_DOWN;
13124 MovePlayerOneStep()
13125 -----------------------------------------------------------------------------
13126 dx, dy: direction (non-diagonal) to try to move the player to
13127 real_dx, real_dy: direction as read from input device (can be diagonal)
13130 boolean MovePlayerOneStep(struct PlayerInfo *player,
13131 int dx, int dy, int real_dx, int real_dy)
13133 int jx = player->jx, jy = player->jy;
13134 int new_jx = jx + dx, new_jy = jy + dy;
13136 boolean player_can_move = !player->cannot_move;
13138 if (!player->active || (!dx && !dy))
13139 return MP_NO_ACTION;
13141 player->MovDir = (dx < 0 ? MV_LEFT :
13142 dx > 0 ? MV_RIGHT :
13144 dy > 0 ? MV_DOWN : MV_NONE);
13146 if (!IN_LEV_FIELD(new_jx, new_jy))
13147 return MP_NO_ACTION;
13149 if (!player_can_move)
13151 if (player->MovPos == 0)
13153 player->is_moving = FALSE;
13154 player->is_digging = FALSE;
13155 player->is_collecting = FALSE;
13156 player->is_snapping = FALSE;
13157 player->is_pushing = FALSE;
13161 if (!network.enabled && game.centered_player_nr == -1 &&
13162 !AllPlayersInSight(player, new_jx, new_jy))
13163 return MP_NO_ACTION;
13165 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG);
13166 if (can_move != MP_MOVING)
13169 // check if DigField() has caused relocation of the player
13170 if (player->jx != jx || player->jy != jy)
13171 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
13173 StorePlayer[jx][jy] = 0;
13174 player->last_jx = jx;
13175 player->last_jy = jy;
13176 player->jx = new_jx;
13177 player->jy = new_jy;
13178 StorePlayer[new_jx][new_jy] = player->element_nr;
13180 if (player->move_delay_value_next != -1)
13182 player->move_delay_value = player->move_delay_value_next;
13183 player->move_delay_value_next = -1;
13187 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
13189 player->step_counter++;
13191 PlayerVisit[jx][jy] = FrameCounter;
13193 player->is_moving = TRUE;
13196 // should better be called in MovePlayer(), but this breaks some tapes
13197 ScrollPlayer(player, SCROLL_INIT);
13203 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
13205 int jx = player->jx, jy = player->jy;
13206 int old_jx = jx, old_jy = jy;
13207 int moved = MP_NO_ACTION;
13209 if (!player->active)
13214 if (player->MovPos == 0)
13216 player->is_moving = FALSE;
13217 player->is_digging = FALSE;
13218 player->is_collecting = FALSE;
13219 player->is_snapping = FALSE;
13220 player->is_pushing = FALSE;
13226 if (player->move_delay > 0)
13229 player->move_delay = -1; // set to "uninitialized" value
13231 // store if player is automatically moved to next field
13232 player->is_auto_moving = (player->programmed_action != MV_NONE);
13234 // remove the last programmed player action
13235 player->programmed_action = 0;
13237 if (player->MovPos)
13239 // should only happen if pre-1.2 tape recordings are played
13240 // this is only for backward compatibility
13242 int original_move_delay_value = player->move_delay_value;
13245 Debug("game:playing:MovePlayer",
13246 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13250 // scroll remaining steps with finest movement resolution
13251 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13253 while (player->MovPos)
13255 ScrollPlayer(player, SCROLL_GO_ON);
13256 ScrollScreen(NULL, SCROLL_GO_ON);
13258 AdvanceFrameAndPlayerCounters(player->index_nr);
13261 BackToFront_WithFrameDelay(0);
13264 player->move_delay_value = original_move_delay_value;
13267 player->is_active = FALSE;
13269 if (player->last_move_dir & MV_HORIZONTAL)
13271 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13272 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13276 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13277 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13280 if (!moved && !player->is_active)
13282 player->is_moving = FALSE;
13283 player->is_digging = FALSE;
13284 player->is_collecting = FALSE;
13285 player->is_snapping = FALSE;
13286 player->is_pushing = FALSE;
13292 if (moved & MP_MOVING && !ScreenMovPos &&
13293 (player->index_nr == game.centered_player_nr ||
13294 game.centered_player_nr == -1))
13296 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13298 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13300 // actual player has left the screen -- scroll in that direction
13301 if (jx != old_jx) // player has moved horizontally
13302 scroll_x += (jx - old_jx);
13303 else // player has moved vertically
13304 scroll_y += (jy - old_jy);
13308 int offset_raw = game.scroll_delay_value;
13310 if (jx != old_jx) // player has moved horizontally
13312 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13313 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13314 int new_scroll_x = jx - MIDPOSX + offset_x;
13316 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13317 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13318 scroll_x = new_scroll_x;
13320 // don't scroll over playfield boundaries
13321 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13323 // don't scroll more than one field at a time
13324 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13326 // don't scroll against the player's moving direction
13327 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13328 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13329 scroll_x = old_scroll_x;
13331 else // player has moved vertically
13333 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13334 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13335 int new_scroll_y = jy - MIDPOSY + offset_y;
13337 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13338 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13339 scroll_y = new_scroll_y;
13341 // don't scroll over playfield boundaries
13342 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13344 // don't scroll more than one field at a time
13345 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13347 // don't scroll against the player's moving direction
13348 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13349 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13350 scroll_y = old_scroll_y;
13354 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13356 if (!network.enabled && game.centered_player_nr == -1 &&
13357 !AllPlayersInVisibleScreen())
13359 scroll_x = old_scroll_x;
13360 scroll_y = old_scroll_y;
13364 ScrollScreen(player, SCROLL_INIT);
13365 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13370 player->StepFrame = 0;
13372 if (moved & MP_MOVING)
13374 if (old_jx != jx && old_jy == jy)
13375 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13376 else if (old_jx == jx && old_jy != jy)
13377 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13379 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13381 player->last_move_dir = player->MovDir;
13382 player->is_moving = TRUE;
13383 player->is_snapping = FALSE;
13384 player->is_switching = FALSE;
13385 player->is_dropping = FALSE;
13386 player->is_dropping_pressed = FALSE;
13387 player->drop_pressed_delay = 0;
13390 // should better be called here than above, but this breaks some tapes
13391 ScrollPlayer(player, SCROLL_INIT);
13396 CheckGravityMovementWhenNotMoving(player);
13398 player->is_moving = FALSE;
13400 /* at this point, the player is allowed to move, but cannot move right now
13401 (e.g. because of something blocking the way) -- ensure that the player
13402 is also allowed to move in the next frame (in old versions before 3.1.1,
13403 the player was forced to wait again for eight frames before next try) */
13405 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13406 player->move_delay = 0; // allow direct movement in the next frame
13409 if (player->move_delay == -1) // not yet initialized by DigField()
13410 player->move_delay = player->move_delay_value;
13412 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13414 TestIfPlayerTouchesBadThing(jx, jy);
13415 TestIfPlayerTouchesCustomElement(jx, jy);
13418 if (!player->active)
13419 RemovePlayer(player);
13424 void ScrollPlayer(struct PlayerInfo *player, int mode)
13426 int jx = player->jx, jy = player->jy;
13427 int last_jx = player->last_jx, last_jy = player->last_jy;
13428 int move_stepsize = TILEX / player->move_delay_value;
13430 if (!player->active)
13433 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13436 if (mode == SCROLL_INIT)
13438 player->actual_frame_counter.count = FrameCounter;
13439 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13441 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13442 Tile[last_jx][last_jy] == EL_EMPTY)
13444 int last_field_block_delay = 0; // start with no blocking at all
13445 int block_delay_adjustment = player->block_delay_adjustment;
13447 // if player blocks last field, add delay for exactly one move
13448 if (player->block_last_field)
13450 last_field_block_delay += player->move_delay_value;
13452 // when blocking enabled, prevent moving up despite gravity
13453 if (player->gravity && player->MovDir == MV_UP)
13454 block_delay_adjustment = -1;
13457 // add block delay adjustment (also possible when not blocking)
13458 last_field_block_delay += block_delay_adjustment;
13460 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13461 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13464 if (player->MovPos != 0) // player has not yet reached destination
13467 else if (!FrameReached(&player->actual_frame_counter))
13470 if (player->MovPos != 0)
13472 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13473 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13475 // before DrawPlayer() to draw correct player graphic for this case
13476 if (player->MovPos == 0)
13477 CheckGravityMovement(player);
13480 if (player->MovPos == 0) // player reached destination field
13482 if (player->move_delay_reset_counter > 0)
13484 player->move_delay_reset_counter--;
13486 if (player->move_delay_reset_counter == 0)
13488 // continue with normal speed after quickly moving through gate
13489 HALVE_PLAYER_SPEED(player);
13491 // be able to make the next move without delay
13492 player->move_delay = 0;
13496 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13497 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13498 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13499 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13500 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13501 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13502 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13503 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13505 ExitPlayer(player);
13507 if (game.players_still_needed == 0 &&
13508 (game.friends_still_needed == 0 ||
13509 IS_SP_ELEMENT(Tile[jx][jy])))
13513 player->last_jx = jx;
13514 player->last_jy = jy;
13516 // this breaks one level: "machine", level 000
13518 int move_direction = player->MovDir;
13519 int enter_side = MV_DIR_OPPOSITE(move_direction);
13520 int leave_side = move_direction;
13521 int old_jx = last_jx;
13522 int old_jy = last_jy;
13523 int old_element = Tile[old_jx][old_jy];
13524 int new_element = Tile[jx][jy];
13526 if (IS_CUSTOM_ELEMENT(old_element))
13527 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13529 player->index_bit, leave_side);
13531 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13532 CE_PLAYER_LEAVES_X,
13533 player->index_bit, leave_side);
13535 // needed because pushed element has not yet reached its destination,
13536 // so it would trigger a change event at its previous field location
13537 if (!player->is_pushing)
13539 if (IS_CUSTOM_ELEMENT(new_element))
13540 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13541 player->index_bit, enter_side);
13543 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13544 CE_PLAYER_ENTERS_X,
13545 player->index_bit, enter_side);
13548 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13549 CE_MOVE_OF_X, move_direction);
13552 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13554 TestIfPlayerTouchesBadThing(jx, jy);
13555 TestIfPlayerTouchesCustomElement(jx, jy);
13557 // needed because pushed element has not yet reached its destination,
13558 // so it would trigger a change event at its previous field location
13559 if (!player->is_pushing)
13560 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13562 if (level.finish_dig_collect &&
13563 (player->is_digging || player->is_collecting))
13565 int last_element = player->last_removed_element;
13566 int move_direction = player->MovDir;
13567 int enter_side = MV_DIR_OPPOSITE(move_direction);
13568 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13569 CE_PLAYER_COLLECTS_X);
13571 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13572 player->index_bit, enter_side);
13574 player->last_removed_element = EL_UNDEFINED;
13577 if (!player->active)
13578 RemovePlayer(player);
13581 if (level.use_step_counter)
13582 CheckLevelTime_StepCounter();
13584 if (tape.single_step && tape.recording && !tape.pausing &&
13585 !player->programmed_action)
13586 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13588 if (!player->programmed_action)
13589 CheckSaveEngineSnapshot(player);
13593 void ScrollScreen(struct PlayerInfo *player, int mode)
13595 static DelayCounter screen_frame_counter = { 0 };
13597 if (mode == SCROLL_INIT)
13599 // set scrolling step size according to actual player's moving speed
13600 ScrollStepSize = TILEX / player->move_delay_value;
13602 screen_frame_counter.count = FrameCounter;
13603 screen_frame_counter.value = 1;
13605 ScreenMovDir = player->MovDir;
13606 ScreenMovPos = player->MovPos;
13607 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13610 else if (!FrameReached(&screen_frame_counter))
13615 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13616 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13617 redraw_mask |= REDRAW_FIELD;
13620 ScreenMovDir = MV_NONE;
13623 void CheckNextToConditions(int x, int y)
13625 int element = Tile[x][y];
13627 if (IS_PLAYER(x, y))
13628 TestIfPlayerNextToCustomElement(x, y);
13630 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13631 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13632 TestIfElementNextToCustomElement(x, y);
13635 void TestIfPlayerNextToCustomElement(int x, int y)
13637 struct XY *xy = xy_topdown;
13638 static int trigger_sides[4][2] =
13640 // center side border side
13641 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13642 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13643 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13644 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13648 if (!IS_PLAYER(x, y))
13651 struct PlayerInfo *player = PLAYERINFO(x, y);
13653 if (player->is_moving)
13656 for (i = 0; i < NUM_DIRECTIONS; i++)
13658 int xx = x + xy[i].x;
13659 int yy = y + xy[i].y;
13660 int border_side = trigger_sides[i][1];
13661 int border_element;
13663 if (!IN_LEV_FIELD(xx, yy))
13666 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13667 continue; // center and border element not connected
13669 border_element = Tile[xx][yy];
13671 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13672 player->index_bit, border_side);
13673 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13674 CE_PLAYER_NEXT_TO_X,
13675 player->index_bit, border_side);
13677 /* use player element that is initially defined in the level playfield,
13678 not the player element that corresponds to the runtime player number
13679 (example: a level that contains EL_PLAYER_3 as the only player would
13680 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13682 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13683 CE_NEXT_TO_X, border_side);
13687 void TestIfPlayerTouchesCustomElement(int x, int y)
13689 struct XY *xy = xy_topdown;
13690 static int trigger_sides[4][2] =
13692 // center side border side
13693 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13694 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13695 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13696 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13698 static int touch_dir[4] =
13700 MV_LEFT | MV_RIGHT,
13705 int center_element = Tile[x][y]; // should always be non-moving!
13708 for (i = 0; i < NUM_DIRECTIONS; i++)
13710 int xx = x + xy[i].x;
13711 int yy = y + xy[i].y;
13712 int center_side = trigger_sides[i][0];
13713 int border_side = trigger_sides[i][1];
13714 int border_element;
13716 if (!IN_LEV_FIELD(xx, yy))
13719 if (IS_PLAYER(x, y)) // player found at center element
13721 struct PlayerInfo *player = PLAYERINFO(x, y);
13723 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13724 border_element = Tile[xx][yy]; // may be moving!
13725 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13726 border_element = Tile[xx][yy];
13727 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13728 border_element = MovingOrBlocked2Element(xx, yy);
13730 continue; // center and border element do not touch
13732 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13733 player->index_bit, border_side);
13734 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13735 CE_PLAYER_TOUCHES_X,
13736 player->index_bit, border_side);
13739 /* use player element that is initially defined in the level playfield,
13740 not the player element that corresponds to the runtime player number
13741 (example: a level that contains EL_PLAYER_3 as the only player would
13742 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13743 int player_element = PLAYERINFO(x, y)->initial_element;
13745 // as element "X" is the player here, check opposite (center) side
13746 CheckElementChangeBySide(xx, yy, border_element, player_element,
13747 CE_TOUCHING_X, center_side);
13750 else if (IS_PLAYER(xx, yy)) // player found at border element
13752 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13754 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13756 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13757 continue; // center and border element do not touch
13760 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13761 player->index_bit, center_side);
13762 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13763 CE_PLAYER_TOUCHES_X,
13764 player->index_bit, center_side);
13767 /* use player element that is initially defined in the level playfield,
13768 not the player element that corresponds to the runtime player number
13769 (example: a level that contains EL_PLAYER_3 as the only player would
13770 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13771 int player_element = PLAYERINFO(xx, yy)->initial_element;
13773 // as element "X" is the player here, check opposite (border) side
13774 CheckElementChangeBySide(x, y, center_element, player_element,
13775 CE_TOUCHING_X, border_side);
13783 void TestIfElementNextToCustomElement(int x, int y)
13785 struct XY *xy = xy_topdown;
13786 static int trigger_sides[4][2] =
13788 // center side border side
13789 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13790 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13791 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13792 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13794 int center_element = Tile[x][y]; // should always be non-moving!
13797 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13800 for (i = 0; i < NUM_DIRECTIONS; i++)
13802 int xx = x + xy[i].x;
13803 int yy = y + xy[i].y;
13804 int border_side = trigger_sides[i][1];
13805 int border_element;
13807 if (!IN_LEV_FIELD(xx, yy))
13810 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13811 continue; // center and border element not connected
13813 border_element = Tile[xx][yy];
13815 // check for change of center element (but change it only once)
13816 if (CheckElementChangeBySide(x, y, center_element, border_element,
13817 CE_NEXT_TO_X, border_side))
13822 void TestIfElementTouchesCustomElement(int x, int y)
13824 struct XY *xy = xy_topdown;
13825 static int trigger_sides[4][2] =
13827 // center side border side
13828 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13829 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13830 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13831 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13833 static int touch_dir[4] =
13835 MV_LEFT | MV_RIGHT,
13840 boolean change_center_element = FALSE;
13841 int center_element = Tile[x][y]; // should always be non-moving!
13842 int border_element_old[NUM_DIRECTIONS];
13845 for (i = 0; i < NUM_DIRECTIONS; i++)
13847 int xx = x + xy[i].x;
13848 int yy = y + xy[i].y;
13849 int border_element;
13851 border_element_old[i] = -1;
13853 if (!IN_LEV_FIELD(xx, yy))
13856 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13857 border_element = Tile[xx][yy]; // may be moving!
13858 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13859 border_element = Tile[xx][yy];
13860 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13861 border_element = MovingOrBlocked2Element(xx, yy);
13863 continue; // center and border element do not touch
13865 border_element_old[i] = border_element;
13868 for (i = 0; i < NUM_DIRECTIONS; i++)
13870 int xx = x + xy[i].x;
13871 int yy = y + xy[i].y;
13872 int center_side = trigger_sides[i][0];
13873 int border_element = border_element_old[i];
13875 if (border_element == -1)
13878 // check for change of border element
13879 CheckElementChangeBySide(xx, yy, border_element, center_element,
13880 CE_TOUCHING_X, center_side);
13882 // (center element cannot be player, so we don't have to check this here)
13885 for (i = 0; i < NUM_DIRECTIONS; i++)
13887 int xx = x + xy[i].x;
13888 int yy = y + xy[i].y;
13889 int border_side = trigger_sides[i][1];
13890 int border_element = border_element_old[i];
13892 if (border_element == -1)
13895 // check for change of center element (but change it only once)
13896 if (!change_center_element)
13897 change_center_element =
13898 CheckElementChangeBySide(x, y, center_element, border_element,
13899 CE_TOUCHING_X, border_side);
13901 if (IS_PLAYER(xx, yy))
13903 /* use player element that is initially defined in the level playfield,
13904 not the player element that corresponds to the runtime player number
13905 (example: a level that contains EL_PLAYER_3 as the only player would
13906 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13907 int player_element = PLAYERINFO(xx, yy)->initial_element;
13909 // as element "X" is the player here, check opposite (border) side
13910 CheckElementChangeBySide(x, y, center_element, player_element,
13911 CE_TOUCHING_X, border_side);
13916 void TestIfElementHitsCustomElement(int x, int y, int direction)
13918 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13919 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13920 int hitx = x + dx, hity = y + dy;
13921 int hitting_element = Tile[x][y];
13922 int touched_element;
13924 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13927 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13928 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13930 if (IN_LEV_FIELD(hitx, hity))
13932 int opposite_direction = MV_DIR_OPPOSITE(direction);
13933 int hitting_side = direction;
13934 int touched_side = opposite_direction;
13935 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13936 MovDir[hitx][hity] != direction ||
13937 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13943 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13944 CE_HITTING_X, touched_side);
13946 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13947 CE_HIT_BY_X, hitting_side);
13949 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13950 CE_HIT_BY_SOMETHING, opposite_direction);
13952 if (IS_PLAYER(hitx, hity))
13954 /* use player element that is initially defined in the level playfield,
13955 not the player element that corresponds to the runtime player number
13956 (example: a level that contains EL_PLAYER_3 as the only player would
13957 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13958 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13960 CheckElementChangeBySide(x, y, hitting_element, player_element,
13961 CE_HITTING_X, touched_side);
13966 // "hitting something" is also true when hitting the playfield border
13967 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13968 CE_HITTING_SOMETHING, direction);
13971 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13973 int i, kill_x = -1, kill_y = -1;
13975 int bad_element = -1;
13976 struct XY *test_xy = xy_topdown;
13977 static int test_dir[4] =
13985 for (i = 0; i < NUM_DIRECTIONS; i++)
13987 int test_x, test_y, test_move_dir, test_element;
13989 test_x = good_x + test_xy[i].x;
13990 test_y = good_y + test_xy[i].y;
13992 if (!IN_LEV_FIELD(test_x, test_y))
13996 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13998 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
14000 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
14001 2nd case: DONT_TOUCH style bad thing does not move away from good thing
14003 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
14004 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
14008 bad_element = test_element;
14014 if (kill_x != -1 || kill_y != -1)
14016 if (IS_PLAYER(good_x, good_y))
14018 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
14020 if (player->shield_deadly_time_left > 0 &&
14021 !IS_INDESTRUCTIBLE(bad_element))
14022 Bang(kill_x, kill_y);
14023 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
14024 KillPlayer(player);
14027 Bang(good_x, good_y);
14031 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
14033 int i, kill_x = -1, kill_y = -1;
14034 int bad_element = Tile[bad_x][bad_y];
14035 struct XY *test_xy = xy_topdown;
14036 static int touch_dir[4] =
14038 MV_LEFT | MV_RIGHT,
14043 static int test_dir[4] =
14051 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
14054 for (i = 0; i < NUM_DIRECTIONS; i++)
14056 int test_x, test_y, test_move_dir, test_element;
14058 test_x = bad_x + test_xy[i].x;
14059 test_y = bad_y + test_xy[i].y;
14061 if (!IN_LEV_FIELD(test_x, test_y))
14065 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14067 test_element = Tile[test_x][test_y];
14069 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
14070 2nd case: DONT_TOUCH style bad thing does not move away from good thing
14072 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
14073 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
14075 // good thing is player or penguin that does not move away
14076 if (IS_PLAYER(test_x, test_y))
14078 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14080 if (bad_element == EL_ROBOT && player->is_moving)
14081 continue; // robot does not kill player if he is moving
14083 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
14085 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
14086 continue; // center and border element do not touch
14094 else if (test_element == EL_PENGUIN)
14104 if (kill_x != -1 || kill_y != -1)
14106 if (IS_PLAYER(kill_x, kill_y))
14108 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14110 if (player->shield_deadly_time_left > 0 &&
14111 !IS_INDESTRUCTIBLE(bad_element))
14112 Bang(bad_x, bad_y);
14113 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14114 KillPlayer(player);
14117 Bang(kill_x, kill_y);
14121 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
14123 int bad_element = Tile[bad_x][bad_y];
14124 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
14125 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
14126 int test_x = bad_x + dx, test_y = bad_y + dy;
14127 int test_move_dir, test_element;
14128 int kill_x = -1, kill_y = -1;
14130 if (!IN_LEV_FIELD(test_x, test_y))
14134 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14136 test_element = Tile[test_x][test_y];
14138 if (test_move_dir != bad_move_dir)
14140 // good thing can be player or penguin that does not move away
14141 if (IS_PLAYER(test_x, test_y))
14143 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14145 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
14146 player as being hit when he is moving towards the bad thing, because
14147 the "get hit by" condition would be lost after the player stops) */
14148 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
14149 return; // player moves away from bad thing
14154 else if (test_element == EL_PENGUIN)
14161 if (kill_x != -1 || kill_y != -1)
14163 if (IS_PLAYER(kill_x, kill_y))
14165 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14167 if (player->shield_deadly_time_left > 0 &&
14168 !IS_INDESTRUCTIBLE(bad_element))
14169 Bang(bad_x, bad_y);
14170 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14171 KillPlayer(player);
14174 Bang(kill_x, kill_y);
14178 void TestIfPlayerTouchesBadThing(int x, int y)
14180 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14183 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
14185 TestIfGoodThingHitsBadThing(x, y, move_dir);
14188 void TestIfBadThingTouchesPlayer(int x, int y)
14190 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14193 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
14195 TestIfBadThingHitsGoodThing(x, y, move_dir);
14198 void TestIfFriendTouchesBadThing(int x, int y)
14200 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14203 void TestIfBadThingTouchesFriend(int x, int y)
14205 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14208 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
14210 int i, kill_x = bad_x, kill_y = bad_y;
14211 struct XY *xy = xy_topdown;
14213 for (i = 0; i < NUM_DIRECTIONS; i++)
14217 x = bad_x + xy[i].x;
14218 y = bad_y + xy[i].y;
14219 if (!IN_LEV_FIELD(x, y))
14222 element = Tile[x][y];
14223 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14224 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14232 if (kill_x != bad_x || kill_y != bad_y)
14233 Bang(bad_x, bad_y);
14236 void KillPlayer(struct PlayerInfo *player)
14238 int jx = player->jx, jy = player->jy;
14240 if (!player->active)
14244 Debug("game:playing:KillPlayer",
14245 "0: killed == %d, active == %d, reanimated == %d",
14246 player->killed, player->active, player->reanimated);
14249 /* the following code was introduced to prevent an infinite loop when calling
14251 -> CheckTriggeredElementChangeExt()
14252 -> ExecuteCustomElementAction()
14254 -> (infinitely repeating the above sequence of function calls)
14255 which occurs when killing the player while having a CE with the setting
14256 "kill player X when explosion of <player X>"; the solution using a new
14257 field "player->killed" was chosen for backwards compatibility, although
14258 clever use of the fields "player->active" etc. would probably also work */
14260 if (player->killed)
14264 player->killed = TRUE;
14266 // remove accessible field at the player's position
14267 RemoveField(jx, jy);
14269 // deactivate shield (else Bang()/Explode() would not work right)
14270 player->shield_normal_time_left = 0;
14271 player->shield_deadly_time_left = 0;
14274 Debug("game:playing:KillPlayer",
14275 "1: killed == %d, active == %d, reanimated == %d",
14276 player->killed, player->active, player->reanimated);
14282 Debug("game:playing:KillPlayer",
14283 "2: killed == %d, active == %d, reanimated == %d",
14284 player->killed, player->active, player->reanimated);
14287 if (player->reanimated) // killed player may have been reanimated
14288 player->killed = player->reanimated = FALSE;
14290 BuryPlayer(player);
14293 static void KillPlayerUnlessEnemyProtected(int x, int y)
14295 if (!PLAYER_ENEMY_PROTECTED(x, y))
14296 KillPlayer(PLAYERINFO(x, y));
14299 static void KillPlayerUnlessExplosionProtected(int x, int y)
14301 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14302 KillPlayer(PLAYERINFO(x, y));
14305 void BuryPlayer(struct PlayerInfo *player)
14307 int jx = player->jx, jy = player->jy;
14309 if (!player->active)
14312 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14314 RemovePlayer(player);
14316 player->buried = TRUE;
14318 if (game.all_players_gone)
14319 game.GameOver = TRUE;
14322 void RemovePlayer(struct PlayerInfo *player)
14324 int jx = player->jx, jy = player->jy;
14325 int i, found = FALSE;
14327 player->present = FALSE;
14328 player->active = FALSE;
14330 // required for some CE actions (even if the player is not active anymore)
14331 player->MovPos = 0;
14333 if (!ExplodeField[jx][jy])
14334 StorePlayer[jx][jy] = 0;
14336 if (player->is_moving)
14337 TEST_DrawLevelField(player->last_jx, player->last_jy);
14339 for (i = 0; i < MAX_PLAYERS; i++)
14340 if (stored_player[i].active)
14345 game.all_players_gone = TRUE;
14346 game.GameOver = TRUE;
14349 game.exit_x = game.robot_wheel_x = jx;
14350 game.exit_y = game.robot_wheel_y = jy;
14353 void ExitPlayer(struct PlayerInfo *player)
14355 DrawPlayer(player); // needed here only to cleanup last field
14356 RemovePlayer(player);
14358 if (game.players_still_needed > 0)
14359 game.players_still_needed--;
14362 static void SetFieldForSnapping(int x, int y, int element, int direction,
14363 int player_index_bit)
14365 struct ElementInfo *ei = &element_info[element];
14366 int direction_bit = MV_DIR_TO_BIT(direction);
14367 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14368 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14369 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14371 Tile[x][y] = EL_ELEMENT_SNAPPING;
14372 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14373 MovDir[x][y] = direction;
14374 Store[x][y] = element;
14375 Store2[x][y] = player_index_bit;
14377 ResetGfxAnimation(x, y);
14379 GfxElement[x][y] = element;
14380 GfxAction[x][y] = action;
14381 GfxDir[x][y] = direction;
14382 GfxFrame[x][y] = -1;
14385 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14386 int player_index_bit)
14388 TestIfElementTouchesCustomElement(x, y); // for empty space
14390 if (level.finish_dig_collect)
14392 int dig_side = MV_DIR_OPPOSITE(direction);
14393 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14394 CE_PLAYER_COLLECTS_X);
14396 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14397 player_index_bit, dig_side);
14398 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14399 player_index_bit, dig_side);
14404 =============================================================================
14405 checkDiagonalPushing()
14406 -----------------------------------------------------------------------------
14407 check if diagonal input device direction results in pushing of object
14408 (by checking if the alternative direction is walkable, diggable, ...)
14409 =============================================================================
14412 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14413 int x, int y, int real_dx, int real_dy)
14415 int jx, jy, dx, dy, xx, yy;
14417 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14420 // diagonal direction: check alternative direction
14425 xx = jx + (dx == 0 ? real_dx : 0);
14426 yy = jy + (dy == 0 ? real_dy : 0);
14428 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14432 =============================================================================
14434 -----------------------------------------------------------------------------
14435 x, y: field next to player (non-diagonal) to try to dig to
14436 real_dx, real_dy: direction as read from input device (can be diagonal)
14437 =============================================================================
14440 static int DigField(struct PlayerInfo *player,
14441 int oldx, int oldy, int x, int y,
14442 int real_dx, int real_dy, int mode)
14444 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14445 boolean player_was_pushing = player->is_pushing;
14446 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14447 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14448 int jx = oldx, jy = oldy;
14449 int dx = x - jx, dy = y - jy;
14450 int nextx = x + dx, nexty = y + dy;
14451 int move_direction = (dx == -1 ? MV_LEFT :
14452 dx == +1 ? MV_RIGHT :
14454 dy == +1 ? MV_DOWN : MV_NONE);
14455 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14456 int dig_side = MV_DIR_OPPOSITE(move_direction);
14457 int old_element = Tile[jx][jy];
14458 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14461 if (is_player) // function can also be called by EL_PENGUIN
14463 if (player->MovPos == 0)
14465 player->is_digging = FALSE;
14466 player->is_collecting = FALSE;
14469 if (player->MovPos == 0) // last pushing move finished
14470 player->is_pushing = FALSE;
14472 if (mode == DF_NO_PUSH) // player just stopped pushing
14474 player->is_switching = FALSE;
14475 player->push_delay = -1;
14477 return MP_NO_ACTION;
14480 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14481 old_element = Back[jx][jy];
14483 // in case of element dropped at player position, check background
14484 else if (Back[jx][jy] != EL_EMPTY &&
14485 game.engine_version >= VERSION_IDENT(2,2,0,0))
14486 old_element = Back[jx][jy];
14488 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14489 return MP_NO_ACTION; // field has no opening in this direction
14491 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction))
14492 return MP_NO_ACTION; // field has no opening in this direction
14494 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14498 Tile[jx][jy] = player->artwork_element;
14499 InitMovingField(jx, jy, MV_DOWN);
14500 Store[jx][jy] = EL_ACID;
14501 ContinueMoving(jx, jy);
14502 BuryPlayer(player);
14504 return MP_DONT_RUN_INTO;
14507 if (player_can_move && DONT_RUN_INTO(element))
14509 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14511 return MP_DONT_RUN_INTO;
14514 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14515 return MP_NO_ACTION;
14517 collect_count = element_info[element].collect_count_initial;
14519 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14520 return MP_NO_ACTION;
14522 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14523 player_can_move = player_can_move_or_snap;
14525 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14526 game.engine_version >= VERSION_IDENT(2,2,0,0))
14528 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14529 player->index_bit, dig_side);
14530 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14531 player->index_bit, dig_side);
14533 if (element == EL_DC_LANDMINE)
14536 if (Tile[x][y] != element) // field changed by snapping
14539 return MP_NO_ACTION;
14542 if (player->gravity && is_player && !player->is_auto_moving &&
14543 canFallDown(player) && move_direction != MV_DOWN &&
14544 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14545 return MP_NO_ACTION; // player cannot walk here due to gravity
14547 if (player_can_move &&
14548 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14550 int sound_element = SND_ELEMENT(element);
14551 int sound_action = ACTION_WALKING;
14553 if (IS_RND_GATE(element))
14555 if (!player->key[RND_GATE_NR(element)])
14556 return MP_NO_ACTION;
14558 else if (IS_RND_GATE_GRAY(element))
14560 if (!player->key[RND_GATE_GRAY_NR(element)])
14561 return MP_NO_ACTION;
14563 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14565 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14566 return MP_NO_ACTION;
14568 else if (element == EL_EXIT_OPEN ||
14569 element == EL_EM_EXIT_OPEN ||
14570 element == EL_EM_EXIT_OPENING ||
14571 element == EL_STEEL_EXIT_OPEN ||
14572 element == EL_EM_STEEL_EXIT_OPEN ||
14573 element == EL_EM_STEEL_EXIT_OPENING ||
14574 element == EL_SP_EXIT_OPEN ||
14575 element == EL_SP_EXIT_OPENING)
14577 sound_action = ACTION_PASSING; // player is passing exit
14579 else if (element == EL_EMPTY)
14581 sound_action = ACTION_MOVING; // nothing to walk on
14584 // play sound from background or player, whatever is available
14585 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14586 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14588 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14590 else if (player_can_move &&
14591 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14593 if (!ACCESS_FROM(element, opposite_direction))
14594 return MP_NO_ACTION; // field not accessible from this direction
14596 if (CAN_MOVE(element)) // only fixed elements can be passed!
14597 return MP_NO_ACTION;
14599 if (IS_EM_GATE(element))
14601 if (!player->key[EM_GATE_NR(element)])
14602 return MP_NO_ACTION;
14604 else if (IS_EM_GATE_GRAY(element))
14606 if (!player->key[EM_GATE_GRAY_NR(element)])
14607 return MP_NO_ACTION;
14609 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14611 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14612 return MP_NO_ACTION;
14614 else if (IS_EMC_GATE(element))
14616 if (!player->key[EMC_GATE_NR(element)])
14617 return MP_NO_ACTION;
14619 else if (IS_EMC_GATE_GRAY(element))
14621 if (!player->key[EMC_GATE_GRAY_NR(element)])
14622 return MP_NO_ACTION;
14624 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14626 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14627 return MP_NO_ACTION;
14629 else if (element == EL_DC_GATE_WHITE ||
14630 element == EL_DC_GATE_WHITE_GRAY ||
14631 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14633 if (player->num_white_keys == 0)
14634 return MP_NO_ACTION;
14636 player->num_white_keys--;
14638 else if (IS_SP_PORT(element))
14640 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14641 element == EL_SP_GRAVITY_PORT_RIGHT ||
14642 element == EL_SP_GRAVITY_PORT_UP ||
14643 element == EL_SP_GRAVITY_PORT_DOWN)
14644 player->gravity = !player->gravity;
14645 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14646 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14647 element == EL_SP_GRAVITY_ON_PORT_UP ||
14648 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14649 player->gravity = TRUE;
14650 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14651 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14652 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14653 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14654 player->gravity = FALSE;
14657 // automatically move to the next field with double speed
14658 player->programmed_action = move_direction;
14660 if (player->move_delay_reset_counter == 0)
14662 player->move_delay_reset_counter = 2; // two double speed steps
14664 DOUBLE_PLAYER_SPEED(player);
14667 PlayLevelSoundAction(x, y, ACTION_PASSING);
14669 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14673 if (mode != DF_SNAP)
14675 GfxElement[x][y] = GFX_ELEMENT(element);
14676 player->is_digging = TRUE;
14679 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14681 // use old behaviour for old levels (digging)
14682 if (!level.finish_dig_collect)
14684 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14685 player->index_bit, dig_side);
14687 // if digging triggered player relocation, finish digging tile
14688 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14689 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14692 if (mode == DF_SNAP)
14694 if (level.block_snap_field)
14695 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14697 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14699 // use old behaviour for old levels (snapping)
14700 if (!level.finish_dig_collect)
14701 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14702 player->index_bit, dig_side);
14705 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14709 if (is_player && mode != DF_SNAP)
14711 GfxElement[x][y] = element;
14712 player->is_collecting = TRUE;
14715 if (element == EL_SPEED_PILL)
14717 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14719 else if (element == EL_EXTRA_TIME && level.time > 0)
14721 TimeLeft += level.extra_time;
14723 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14725 DisplayGameControlValues();
14727 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14729 int shield_time = (element == EL_SHIELD_DEADLY ?
14730 level.shield_deadly_time :
14731 level.shield_normal_time);
14733 player->shield_normal_time_left += shield_time;
14734 if (element == EL_SHIELD_DEADLY)
14735 player->shield_deadly_time_left += shield_time;
14737 else if (element == EL_DYNAMITE ||
14738 element == EL_EM_DYNAMITE ||
14739 element == EL_SP_DISK_RED)
14741 if (player->inventory_size < MAX_INVENTORY_SIZE)
14742 player->inventory_element[player->inventory_size++] = element;
14744 DrawGameDoorValues();
14746 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14748 player->dynabomb_count++;
14749 player->dynabombs_left++;
14751 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14753 player->dynabomb_size++;
14755 else if (element == EL_DYNABOMB_INCREASE_POWER)
14757 player->dynabomb_xl = TRUE;
14759 else if (IS_KEY(element))
14761 player->key[KEY_NR(element)] = TRUE;
14763 DrawGameDoorValues();
14765 else if (element == EL_DC_KEY_WHITE)
14767 player->num_white_keys++;
14769 // display white keys?
14770 // DrawGameDoorValues();
14772 else if (IS_ENVELOPE(element))
14774 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14776 if (!wait_for_snapping)
14777 player->show_envelope = element;
14779 else if (element == EL_EMC_LENSES)
14781 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14783 RedrawAllInvisibleElementsForLenses();
14785 else if (element == EL_EMC_MAGNIFIER)
14787 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14789 RedrawAllInvisibleElementsForMagnifier();
14791 else if (IS_DROPPABLE(element) ||
14792 IS_THROWABLE(element)) // can be collected and dropped
14796 if (collect_count == 0)
14797 player->inventory_infinite_element = element;
14799 for (i = 0; i < collect_count; i++)
14800 if (player->inventory_size < MAX_INVENTORY_SIZE)
14801 player->inventory_element[player->inventory_size++] = element;
14803 DrawGameDoorValues();
14805 else if (collect_count > 0)
14807 game.gems_still_needed -= collect_count;
14808 if (game.gems_still_needed < 0)
14809 game.gems_still_needed = 0;
14811 game.snapshot.collected_item = TRUE;
14813 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14815 DisplayGameControlValues();
14818 RaiseScoreElement(element);
14819 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14821 // use old behaviour for old levels (collecting)
14822 if (!level.finish_dig_collect && is_player)
14824 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14825 player->index_bit, dig_side);
14827 // if collecting triggered player relocation, finish collecting tile
14828 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14829 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14832 if (mode == DF_SNAP)
14834 if (level.block_snap_field)
14835 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14837 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14839 // use old behaviour for old levels (snapping)
14840 if (!level.finish_dig_collect)
14841 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14842 player->index_bit, dig_side);
14845 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14847 if (mode == DF_SNAP && element != EL_BD_ROCK)
14848 return MP_NO_ACTION;
14850 if (CAN_FALL(element) && dy)
14851 return MP_NO_ACTION;
14853 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14854 !(element == EL_SPRING && level.use_spring_bug))
14855 return MP_NO_ACTION;
14857 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14858 ((move_direction & MV_VERTICAL &&
14859 ((element_info[element].move_pattern & MV_LEFT &&
14860 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14861 (element_info[element].move_pattern & MV_RIGHT &&
14862 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14863 (move_direction & MV_HORIZONTAL &&
14864 ((element_info[element].move_pattern & MV_UP &&
14865 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14866 (element_info[element].move_pattern & MV_DOWN &&
14867 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14868 return MP_NO_ACTION;
14870 // do not push elements already moving away faster than player
14871 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14872 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14873 return MP_NO_ACTION;
14875 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14877 if (player->push_delay_value == -1 || !player_was_pushing)
14878 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14880 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14882 if (player->push_delay_value == -1)
14883 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14885 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14887 if (!player->is_pushing)
14888 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14891 player->is_pushing = TRUE;
14892 player->is_active = TRUE;
14894 if (!(IN_LEV_FIELD(nextx, nexty) &&
14895 (IS_FREE(nextx, nexty) ||
14896 (IS_SB_ELEMENT(element) &&
14897 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14898 (IS_CUSTOM_ELEMENT(element) &&
14899 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14900 return MP_NO_ACTION;
14902 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14903 return MP_NO_ACTION;
14905 if (player->push_delay == -1) // new pushing; restart delay
14906 player->push_delay = 0;
14908 if (player->push_delay < player->push_delay_value &&
14909 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14910 element != EL_SPRING && element != EL_BALLOON)
14912 // make sure that there is no move delay before next try to push
14913 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14914 player->move_delay = 0;
14916 return MP_NO_ACTION;
14919 if (IS_CUSTOM_ELEMENT(element) &&
14920 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14922 if (!DigFieldByCE(nextx, nexty, element))
14923 return MP_NO_ACTION;
14926 if (IS_SB_ELEMENT(element))
14928 boolean sokoban_task_solved = FALSE;
14930 if (element == EL_SOKOBAN_FIELD_FULL)
14932 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14934 IncrementSokobanFieldsNeeded();
14935 IncrementSokobanObjectsNeeded();
14938 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14940 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14942 DecrementSokobanFieldsNeeded();
14943 DecrementSokobanObjectsNeeded();
14945 // sokoban object was pushed from empty field to sokoban field
14946 if (Back[x][y] == EL_EMPTY)
14947 sokoban_task_solved = TRUE;
14950 Tile[x][y] = EL_SOKOBAN_OBJECT;
14952 if (Back[x][y] == Back[nextx][nexty])
14953 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14954 else if (Back[x][y] != 0)
14955 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14958 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14961 if (sokoban_task_solved &&
14962 game.sokoban_fields_still_needed == 0 &&
14963 game.sokoban_objects_still_needed == 0 &&
14964 level.auto_exit_sokoban)
14966 game.players_still_needed = 0;
14970 PlaySound(SND_GAME_SOKOBAN_SOLVING);
14974 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14976 InitMovingField(x, y, move_direction);
14977 GfxAction[x][y] = ACTION_PUSHING;
14979 if (mode == DF_SNAP)
14980 ContinueMoving(x, y);
14982 MovPos[x][y] = (dx != 0 ? dx : dy);
14984 Pushed[x][y] = TRUE;
14985 Pushed[nextx][nexty] = TRUE;
14987 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14988 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14990 player->push_delay_value = -1; // get new value later
14992 // check for element change _after_ element has been pushed
14993 if (game.use_change_when_pushing_bug)
14995 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14996 player->index_bit, dig_side);
14997 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14998 player->index_bit, dig_side);
15001 else if (IS_SWITCHABLE(element))
15003 if (PLAYER_SWITCHING(player, x, y))
15005 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15006 player->index_bit, dig_side);
15011 player->is_switching = TRUE;
15012 player->switch_x = x;
15013 player->switch_y = y;
15015 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15017 if (element == EL_ROBOT_WHEEL)
15019 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
15021 game.robot_wheel_x = x;
15022 game.robot_wheel_y = y;
15023 game.robot_wheel_active = TRUE;
15025 TEST_DrawLevelField(x, y);
15027 else if (element == EL_SP_TERMINAL)
15031 SCAN_PLAYFIELD(xx, yy)
15033 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
15037 else if (Tile[xx][yy] == EL_SP_TERMINAL)
15039 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
15041 ResetGfxAnimation(xx, yy);
15042 TEST_DrawLevelField(xx, yy);
15046 else if (IS_BELT_SWITCH(element))
15048 ToggleBeltSwitch(x, y);
15050 else if (element == EL_SWITCHGATE_SWITCH_UP ||
15051 element == EL_SWITCHGATE_SWITCH_DOWN ||
15052 element == EL_DC_SWITCHGATE_SWITCH_UP ||
15053 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
15055 ToggleSwitchgateSwitch();
15057 else if (element == EL_LIGHT_SWITCH ||
15058 element == EL_LIGHT_SWITCH_ACTIVE)
15060 ToggleLightSwitch(x, y);
15062 else if (element == EL_TIMEGATE_SWITCH ||
15063 element == EL_DC_TIMEGATE_SWITCH)
15065 ActivateTimegateSwitch(x, y);
15067 else if (element == EL_BALLOON_SWITCH_LEFT ||
15068 element == EL_BALLOON_SWITCH_RIGHT ||
15069 element == EL_BALLOON_SWITCH_UP ||
15070 element == EL_BALLOON_SWITCH_DOWN ||
15071 element == EL_BALLOON_SWITCH_NONE ||
15072 element == EL_BALLOON_SWITCH_ANY)
15074 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
15075 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
15076 element == EL_BALLOON_SWITCH_UP ? MV_UP :
15077 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
15078 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
15081 else if (element == EL_LAMP)
15083 Tile[x][y] = EL_LAMP_ACTIVE;
15084 game.lights_still_needed--;
15086 ResetGfxAnimation(x, y);
15087 TEST_DrawLevelField(x, y);
15089 else if (element == EL_TIME_ORB_FULL)
15091 Tile[x][y] = EL_TIME_ORB_EMPTY;
15093 if (level.time > 0 || level.use_time_orb_bug)
15095 TimeLeft += level.time_orb_time;
15096 game.no_level_time_limit = FALSE;
15098 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
15100 DisplayGameControlValues();
15103 ResetGfxAnimation(x, y);
15104 TEST_DrawLevelField(x, y);
15106 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
15107 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15111 game.ball_active = !game.ball_active;
15113 SCAN_PLAYFIELD(xx, yy)
15115 int e = Tile[xx][yy];
15117 if (game.ball_active)
15119 if (e == EL_EMC_MAGIC_BALL)
15120 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
15121 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
15122 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
15126 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
15127 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
15128 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15129 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
15134 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15135 player->index_bit, dig_side);
15137 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15138 player->index_bit, dig_side);
15140 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15141 player->index_bit, dig_side);
15147 if (!PLAYER_SWITCHING(player, x, y))
15149 player->is_switching = TRUE;
15150 player->switch_x = x;
15151 player->switch_y = y;
15153 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
15154 player->index_bit, dig_side);
15155 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15156 player->index_bit, dig_side);
15158 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
15159 player->index_bit, dig_side);
15160 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15161 player->index_bit, dig_side);
15164 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
15165 player->index_bit, dig_side);
15166 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15167 player->index_bit, dig_side);
15169 return MP_NO_ACTION;
15172 player->push_delay = -1;
15174 if (is_player) // function can also be called by EL_PENGUIN
15176 if (Tile[x][y] != element) // really digged/collected something
15178 player->is_collecting = !player->is_digging;
15179 player->is_active = TRUE;
15181 player->last_removed_element = element;
15188 static boolean DigFieldByCE(int x, int y, int digging_element)
15190 int element = Tile[x][y];
15192 if (!IS_FREE(x, y))
15194 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
15195 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
15198 // no element can dig solid indestructible elements
15199 if (IS_INDESTRUCTIBLE(element) &&
15200 !IS_DIGGABLE(element) &&
15201 !IS_COLLECTIBLE(element))
15204 if (AmoebaNr[x][y] &&
15205 (element == EL_AMOEBA_FULL ||
15206 element == EL_BD_AMOEBA ||
15207 element == EL_AMOEBA_GROWING))
15209 AmoebaCnt[AmoebaNr[x][y]]--;
15210 AmoebaCnt2[AmoebaNr[x][y]]--;
15213 if (IS_MOVING(x, y))
15214 RemoveMovingField(x, y);
15218 TEST_DrawLevelField(x, y);
15221 // if digged element was about to explode, prevent the explosion
15222 ExplodeField[x][y] = EX_TYPE_NONE;
15224 PlayLevelSoundAction(x, y, action);
15227 Store[x][y] = EL_EMPTY;
15229 // this makes it possible to leave the removed element again
15230 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15231 Store[x][y] = element;
15236 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15238 int jx = player->jx, jy = player->jy;
15239 int x = jx + dx, y = jy + dy;
15240 int snap_direction = (dx == -1 ? MV_LEFT :
15241 dx == +1 ? MV_RIGHT :
15243 dy == +1 ? MV_DOWN : MV_NONE);
15244 boolean can_continue_snapping = (level.continuous_snapping &&
15245 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15247 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15250 if (!player->active || !IN_LEV_FIELD(x, y))
15258 if (player->MovPos == 0)
15259 player->is_pushing = FALSE;
15261 player->is_snapping = FALSE;
15263 if (player->MovPos == 0)
15265 player->is_moving = FALSE;
15266 player->is_digging = FALSE;
15267 player->is_collecting = FALSE;
15273 // prevent snapping with already pressed snap key when not allowed
15274 if (player->is_snapping && !can_continue_snapping)
15277 player->MovDir = snap_direction;
15279 if (player->MovPos == 0)
15281 player->is_moving = FALSE;
15282 player->is_digging = FALSE;
15283 player->is_collecting = FALSE;
15286 player->is_dropping = FALSE;
15287 player->is_dropping_pressed = FALSE;
15288 player->drop_pressed_delay = 0;
15290 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15293 player->is_snapping = TRUE;
15294 player->is_active = TRUE;
15296 if (player->MovPos == 0)
15298 player->is_moving = FALSE;
15299 player->is_digging = FALSE;
15300 player->is_collecting = FALSE;
15303 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15304 TEST_DrawLevelField(player->last_jx, player->last_jy);
15306 TEST_DrawLevelField(x, y);
15311 static boolean DropElement(struct PlayerInfo *player)
15313 int old_element, new_element;
15314 int dropx = player->jx, dropy = player->jy;
15315 int drop_direction = player->MovDir;
15316 int drop_side = drop_direction;
15317 int drop_element = get_next_dropped_element(player);
15319 /* do not drop an element on top of another element; when holding drop key
15320 pressed without moving, dropped element must move away before the next
15321 element can be dropped (this is especially important if the next element
15322 is dynamite, which can be placed on background for historical reasons) */
15323 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15326 if (IS_THROWABLE(drop_element))
15328 dropx += GET_DX_FROM_DIR(drop_direction);
15329 dropy += GET_DY_FROM_DIR(drop_direction);
15331 if (!IN_LEV_FIELD(dropx, dropy))
15335 old_element = Tile[dropx][dropy]; // old element at dropping position
15336 new_element = drop_element; // default: no change when dropping
15338 // check if player is active, not moving and ready to drop
15339 if (!player->active || player->MovPos || player->drop_delay > 0)
15342 // check if player has anything that can be dropped
15343 if (new_element == EL_UNDEFINED)
15346 // only set if player has anything that can be dropped
15347 player->is_dropping_pressed = TRUE;
15349 // check if drop key was pressed long enough for EM style dynamite
15350 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15353 // check if anything can be dropped at the current position
15354 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15357 // collected custom elements can only be dropped on empty fields
15358 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15361 if (old_element != EL_EMPTY)
15362 Back[dropx][dropy] = old_element; // store old element on this field
15364 ResetGfxAnimation(dropx, dropy);
15365 ResetRandomAnimationValue(dropx, dropy);
15367 if (player->inventory_size > 0 ||
15368 player->inventory_infinite_element != EL_UNDEFINED)
15370 if (player->inventory_size > 0)
15372 player->inventory_size--;
15374 DrawGameDoorValues();
15376 if (new_element == EL_DYNAMITE)
15377 new_element = EL_DYNAMITE_ACTIVE;
15378 else if (new_element == EL_EM_DYNAMITE)
15379 new_element = EL_EM_DYNAMITE_ACTIVE;
15380 else if (new_element == EL_SP_DISK_RED)
15381 new_element = EL_SP_DISK_RED_ACTIVE;
15384 Tile[dropx][dropy] = new_element;
15386 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15387 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15388 el2img(Tile[dropx][dropy]), 0);
15390 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15392 // needed if previous element just changed to "empty" in the last frame
15393 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15395 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15396 player->index_bit, drop_side);
15397 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15399 player->index_bit, drop_side);
15401 TestIfElementTouchesCustomElement(dropx, dropy);
15403 else // player is dropping a dyna bomb
15405 player->dynabombs_left--;
15407 Tile[dropx][dropy] = new_element;
15409 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15410 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15411 el2img(Tile[dropx][dropy]), 0);
15413 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15416 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15417 InitField_WithBug1(dropx, dropy, FALSE);
15419 new_element = Tile[dropx][dropy]; // element might have changed
15421 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15422 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15424 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15425 MovDir[dropx][dropy] = drop_direction;
15427 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15429 // do not cause impact style collision by dropping elements that can fall
15430 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15433 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15434 player->is_dropping = TRUE;
15436 player->drop_pressed_delay = 0;
15437 player->is_dropping_pressed = FALSE;
15439 player->drop_x = dropx;
15440 player->drop_y = dropy;
15445 // ----------------------------------------------------------------------------
15446 // game sound playing functions
15447 // ----------------------------------------------------------------------------
15449 static int *loop_sound_frame = NULL;
15450 static int *loop_sound_volume = NULL;
15452 void InitPlayLevelSound(void)
15454 int num_sounds = getSoundListSize();
15456 checked_free(loop_sound_frame);
15457 checked_free(loop_sound_volume);
15459 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15460 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15463 static void PlayLevelSoundExt(int x, int y, int nr, boolean is_loop_sound)
15465 int sx = SCREENX(x), sy = SCREENY(y);
15466 int volume, stereo_position;
15467 int max_distance = 8;
15468 int type = (is_loop_sound ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15470 if ((!setup.sound_simple && !is_loop_sound) ||
15471 (!setup.sound_loops && is_loop_sound))
15474 if (!IN_LEV_FIELD(x, y) ||
15475 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15476 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15479 volume = SOUND_MAX_VOLUME;
15481 if (!IN_SCR_FIELD(sx, sy))
15483 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15484 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15486 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15489 stereo_position = (SOUND_MAX_LEFT +
15490 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15491 (SCR_FIELDX + 2 * max_distance));
15495 /* This assures that quieter loop sounds do not overwrite louder ones,
15496 while restarting sound volume comparison with each new game frame. */
15498 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15501 loop_sound_volume[nr] = volume;
15502 loop_sound_frame[nr] = FrameCounter;
15505 PlaySoundExt(nr, volume, stereo_position, type);
15508 static void PlayLevelSound(int x, int y, int nr)
15510 PlayLevelSoundExt(x, y, nr, IS_LOOP_SOUND(nr));
15513 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15515 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15516 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15517 y < LEVELY(BY1) ? LEVELY(BY1) :
15518 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15522 static void PlayLevelSoundAction(int x, int y, int action)
15524 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15527 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15529 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15531 if (sound_effect != SND_UNDEFINED)
15532 PlayLevelSound(x, y, sound_effect);
15535 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15538 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15540 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15541 PlayLevelSound(x, y, sound_effect);
15544 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15546 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15548 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15549 PlayLevelSound(x, y, sound_effect);
15552 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15554 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15556 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15557 StopSound(sound_effect);
15560 static int getLevelMusicNr(void)
15562 int level_pos = level_nr - leveldir_current->first_level;
15564 if (levelset.music[level_nr] != MUS_UNDEFINED)
15565 return levelset.music[level_nr]; // from config file
15567 return MAP_NOCONF_MUSIC(level_pos); // from music dir
15570 static void FadeLevelSounds(void)
15575 static void FadeLevelMusic(void)
15577 int music_nr = getLevelMusicNr();
15578 char *curr_music = getCurrentlyPlayingMusicFilename();
15579 char *next_music = getMusicInfoEntryFilename(music_nr);
15581 if (!strEqual(curr_music, next_music))
15585 void FadeLevelSoundsAndMusic(void)
15591 static void PlayLevelMusic(void)
15593 int music_nr = getLevelMusicNr();
15594 char *curr_music = getCurrentlyPlayingMusicFilename();
15595 char *next_music = getMusicInfoEntryFilename(music_nr);
15597 if (!strEqual(curr_music, next_music))
15598 PlayMusicLoop(music_nr);
15601 static int getSoundAction_BD(int sample)
15605 case GD_S_STONE_PUSHING:
15606 case GD_S_MEGA_STONE_PUSHING:
15607 case GD_S_FLYING_STONE_PUSHING:
15608 case GD_S_WAITING_STONE_PUSHING:
15609 case GD_S_CHASING_STONE_PUSHING:
15610 case GD_S_NUT_PUSHING:
15611 case GD_S_NITRO_PACK_PUSHING:
15612 case GD_S_BLADDER_PUSHING:
15613 case GD_S_BOX_PUSHING:
15614 return ACTION_PUSHING;
15616 case GD_S_STONE_FALLING:
15617 case GD_S_MEGA_STONE_FALLING:
15618 case GD_S_FLYING_STONE_FALLING:
15619 case GD_S_NUT_FALLING:
15620 case GD_S_DIRT_BALL_FALLING:
15621 case GD_S_DIRT_LOOSE_FALLING:
15622 case GD_S_NITRO_PACK_FALLING:
15623 case GD_S_FALLING_WALL_FALLING:
15624 return ACTION_FALLING;
15626 case GD_S_STONE_IMPACT:
15627 case GD_S_MEGA_STONE_IMPACT:
15628 case GD_S_FLYING_STONE_IMPACT:
15629 case GD_S_NUT_IMPACT:
15630 case GD_S_DIRT_BALL_IMPACT:
15631 case GD_S_DIRT_LOOSE_IMPACT:
15632 case GD_S_NITRO_PACK_IMPACT:
15633 case GD_S_FALLING_WALL_IMPACT:
15634 return ACTION_IMPACT;
15636 case GD_S_NUT_CRACKING:
15637 return ACTION_BREAKING;
15639 case GD_S_EXPANDING_WALL:
15640 case GD_S_WALL_REAPPEARING:
15643 case GD_S_ACID_SPREADING:
15644 return ACTION_GROWING;
15646 case GD_S_DIAMOND_COLLECTING:
15647 case GD_S_FLYING_DIAMOND_COLLECTING:
15648 case GD_S_SKELETON_COLLECTING:
15649 case GD_S_PNEUMATIC_COLLECTING:
15650 case GD_S_BOMB_COLLECTING:
15651 case GD_S_CLOCK_COLLECTING:
15652 case GD_S_SWEET_COLLECTING:
15653 case GD_S_KEY_COLLECTING:
15654 case GD_S_DIAMOND_KEY_COLLECTING:
15655 return ACTION_COLLECTING;
15657 case GD_S_BOMB_PLACING:
15658 case GD_S_REPLICATOR:
15659 return ACTION_DROPPING;
15661 case GD_S_BLADDER_MOVING:
15662 return ACTION_MOVING;
15664 case GD_S_BLADDER_SPENDER:
15665 case GD_S_BLADDER_CONVERTING:
15666 case GD_S_GRAVITY_CHANGING:
15667 return ACTION_CHANGING;
15669 case GD_S_BITER_EATING:
15670 return ACTION_EATING;
15672 case GD_S_DOOR_OPENING:
15673 case GD_S_CRACKING:
15674 return ACTION_OPENING;
15676 case GD_S_DIRT_WALKING:
15677 return ACTION_DIGGING;
15679 case GD_S_EMPTY_WALKING:
15680 return ACTION_WALKING;
15682 case GD_S_SWITCH_BITER:
15683 case GD_S_SWITCH_CREATURES:
15684 case GD_S_SWITCH_GRAVITY:
15685 case GD_S_SWITCH_EXPANDING:
15686 case GD_S_SWITCH_CONVEYOR:
15687 case GD_S_SWITCH_REPLICATOR:
15688 case GD_S_STIRRING:
15689 return ACTION_ACTIVATING;
15691 case GD_S_TELEPORTER:
15692 return ACTION_PASSING;
15694 case GD_S_EXPLODING:
15695 case GD_S_BOMB_EXPLODING:
15696 case GD_S_GHOST_EXPLODING:
15697 case GD_S_VOODOO_EXPLODING:
15698 case GD_S_NITRO_PACK_EXPLODING:
15699 return ACTION_EXPLODING;
15701 case GD_S_COVERING:
15703 case GD_S_MAGIC_WALL:
15704 case GD_S_PNEUMATIC_HAMMER:
15706 return ACTION_ACTIVE;
15708 case GD_S_DIAMOND_FALLING_RANDOM:
15709 case GD_S_DIAMOND_FALLING_1:
15710 case GD_S_DIAMOND_FALLING_2:
15711 case GD_S_DIAMOND_FALLING_3:
15712 case GD_S_DIAMOND_FALLING_4:
15713 case GD_S_DIAMOND_FALLING_5:
15714 case GD_S_DIAMOND_FALLING_6:
15715 case GD_S_DIAMOND_FALLING_7:
15716 case GD_S_DIAMOND_FALLING_8:
15717 case GD_S_DIAMOND_IMPACT_RANDOM:
15718 case GD_S_DIAMOND_IMPACT_1:
15719 case GD_S_DIAMOND_IMPACT_2:
15720 case GD_S_DIAMOND_IMPACT_3:
15721 case GD_S_DIAMOND_IMPACT_4:
15722 case GD_S_DIAMOND_IMPACT_5:
15723 case GD_S_DIAMOND_IMPACT_6:
15724 case GD_S_DIAMOND_IMPACT_7:
15725 case GD_S_DIAMOND_IMPACT_8:
15726 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15727 case GD_S_FLYING_DIAMOND_FALLING_1:
15728 case GD_S_FLYING_DIAMOND_FALLING_2:
15729 case GD_S_FLYING_DIAMOND_FALLING_3:
15730 case GD_S_FLYING_DIAMOND_FALLING_4:
15731 case GD_S_FLYING_DIAMOND_FALLING_5:
15732 case GD_S_FLYING_DIAMOND_FALLING_6:
15733 case GD_S_FLYING_DIAMOND_FALLING_7:
15734 case GD_S_FLYING_DIAMOND_FALLING_8:
15735 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15736 case GD_S_FLYING_DIAMOND_IMPACT_1:
15737 case GD_S_FLYING_DIAMOND_IMPACT_2:
15738 case GD_S_FLYING_DIAMOND_IMPACT_3:
15739 case GD_S_FLYING_DIAMOND_IMPACT_4:
15740 case GD_S_FLYING_DIAMOND_IMPACT_5:
15741 case GD_S_FLYING_DIAMOND_IMPACT_6:
15742 case GD_S_FLYING_DIAMOND_IMPACT_7:
15743 case GD_S_FLYING_DIAMOND_IMPACT_8:
15744 case GD_S_TIMEOUT_0:
15745 case GD_S_TIMEOUT_1:
15746 case GD_S_TIMEOUT_2:
15747 case GD_S_TIMEOUT_3:
15748 case GD_S_TIMEOUT_4:
15749 case GD_S_TIMEOUT_5:
15750 case GD_S_TIMEOUT_6:
15751 case GD_S_TIMEOUT_7:
15752 case GD_S_TIMEOUT_8:
15753 case GD_S_TIMEOUT_9:
15754 case GD_S_TIMEOUT_10:
15755 case GD_S_BONUS_LIFE:
15756 // trigger special post-processing (and force sound to be non-looping)
15757 return ACTION_OTHER;
15759 case GD_S_AMOEBA_MAGIC:
15760 case GD_S_FINISHED:
15761 // trigger special post-processing (and force sound to be looping)
15762 return ACTION_DEFAULT;
15765 return ACTION_DEFAULT;
15769 static int getSoundEffect_BD(int element_bd, int sample)
15771 int sound_action = getSoundAction_BD(sample);
15772 int sound_effect = element_info[SND_ELEMENT(element_bd)].sound[sound_action];
15776 if (sound_action != ACTION_OTHER &&
15777 sound_action != ACTION_DEFAULT)
15778 return sound_effect;
15780 // special post-processing for some sounds
15783 case GD_S_DIAMOND_FALLING_RANDOM:
15784 case GD_S_DIAMOND_FALLING_1:
15785 case GD_S_DIAMOND_FALLING_2:
15786 case GD_S_DIAMOND_FALLING_3:
15787 case GD_S_DIAMOND_FALLING_4:
15788 case GD_S_DIAMOND_FALLING_5:
15789 case GD_S_DIAMOND_FALLING_6:
15790 case GD_S_DIAMOND_FALLING_7:
15791 case GD_S_DIAMOND_FALLING_8:
15792 nr = (sample == GD_S_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15793 sample - GD_S_DIAMOND_FALLING_1);
15794 sound_effect = SND_BDX_DIAMOND_FALLING_RANDOM_1 + nr;
15796 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15797 sound_effect = SND_BDX_DIAMOND_FALLING;
15800 case GD_S_DIAMOND_IMPACT_RANDOM:
15801 case GD_S_DIAMOND_IMPACT_1:
15802 case GD_S_DIAMOND_IMPACT_2:
15803 case GD_S_DIAMOND_IMPACT_3:
15804 case GD_S_DIAMOND_IMPACT_4:
15805 case GD_S_DIAMOND_IMPACT_5:
15806 case GD_S_DIAMOND_IMPACT_6:
15807 case GD_S_DIAMOND_IMPACT_7:
15808 case GD_S_DIAMOND_IMPACT_8:
15809 nr = (sample == GD_S_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15810 sample - GD_S_DIAMOND_IMPACT_1);
15811 sound_effect = SND_BDX_DIAMOND_IMPACT_RANDOM_1 + nr;
15813 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15814 sound_effect = SND_BDX_DIAMOND_IMPACT;
15817 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15818 case GD_S_FLYING_DIAMOND_FALLING_1:
15819 case GD_S_FLYING_DIAMOND_FALLING_2:
15820 case GD_S_FLYING_DIAMOND_FALLING_3:
15821 case GD_S_FLYING_DIAMOND_FALLING_4:
15822 case GD_S_FLYING_DIAMOND_FALLING_5:
15823 case GD_S_FLYING_DIAMOND_FALLING_6:
15824 case GD_S_FLYING_DIAMOND_FALLING_7:
15825 case GD_S_FLYING_DIAMOND_FALLING_8:
15826 nr = (sample == GD_S_FLYING_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15827 sample - GD_S_FLYING_DIAMOND_FALLING_1);
15828 sound_effect = SND_BDX_FLYING_DIAMOND_FALLING_RANDOM_1 + nr;
15830 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15831 sound_effect = SND_BDX_FLYING_DIAMOND_FALLING;
15834 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15835 case GD_S_FLYING_DIAMOND_IMPACT_1:
15836 case GD_S_FLYING_DIAMOND_IMPACT_2:
15837 case GD_S_FLYING_DIAMOND_IMPACT_3:
15838 case GD_S_FLYING_DIAMOND_IMPACT_4:
15839 case GD_S_FLYING_DIAMOND_IMPACT_5:
15840 case GD_S_FLYING_DIAMOND_IMPACT_6:
15841 case GD_S_FLYING_DIAMOND_IMPACT_7:
15842 case GD_S_FLYING_DIAMOND_IMPACT_8:
15843 nr = (sample == GD_S_FLYING_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15844 sample - GD_S_FLYING_DIAMOND_IMPACT_1);
15845 sound_effect = SND_BDX_FLYING_DIAMOND_IMPACT_RANDOM_1 + nr;
15847 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15848 sound_effect = SND_BDX_FLYING_DIAMOND_IMPACT;
15851 case GD_S_TIMEOUT_0:
15852 case GD_S_TIMEOUT_1:
15853 case GD_S_TIMEOUT_2:
15854 case GD_S_TIMEOUT_3:
15855 case GD_S_TIMEOUT_4:
15856 case GD_S_TIMEOUT_5:
15857 case GD_S_TIMEOUT_6:
15858 case GD_S_TIMEOUT_7:
15859 case GD_S_TIMEOUT_8:
15860 case GD_S_TIMEOUT_9:
15861 case GD_S_TIMEOUT_10:
15862 nr = sample - GD_S_TIMEOUT_0;
15863 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME_0 + nr;
15865 if (getSoundInfoEntryFilename(sound_effect) == NULL && sample != GD_S_TIMEOUT_0)
15866 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME;
15869 case GD_S_BONUS_LIFE:
15870 sound_effect = SND_GAME_HEALTH_BONUS;
15873 case GD_S_AMOEBA_MAGIC:
15874 sound_effect = SND_BDX_AMOEBA_1_OTHER;
15877 case GD_S_FINISHED:
15878 sound_effect = SND_GAME_LEVELTIME_BONUS;
15882 sound_effect = SND_UNDEFINED;
15886 return sound_effect;
15889 void PlayLevelSound_BD(int xx, int yy, int element_bd, int sample)
15891 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15892 int sound_effect = getSoundEffect_BD(element, sample);
15893 int sound_action = getSoundAction_BD(sample);
15894 boolean is_loop_sound = IS_LOOP_SOUND(sound_effect);
15896 int x = xx - offset;
15897 int y = yy - offset;
15899 // some sound actions are always looping in native BD game engine
15900 if (sound_action == ACTION_DEFAULT)
15901 is_loop_sound = TRUE;
15903 // some sound actions are always non-looping in native BD game engine
15904 if (sound_action == ACTION_FALLING ||
15905 sound_action == ACTION_MOVING ||
15906 sound_action == ACTION_OTHER)
15907 is_loop_sound = FALSE;
15909 if (sound_effect != SND_UNDEFINED)
15910 PlayLevelSoundExt(x, y, sound_effect, is_loop_sound);
15913 void StopSound_BD(int element_bd, int sample)
15915 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15916 int sound_effect = getSoundEffect_BD(element, sample);
15918 if (sound_effect != SND_UNDEFINED)
15919 StopSound(sound_effect);
15922 boolean isSoundPlaying_BD(int element_bd, int sample)
15924 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15925 int sound_effect = getSoundEffect_BD(element, sample);
15927 if (sound_effect != SND_UNDEFINED)
15928 return isSoundPlaying(sound_effect);
15933 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15935 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15937 int x = xx - offset;
15938 int y = yy - offset;
15943 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15947 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15951 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15955 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15959 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15963 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15967 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15970 case SOUND_android_clone:
15971 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15974 case SOUND_android_move:
15975 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15979 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15983 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15987 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15990 case SOUND_eater_eat:
15991 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15995 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15998 case SOUND_collect:
15999 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
16002 case SOUND_diamond:
16003 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
16007 // !!! CHECK THIS !!!
16009 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
16011 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
16015 case SOUND_wonderfall:
16016 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
16020 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
16024 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
16028 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
16032 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
16036 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16040 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
16044 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16048 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16051 case SOUND_exit_open:
16052 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
16055 case SOUND_exit_leave:
16056 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16059 case SOUND_dynamite:
16060 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16064 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16068 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
16072 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16076 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
16080 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
16084 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
16088 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
16093 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
16095 int element = map_element_SP_to_RND(element_sp);
16096 int action = map_action_SP_to_RND(action_sp);
16097 int offset = (setup.sp_show_border_elements ? 0 : 1);
16098 int x = xx - offset;
16099 int y = yy - offset;
16101 PlayLevelSoundElementAction(x, y, element, action);
16104 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
16106 int element = map_element_MM_to_RND(element_mm);
16107 int action = map_action_MM_to_RND(action_mm);
16109 int x = xx - offset;
16110 int y = yy - offset;
16112 if (!IS_MM_ELEMENT(element))
16113 element = EL_MM_DEFAULT;
16115 PlayLevelSoundElementAction(x, y, element, action);
16118 void PlaySound_MM(int sound_mm)
16120 int sound = map_sound_MM_to_RND(sound_mm);
16122 if (sound == SND_UNDEFINED)
16128 void PlaySoundLoop_MM(int sound_mm)
16130 int sound = map_sound_MM_to_RND(sound_mm);
16132 if (sound == SND_UNDEFINED)
16135 PlaySoundLoop(sound);
16138 void StopSound_MM(int sound_mm)
16140 int sound = map_sound_MM_to_RND(sound_mm);
16142 if (sound == SND_UNDEFINED)
16148 void RaiseScore(int value)
16150 game.score += value;
16152 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
16154 DisplayGameControlValues();
16157 void RaiseScoreElement(int element)
16162 case EL_BD_DIAMOND:
16163 case EL_EMERALD_YELLOW:
16164 case EL_EMERALD_RED:
16165 case EL_EMERALD_PURPLE:
16166 case EL_SP_INFOTRON:
16167 RaiseScore(level.score[SC_EMERALD]);
16170 RaiseScore(level.score[SC_DIAMOND]);
16173 RaiseScore(level.score[SC_CRYSTAL]);
16176 RaiseScore(level.score[SC_PEARL]);
16179 case EL_BD_BUTTERFLY:
16180 case EL_SP_ELECTRON:
16181 RaiseScore(level.score[SC_BUG]);
16184 case EL_BD_FIREFLY:
16185 case EL_SP_SNIKSNAK:
16186 RaiseScore(level.score[SC_SPACESHIP]);
16189 case EL_DARK_YAMYAM:
16190 RaiseScore(level.score[SC_YAMYAM]);
16193 RaiseScore(level.score[SC_ROBOT]);
16196 RaiseScore(level.score[SC_PACMAN]);
16199 RaiseScore(level.score[SC_NUT]);
16202 case EL_EM_DYNAMITE:
16203 case EL_SP_DISK_RED:
16204 case EL_DYNABOMB_INCREASE_NUMBER:
16205 case EL_DYNABOMB_INCREASE_SIZE:
16206 case EL_DYNABOMB_INCREASE_POWER:
16207 RaiseScore(level.score[SC_DYNAMITE]);
16209 case EL_SHIELD_NORMAL:
16210 case EL_SHIELD_DEADLY:
16211 RaiseScore(level.score[SC_SHIELD]);
16213 case EL_EXTRA_TIME:
16214 RaiseScore(level.extra_time_score);
16228 case EL_DC_KEY_WHITE:
16229 RaiseScore(level.score[SC_KEY]);
16232 RaiseScore(element_info[element].collect_score);
16237 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
16239 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
16243 // prevent short reactivation of overlay buttons while closing door
16244 SetOverlayActive(FALSE);
16245 UnmapGameButtons();
16247 // door may still be open due to skipped or envelope style request
16248 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
16251 if (network.enabled)
16253 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
16257 // when using BD game engine, cover screen before fading out
16258 if (!quick_quit && level.game_engine_type == GAME_ENGINE_TYPE_BD)
16259 game_bd.cover_screen = TRUE;
16262 FadeSkipNextFadeIn();
16264 SetGameStatus(GAME_MODE_MAIN);
16269 else // continue playing the game
16271 if (tape.playing && tape.deactivate_display)
16272 TapeDeactivateDisplayOff(TRUE);
16274 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
16276 if (tape.playing && tape.deactivate_display)
16277 TapeDeactivateDisplayOn();
16281 void RequestQuitGame(boolean escape_key_pressed)
16283 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
16284 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
16285 level_editor_test_game);
16286 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
16287 quick_quit || score_info_tape_play);
16289 RequestQuitGameExt(skip_request, quick_quit,
16290 "Do you really want to quit the game?");
16293 static char *getRestartGameMessage(void)
16295 boolean play_again = hasStartedNetworkGame();
16296 static char message[MAX_OUTPUT_LINESIZE];
16297 char *game_over_text = "Game over!";
16298 char *play_again_text = " Play it again?";
16300 if (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
16301 game_mm.game_over_message != NULL)
16302 game_over_text = game_mm.game_over_message;
16304 snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text,
16305 (play_again ? play_again_text : ""));
16310 static void RequestRestartGame(void)
16312 char *message = getRestartGameMessage();
16313 boolean has_started_game = hasStartedNetworkGame();
16314 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
16315 int door_state = DOOR_CLOSE_1;
16317 boolean restart_wanted = (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game);
16319 // if no restart wanted, continue with next level for BD style intermission levels
16320 if (!restart_wanted && !level_editor_test_game && level.bd_intermission)
16322 boolean success = AdvanceToNextLevel();
16324 restart_wanted = (success && setup.auto_play_next_level);
16327 if (restart_wanted)
16329 CloseDoor(door_state);
16331 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16335 // if game was invoked from level editor, also close tape recorder door
16336 if (level_editor_test_game)
16337 door_state = DOOR_CLOSE_ALL;
16339 CloseDoor(door_state);
16341 SetGameStatus(GAME_MODE_MAIN);
16347 boolean CheckRestartGame(void)
16349 static int game_over_delay = 0;
16350 int game_over_delay_value = 50;
16351 boolean game_over = checkGameFailed();
16355 game_over_delay = game_over_delay_value;
16360 if (game_over_delay > 0)
16362 if (game_over_delay == game_over_delay_value / 2)
16363 PlaySound(SND_GAME_LOSING);
16370 // do not ask to play again if request dialog is already active
16371 if (checkRequestActive())
16374 // do not ask to play again if request dialog already handled
16375 if (game.RestartGameRequested)
16378 // do not ask to play again if game was never actually played
16379 if (!game.GamePlayed)
16382 // do not ask to play again if this was disabled in setup menu
16383 if (!setup.ask_on_game_over)
16386 game.RestartGameRequested = TRUE;
16388 RequestRestartGame();
16393 boolean checkGameRunning(void)
16395 if (game_status != GAME_MODE_PLAYING)
16398 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGameRunning_BD())
16404 boolean checkGamePlaying(void)
16406 if (game_status != GAME_MODE_PLAYING)
16409 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGamePlaying_BD())
16415 boolean checkGameSolved(void)
16417 // set for all game engines if level was solved
16418 return game.LevelSolved_GameEnd;
16421 boolean checkGameFailed(void)
16423 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
16424 return (game_bd.game_over && !game_bd.level_solved);
16425 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16426 return (game_em.game_over && !game_em.level_solved);
16427 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16428 return (game_sp.game_over && !game_sp.level_solved);
16429 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16430 return (game_mm.game_over && !game_mm.level_solved);
16431 else // GAME_ENGINE_TYPE_RND
16432 return (game.GameOver && !game.LevelSolved);
16435 boolean checkGameEnded(void)
16437 return (checkGameSolved() || checkGameFailed());
16440 boolean checkRequestActive(void)
16442 return (game.request_active || game.envelope_active || game.any_door_active);
16446 // ----------------------------------------------------------------------------
16447 // random generator functions
16448 // ----------------------------------------------------------------------------
16450 unsigned int InitEngineRandom_RND(int seed)
16452 game.num_random_calls = 0;
16454 return InitEngineRandom(seed);
16457 unsigned int RND(int max)
16461 game.num_random_calls++;
16463 return GetEngineRandom(max);
16470 // ----------------------------------------------------------------------------
16471 // game engine snapshot handling functions
16472 // ----------------------------------------------------------------------------
16474 struct EngineSnapshotInfo
16476 // runtime values for custom element collect score
16477 int collect_score[NUM_CUSTOM_ELEMENTS];
16479 // runtime values for group element choice position
16480 int choice_pos[NUM_GROUP_ELEMENTS];
16482 // runtime values for belt position animations
16483 int belt_graphic[4][NUM_BELT_PARTS];
16484 int belt_anim_mode[4][NUM_BELT_PARTS];
16487 static struct EngineSnapshotInfo engine_snapshot_rnd;
16488 static char *snapshot_level_identifier = NULL;
16489 static int snapshot_level_nr = -1;
16491 static void SaveEngineSnapshotValues_RND(void)
16493 static int belt_base_active_element[4] =
16495 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
16496 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
16497 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
16498 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
16502 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16504 int element = EL_CUSTOM_START + i;
16506 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
16509 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16511 int element = EL_GROUP_START + i;
16513 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
16516 for (i = 0; i < 4; i++)
16518 for (j = 0; j < NUM_BELT_PARTS; j++)
16520 int element = belt_base_active_element[i] + j;
16521 int graphic = el2img(element);
16522 int anim_mode = graphic_info[graphic].anim_mode;
16524 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
16525 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
16530 static void LoadEngineSnapshotValues_RND(void)
16532 unsigned int num_random_calls = game.num_random_calls;
16535 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16537 int element = EL_CUSTOM_START + i;
16539 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
16542 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16544 int element = EL_GROUP_START + i;
16546 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
16549 for (i = 0; i < 4; i++)
16551 for (j = 0; j < NUM_BELT_PARTS; j++)
16553 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
16554 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
16556 graphic_info[graphic].anim_mode = anim_mode;
16560 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16562 InitRND(tape.random_seed);
16563 for (i = 0; i < num_random_calls; i++)
16567 if (game.num_random_calls != num_random_calls)
16569 Error("number of random calls out of sync");
16570 Error("number of random calls should be %d", num_random_calls);
16571 Error("number of random calls is %d", game.num_random_calls);
16573 Fail("this should not happen -- please debug");
16577 void FreeEngineSnapshotSingle(void)
16579 FreeSnapshotSingle();
16581 setString(&snapshot_level_identifier, NULL);
16582 snapshot_level_nr = -1;
16585 void FreeEngineSnapshotList(void)
16587 FreeSnapshotList();
16590 static ListNode *SaveEngineSnapshotBuffers(void)
16592 ListNode *buffers = NULL;
16594 // copy some special values to a structure better suited for the snapshot
16596 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16597 SaveEngineSnapshotValues_RND();
16598 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16599 SaveEngineSnapshotValues_EM();
16600 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16601 SaveEngineSnapshotValues_SP(&buffers);
16602 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16603 SaveEngineSnapshotValues_MM();
16605 // save values stored in special snapshot structure
16607 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16608 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
16609 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16610 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
16611 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16612 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
16613 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16614 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
16616 // save further RND engine values
16618 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
16619 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
16620 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
16622 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
16623 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
16624 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
16625 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16626 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTimeFrames));
16627 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16629 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16630 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16631 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16633 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16635 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16636 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16638 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16639 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16640 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16641 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16642 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16643 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16644 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16645 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16646 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16647 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16648 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16649 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16650 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16651 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16652 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16653 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16654 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16655 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16657 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16658 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16660 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16661 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16662 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16664 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16665 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16667 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16668 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16669 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16670 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16671 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16672 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16674 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16675 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16678 ListNode *node = engine_snapshot_list_rnd;
16681 while (node != NULL)
16683 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16688 Debug("game:playing:SaveEngineSnapshotBuffers",
16689 "size of engine snapshot: %d bytes", num_bytes);
16695 void SaveEngineSnapshotSingle(void)
16697 ListNode *buffers = SaveEngineSnapshotBuffers();
16699 // finally save all snapshot buffers to single snapshot
16700 SaveSnapshotSingle(buffers);
16702 // save level identification information
16703 setString(&snapshot_level_identifier, leveldir_current->identifier);
16704 snapshot_level_nr = level_nr;
16707 boolean CheckSaveEngineSnapshotToList(void)
16709 boolean save_snapshot =
16710 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16711 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16712 game.snapshot.changed_action) ||
16713 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16714 game.snapshot.collected_item));
16716 game.snapshot.changed_action = FALSE;
16717 game.snapshot.collected_item = FALSE;
16718 game.snapshot.save_snapshot = save_snapshot;
16720 return save_snapshot;
16723 void SaveEngineSnapshotToList(void)
16725 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16729 ListNode *buffers = SaveEngineSnapshotBuffers();
16731 // finally save all snapshot buffers to snapshot list
16732 SaveSnapshotToList(buffers);
16735 void SaveEngineSnapshotToListInitial(void)
16737 FreeEngineSnapshotList();
16739 SaveEngineSnapshotToList();
16742 static void LoadEngineSnapshotValues(void)
16744 // restore special values from snapshot structure
16746 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16747 LoadEngineSnapshotValues_RND();
16748 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16749 LoadEngineSnapshotValues_EM();
16750 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16751 LoadEngineSnapshotValues_SP();
16752 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16753 LoadEngineSnapshotValues_MM();
16756 void LoadEngineSnapshotSingle(void)
16758 LoadSnapshotSingle();
16760 LoadEngineSnapshotValues();
16763 static void LoadEngineSnapshot_Undo(int steps)
16765 LoadSnapshotFromList_Older(steps);
16767 LoadEngineSnapshotValues();
16770 static void LoadEngineSnapshot_Redo(int steps)
16772 LoadSnapshotFromList_Newer(steps);
16774 LoadEngineSnapshotValues();
16777 boolean CheckEngineSnapshotSingle(void)
16779 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16780 snapshot_level_nr == level_nr);
16783 boolean CheckEngineSnapshotList(void)
16785 return CheckSnapshotList();
16789 // ---------- new game button stuff -------------------------------------------
16796 boolean *setup_value;
16797 boolean allowed_on_tape;
16798 boolean is_touch_button;
16800 } gamebutton_info[NUM_GAME_BUTTONS] =
16803 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16804 GAME_CTRL_ID_STOP, NULL,
16805 TRUE, FALSE, "stop game"
16808 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16809 GAME_CTRL_ID_PAUSE, NULL,
16810 TRUE, FALSE, "pause game"
16813 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16814 GAME_CTRL_ID_PLAY, NULL,
16815 TRUE, FALSE, "play game"
16818 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16819 GAME_CTRL_ID_UNDO, NULL,
16820 TRUE, FALSE, "undo step"
16823 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16824 GAME_CTRL_ID_REDO, NULL,
16825 TRUE, FALSE, "redo step"
16828 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16829 GAME_CTRL_ID_SAVE, NULL,
16830 TRUE, FALSE, "save game"
16833 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16834 GAME_CTRL_ID_PAUSE2, NULL,
16835 TRUE, FALSE, "pause game"
16838 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16839 GAME_CTRL_ID_LOAD, NULL,
16840 TRUE, FALSE, "load game"
16843 IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart,
16844 GAME_CTRL_ID_RESTART, NULL,
16845 TRUE, FALSE, "restart game"
16848 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16849 GAME_CTRL_ID_PANEL_STOP, NULL,
16850 FALSE, FALSE, "stop game"
16853 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16854 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16855 FALSE, FALSE, "pause game"
16858 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16859 GAME_CTRL_ID_PANEL_PLAY, NULL,
16860 FALSE, FALSE, "play game"
16863 IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart,
16864 GAME_CTRL_ID_PANEL_RESTART, NULL,
16865 FALSE, FALSE, "restart game"
16868 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16869 GAME_CTRL_ID_TOUCH_STOP, NULL,
16870 FALSE, TRUE, "stop game"
16873 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16874 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16875 FALSE, TRUE, "pause game"
16878 IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart,
16879 GAME_CTRL_ID_TOUCH_RESTART, NULL,
16880 FALSE, TRUE, "restart game"
16883 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16884 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16885 TRUE, FALSE, "background music on/off"
16888 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16889 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16890 TRUE, FALSE, "sound loops on/off"
16893 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16894 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16895 TRUE, FALSE, "normal sounds on/off"
16898 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16899 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16900 FALSE, FALSE, "background music on/off"
16903 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16904 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16905 FALSE, FALSE, "sound loops on/off"
16908 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16909 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16910 FALSE, FALSE, "normal sounds on/off"
16914 void CreateGameButtons(void)
16918 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16920 int graphic = gamebutton_info[i].graphic;
16921 struct GraphicInfo *gfx = &graphic_info[graphic];
16922 struct XY *pos = gamebutton_info[i].pos;
16923 struct GadgetInfo *gi;
16926 unsigned int event_mask;
16927 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16928 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16929 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16930 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16931 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16932 int gd_x = gfx->src_x;
16933 int gd_y = gfx->src_y;
16934 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16935 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16936 int gd_xa = gfx->src_x + gfx->active_xoffset;
16937 int gd_ya = gfx->src_y + gfx->active_yoffset;
16938 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16939 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16940 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16941 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16944 // do not use touch buttons if overlay touch buttons are disabled
16945 if (is_touch_button && !setup.touch.overlay_buttons)
16948 if (gfx->bitmap == NULL)
16950 game_gadget[id] = NULL;
16955 if (id == GAME_CTRL_ID_STOP ||
16956 id == GAME_CTRL_ID_PANEL_STOP ||
16957 id == GAME_CTRL_ID_TOUCH_STOP ||
16958 id == GAME_CTRL_ID_PLAY ||
16959 id == GAME_CTRL_ID_PANEL_PLAY ||
16960 id == GAME_CTRL_ID_SAVE ||
16961 id == GAME_CTRL_ID_LOAD ||
16962 id == GAME_CTRL_ID_RESTART ||
16963 id == GAME_CTRL_ID_PANEL_RESTART ||
16964 id == GAME_CTRL_ID_TOUCH_RESTART)
16966 button_type = GD_TYPE_NORMAL_BUTTON;
16968 event_mask = GD_EVENT_RELEASED;
16970 else if (id == GAME_CTRL_ID_UNDO ||
16971 id == GAME_CTRL_ID_REDO)
16973 button_type = GD_TYPE_NORMAL_BUTTON;
16975 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16979 button_type = GD_TYPE_CHECK_BUTTON;
16980 checked = (gamebutton_info[i].setup_value != NULL ?
16981 *gamebutton_info[i].setup_value : FALSE);
16982 event_mask = GD_EVENT_PRESSED;
16985 gi = CreateGadget(GDI_CUSTOM_ID, id,
16986 GDI_IMAGE_ID, graphic,
16987 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16990 GDI_WIDTH, gfx->width,
16991 GDI_HEIGHT, gfx->height,
16992 GDI_TYPE, button_type,
16993 GDI_STATE, GD_BUTTON_UNPRESSED,
16994 GDI_CHECKED, checked,
16995 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16996 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16997 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16998 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16999 GDI_DIRECT_DRAW, FALSE,
17000 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
17001 GDI_EVENT_MASK, event_mask,
17002 GDI_CALLBACK_ACTION, HandleGameButtons,
17006 Fail("cannot create gadget");
17008 game_gadget[id] = gi;
17012 void FreeGameButtons(void)
17016 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17017 FreeGadget(game_gadget[i]);
17020 static void UnmapGameButtonsAtSamePosition(int id)
17024 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17026 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
17027 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
17028 UnmapGadget(game_gadget[i]);
17031 static void UnmapGameButtonsAtSamePosition_All(void)
17033 if (setup.show_load_save_buttons)
17035 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
17036 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
17037 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
17039 else if (setup.show_undo_redo_buttons)
17041 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
17042 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
17043 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
17047 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
17048 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
17049 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
17051 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
17052 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
17053 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
17057 void MapLoadSaveButtons(void)
17059 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
17060 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
17062 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
17063 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
17066 void MapUndoRedoButtons(void)
17068 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
17069 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
17071 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
17072 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
17075 void ModifyPauseButtons(void)
17079 GAME_CTRL_ID_PAUSE,
17080 GAME_CTRL_ID_PAUSE2,
17081 GAME_CTRL_ID_PANEL_PAUSE,
17082 GAME_CTRL_ID_TOUCH_PAUSE,
17087 // do not redraw pause button on closed door (may happen when restarting game)
17088 if (!(GetDoorState() & DOOR_OPEN_1))
17091 for (i = 0; ids[i] > -1; i++)
17092 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
17095 static void MapGameButtonsExt(boolean on_tape)
17099 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17101 if ((i == GAME_CTRL_ID_UNDO ||
17102 i == GAME_CTRL_ID_REDO) &&
17103 game_status != GAME_MODE_PLAYING)
17106 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17107 MapGadget(game_gadget[i]);
17110 UnmapGameButtonsAtSamePosition_All();
17112 RedrawGameButtons();
17115 static void UnmapGameButtonsExt(boolean on_tape)
17119 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17120 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17121 UnmapGadget(game_gadget[i]);
17124 static void RedrawGameButtonsExt(boolean on_tape)
17128 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17129 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17130 RedrawGadget(game_gadget[i]);
17133 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
17138 gi->checked = state;
17141 static void RedrawSoundButtonGadget(int id)
17143 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
17144 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
17145 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
17146 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
17147 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
17148 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
17151 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
17152 RedrawGadget(game_gadget[id2]);
17155 void MapGameButtons(void)
17157 MapGameButtonsExt(FALSE);
17160 void UnmapGameButtons(void)
17162 UnmapGameButtonsExt(FALSE);
17165 void RedrawGameButtons(void)
17167 RedrawGameButtonsExt(FALSE);
17170 void MapGameButtonsOnTape(void)
17172 MapGameButtonsExt(TRUE);
17175 void UnmapGameButtonsOnTape(void)
17177 UnmapGameButtonsExt(TRUE);
17180 void RedrawGameButtonsOnTape(void)
17182 RedrawGameButtonsExt(TRUE);
17185 static void GameUndoRedoExt(void)
17187 ClearPlayerAction();
17189 tape.pausing = TRUE;
17192 UpdateAndDisplayGameControlValues();
17194 DrawCompleteVideoDisplay();
17195 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
17196 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
17197 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
17199 ModifyPauseButtons();
17204 static void GameUndo(int steps)
17206 if (!CheckEngineSnapshotList())
17209 int tape_property_bits = tape.property_bits;
17211 LoadEngineSnapshot_Undo(steps);
17213 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17218 static void GameRedo(int steps)
17220 if (!CheckEngineSnapshotList())
17223 int tape_property_bits = tape.property_bits;
17225 LoadEngineSnapshot_Redo(steps);
17227 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17232 static void HandleGameButtonsExt(int id, int button)
17234 static boolean game_undo_executed = FALSE;
17235 int steps = BUTTON_STEPSIZE(button);
17236 boolean handle_game_buttons =
17237 (game_status == GAME_MODE_PLAYING ||
17238 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
17240 if (!handle_game_buttons)
17245 case GAME_CTRL_ID_STOP:
17246 case GAME_CTRL_ID_PANEL_STOP:
17247 case GAME_CTRL_ID_TOUCH_STOP:
17252 case GAME_CTRL_ID_PAUSE:
17253 case GAME_CTRL_ID_PAUSE2:
17254 case GAME_CTRL_ID_PANEL_PAUSE:
17255 case GAME_CTRL_ID_TOUCH_PAUSE:
17256 if (network.enabled && game_status == GAME_MODE_PLAYING)
17259 SendToServer_ContinuePlaying();
17261 SendToServer_PausePlaying();
17264 TapeTogglePause(TAPE_TOGGLE_MANUAL);
17266 game_undo_executed = FALSE;
17270 case GAME_CTRL_ID_PLAY:
17271 case GAME_CTRL_ID_PANEL_PLAY:
17272 if (game_status == GAME_MODE_MAIN)
17274 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
17276 else if (tape.pausing)
17278 if (network.enabled)
17279 SendToServer_ContinuePlaying();
17281 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
17285 case GAME_CTRL_ID_UNDO:
17286 // Important: When using "save snapshot when collecting an item" mode,
17287 // load last (current) snapshot for first "undo" after pressing "pause"
17288 // (else the last-but-one snapshot would be loaded, because the snapshot
17289 // pointer already points to the last snapshot when pressing "pause",
17290 // which is fine for "every step/move" mode, but not for "every collect")
17291 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
17292 !game_undo_executed)
17295 game_undo_executed = TRUE;
17300 case GAME_CTRL_ID_REDO:
17304 case GAME_CTRL_ID_SAVE:
17308 case GAME_CTRL_ID_LOAD:
17312 case GAME_CTRL_ID_RESTART:
17313 case GAME_CTRL_ID_PANEL_RESTART:
17314 case GAME_CTRL_ID_TOUCH_RESTART:
17319 case SOUND_CTRL_ID_MUSIC:
17320 case SOUND_CTRL_ID_PANEL_MUSIC:
17321 if (setup.sound_music)
17323 setup.sound_music = FALSE;
17327 else if (audio.music_available)
17329 setup.sound = setup.sound_music = TRUE;
17331 SetAudioMode(setup.sound);
17333 if (game_status == GAME_MODE_PLAYING)
17337 RedrawSoundButtonGadget(id);
17341 case SOUND_CTRL_ID_LOOPS:
17342 case SOUND_CTRL_ID_PANEL_LOOPS:
17343 if (setup.sound_loops)
17344 setup.sound_loops = FALSE;
17345 else if (audio.loops_available)
17347 setup.sound = setup.sound_loops = TRUE;
17349 SetAudioMode(setup.sound);
17352 RedrawSoundButtonGadget(id);
17356 case SOUND_CTRL_ID_SIMPLE:
17357 case SOUND_CTRL_ID_PANEL_SIMPLE:
17358 if (setup.sound_simple)
17359 setup.sound_simple = FALSE;
17360 else if (audio.sound_available)
17362 setup.sound = setup.sound_simple = TRUE;
17364 SetAudioMode(setup.sound);
17367 RedrawSoundButtonGadget(id);
17376 static void HandleGameButtons(struct GadgetInfo *gi)
17378 HandleGameButtonsExt(gi->custom_id, gi->event.button);
17381 void HandleSoundButtonKeys(Key key)
17383 if (key == setup.shortcut.sound_simple)
17384 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
17385 else if (key == setup.shortcut.sound_loops)
17386 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
17387 else if (key == setup.shortcut.sound_music)
17388 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);