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_BD_EMPTY ? EL_EMPTY :
1844 element == EL_BD_PLAYER ? EL_PLAYER_1 :
1845 element == EL_BD_INBOX ? EL_PLAYER_1 :
1846 element == EL_BD_SAND ? EL_SAND :
1847 element == EL_BD_STEELWALL ? EL_STEELWALL :
1848 element == EL_BD_EXIT_CLOSED ? EL_EXIT_CLOSED :
1849 element == EL_BD_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 ?
2289 game_bd.time_played :
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);
3681 if (level_editor_test_game)
3682 FadeSkipNextFadeOut();
3684 FadeSetEnterScreen();
3687 fade_mask = REDRAW_ALL;
3689 FadeLevelSoundsAndMusic();
3691 ExpireSoundLoops(TRUE);
3697 // force restarting global animations displayed during game play
3698 RestartGlobalAnimsByStatus(GAME_MODE_PSEUDO_RESTARTING);
3700 // this is required for "transforming" fade modes like cross-fading
3701 // (else global animations will be stopped, but not restarted here)
3702 SetAnimStatusBeforeFading(GAME_MODE_PSEUDO_RESTARTING);
3704 SetGameStatus(GAME_MODE_PLAYING);
3707 if (level_editor_test_game)
3708 FadeSkipNextFadeIn();
3710 // needed if different viewport properties defined for playing
3711 ChangeViewportPropertiesIfNeeded();
3715 DrawCompleteVideoDisplay();
3717 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3720 InitGameControlValues();
3724 // initialize tape actions from game when recording tape
3725 tape.use_key_actions = game.use_key_actions;
3726 tape.use_mouse_actions = game.use_mouse_actions;
3728 // initialize visible playfield size when recording tape (for team mode)
3729 tape.scr_fieldx = SCR_FIELDX;
3730 tape.scr_fieldy = SCR_FIELDY;
3733 // don't play tapes over network
3734 network_playing = (network.enabled && !tape.playing);
3736 for (i = 0; i < MAX_PLAYERS; i++)
3738 struct PlayerInfo *player = &stored_player[i];
3740 player->index_nr = i;
3741 player->index_bit = (1 << i);
3742 player->element_nr = EL_PLAYER_1 + i;
3744 player->present = FALSE;
3745 player->active = FALSE;
3746 player->mapped = FALSE;
3748 player->killed = FALSE;
3749 player->reanimated = FALSE;
3750 player->buried = FALSE;
3753 player->effective_action = 0;
3754 player->programmed_action = 0;
3755 player->snap_action = 0;
3757 player->mouse_action.lx = 0;
3758 player->mouse_action.ly = 0;
3759 player->mouse_action.button = 0;
3760 player->mouse_action.button_hint = 0;
3762 player->effective_mouse_action.lx = 0;
3763 player->effective_mouse_action.ly = 0;
3764 player->effective_mouse_action.button = 0;
3765 player->effective_mouse_action.button_hint = 0;
3767 for (j = 0; j < MAX_NUM_KEYS; j++)
3768 player->key[j] = FALSE;
3770 player->num_white_keys = 0;
3772 player->dynabomb_count = 0;
3773 player->dynabomb_size = 1;
3774 player->dynabombs_left = 0;
3775 player->dynabomb_xl = FALSE;
3777 player->MovDir = initial_move_dir;
3780 player->GfxDir = initial_move_dir;
3781 player->GfxAction = ACTION_DEFAULT;
3783 player->StepFrame = 0;
3785 player->initial_element = player->element_nr;
3786 player->artwork_element =
3787 (level.use_artwork_element[i] ? level.artwork_element[i] :
3788 player->element_nr);
3789 player->use_murphy = FALSE;
3791 player->block_last_field = FALSE; // initialized in InitPlayerField()
3792 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3794 player->gravity = level.initial_player_gravity[i];
3796 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3798 player->actual_frame_counter.count = 0;
3799 player->actual_frame_counter.value = 1;
3801 player->step_counter = 0;
3803 player->last_move_dir = initial_move_dir;
3805 player->is_active = FALSE;
3807 player->is_waiting = FALSE;
3808 player->is_moving = FALSE;
3809 player->is_auto_moving = FALSE;
3810 player->is_digging = FALSE;
3811 player->is_snapping = FALSE;
3812 player->is_collecting = FALSE;
3813 player->is_pushing = FALSE;
3814 player->is_switching = FALSE;
3815 player->is_dropping = FALSE;
3816 player->is_dropping_pressed = FALSE;
3818 player->is_bored = FALSE;
3819 player->is_sleeping = FALSE;
3821 player->was_waiting = TRUE;
3822 player->was_moving = FALSE;
3823 player->was_snapping = FALSE;
3824 player->was_dropping = FALSE;
3826 player->force_dropping = FALSE;
3828 player->frame_counter_bored = -1;
3829 player->frame_counter_sleeping = -1;
3831 player->anim_delay_counter = 0;
3832 player->post_delay_counter = 0;
3834 player->dir_waiting = initial_move_dir;
3835 player->action_waiting = ACTION_DEFAULT;
3836 player->last_action_waiting = ACTION_DEFAULT;
3837 player->special_action_bored = ACTION_DEFAULT;
3838 player->special_action_sleeping = ACTION_DEFAULT;
3840 player->switch_x = -1;
3841 player->switch_y = -1;
3843 player->drop_x = -1;
3844 player->drop_y = -1;
3846 player->show_envelope = 0;
3848 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3850 player->push_delay = -1; // initialized when pushing starts
3851 player->push_delay_value = game.initial_push_delay_value;
3853 player->drop_delay = 0;
3854 player->drop_pressed_delay = 0;
3856 player->last_jx = -1;
3857 player->last_jy = -1;
3861 player->shield_normal_time_left = 0;
3862 player->shield_deadly_time_left = 0;
3864 player->last_removed_element = EL_UNDEFINED;
3866 player->inventory_infinite_element = EL_UNDEFINED;
3867 player->inventory_size = 0;
3869 if (level.use_initial_inventory[i])
3871 for (j = 0; j < level.initial_inventory_size[i]; j++)
3873 int element = level.initial_inventory_content[i][j];
3874 int collect_count = element_info[element].collect_count_initial;
3877 if (!IS_CUSTOM_ELEMENT(element))
3880 if (collect_count == 0)
3881 player->inventory_infinite_element = element;
3883 for (k = 0; k < collect_count; k++)
3884 if (player->inventory_size < MAX_INVENTORY_SIZE)
3885 player->inventory_element[player->inventory_size++] = element;
3889 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3890 SnapField(player, 0, 0);
3892 map_player_action[i] = i;
3895 network_player_action_received = FALSE;
3897 // initial null action
3898 if (network_playing)
3899 SendToServer_MovePlayer(MV_NONE);
3904 TimeLeft = level.time;
3909 ScreenMovDir = MV_NONE;
3913 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3915 game.robot_wheel_x = -1;
3916 game.robot_wheel_y = -1;
3921 game.all_players_gone = FALSE;
3923 game.LevelSolved = FALSE;
3924 game.GameOver = FALSE;
3926 game.GamePlayed = !tape.playing;
3928 game.LevelSolved_GameWon = FALSE;
3929 game.LevelSolved_GameEnd = FALSE;
3930 game.LevelSolved_SaveTape = FALSE;
3931 game.LevelSolved_SaveScore = FALSE;
3933 game.LevelSolved_CountingTime = 0;
3934 game.LevelSolved_CountingScore = 0;
3935 game.LevelSolved_CountingHealth = 0;
3937 game.RestartGameRequested = FALSE;
3939 game.panel.active = TRUE;
3941 game.no_level_time_limit = (level.time == 0);
3942 game.time_limit = (leveldir_current->time_limit && setup.time_limit);
3944 game.yamyam_content_nr = 0;
3945 game.robot_wheel_active = FALSE;
3946 game.magic_wall_active = FALSE;
3947 game.magic_wall_time_left = 0;
3948 game.light_time_left = 0;
3949 game.timegate_time_left = 0;
3950 game.switchgate_pos = 0;
3951 game.wind_direction = level.wind_direction_initial;
3953 game.time_final = 0;
3954 game.score_time_final = 0;
3957 game.score_final = 0;
3959 game.health = MAX_HEALTH;
3960 game.health_final = MAX_HEALTH;
3962 game.gems_still_needed = level.gems_needed;
3963 game.sokoban_fields_still_needed = 0;
3964 game.sokoban_objects_still_needed = 0;
3965 game.lights_still_needed = 0;
3966 game.players_still_needed = 0;
3967 game.friends_still_needed = 0;
3969 game.lenses_time_left = 0;
3970 game.magnify_time_left = 0;
3972 game.ball_active = level.ball_active_initial;
3973 game.ball_content_nr = 0;
3975 game.explosions_delayed = TRUE;
3977 // special case: set custom artwork setting to initial value
3978 game.use_masked_elements = game.use_masked_elements_initial;
3980 for (i = 0; i < NUM_BELTS; i++)
3982 game.belt_dir[i] = MV_NONE;
3983 game.belt_dir_nr[i] = 3; // not moving, next moving left
3986 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3987 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3989 #if DEBUG_INIT_PLAYER
3990 DebugPrintPlayerStatus("Player status at level initialization");
3993 SCAN_PLAYFIELD(x, y)
3995 Tile[x][y] = Last[x][y] = level.field[x][y];
3996 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3997 ChangeDelay[x][y] = 0;
3998 ChangePage[x][y] = -1;
3999 CustomValue[x][y] = 0; // initialized in InitField()
4000 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
4002 WasJustMoving[x][y] = 0;
4003 WasJustFalling[x][y] = 0;
4004 CheckCollision[x][y] = 0;
4005 CheckImpact[x][y] = 0;
4007 Pushed[x][y] = FALSE;
4009 ChangeCount[x][y] = 0;
4010 ChangeEvent[x][y] = -1;
4012 ExplodePhase[x][y] = 0;
4013 ExplodeDelay[x][y] = 0;
4014 ExplodeField[x][y] = EX_TYPE_NONE;
4016 RunnerVisit[x][y] = 0;
4017 PlayerVisit[x][y] = 0;
4020 GfxRandom[x][y] = INIT_GFX_RANDOM();
4021 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
4022 GfxElement[x][y] = EL_UNDEFINED;
4023 GfxElementEmpty[x][y] = EL_EMPTY;
4024 GfxAction[x][y] = ACTION_DEFAULT;
4025 GfxDir[x][y] = MV_NONE;
4026 GfxRedraw[x][y] = GFX_REDRAW_NONE;
4029 SCAN_PLAYFIELD(x, y)
4031 InitFieldForEngine(x, y);
4033 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
4035 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
4038 InitField(x, y, TRUE);
4040 ResetGfxAnimation(x, y);
4045 // required if level does not contain any "empty space" element
4046 if (element_info[EL_EMPTY].use_gfx_element)
4047 game.use_masked_elements = TRUE;
4049 for (i = 0; i < MAX_PLAYERS; i++)
4051 struct PlayerInfo *player = &stored_player[i];
4053 // set number of special actions for bored and sleeping animation
4054 player->num_special_action_bored =
4055 get_num_special_action(player->artwork_element,
4056 ACTION_BORING_1, ACTION_BORING_LAST);
4057 player->num_special_action_sleeping =
4058 get_num_special_action(player->artwork_element,
4059 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
4062 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
4063 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
4065 // initialize type of slippery elements
4066 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4068 if (!IS_CUSTOM_ELEMENT(i))
4070 // default: elements slip down either to the left or right randomly
4071 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
4073 // SP style elements prefer to slip down on the left side
4074 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
4075 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4077 // BD style elements prefer to slip down on the left side
4078 if (game.emulation == EMU_BOULDERDASH)
4079 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4083 // initialize explosion and ignition delay
4084 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4086 if (!IS_CUSTOM_ELEMENT(i))
4089 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
4090 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
4091 game.emulation == EMU_SUPAPLEX ? 3 : 2);
4092 int last_phase = (num_phase + 1) * delay;
4093 int half_phase = (num_phase / 2) * delay;
4095 element_info[i].explosion_delay = last_phase - 1;
4096 element_info[i].ignition_delay = half_phase;
4098 if (i == EL_BLACK_ORB)
4099 element_info[i].ignition_delay = 1;
4103 // correct non-moving belts to start moving left
4104 for (i = 0; i < NUM_BELTS; i++)
4105 if (game.belt_dir[i] == MV_NONE)
4106 game.belt_dir_nr[i] = 3; // not moving, next moving left
4108 #if USE_NEW_PLAYER_ASSIGNMENTS
4109 // use preferred player also in local single-player mode
4110 if (!network.enabled && !game.team_mode)
4112 int new_index_nr = setup.network_player_nr;
4114 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
4116 for (i = 0; i < MAX_PLAYERS; i++)
4117 stored_player[i].connected_locally = FALSE;
4119 stored_player[new_index_nr].connected_locally = TRUE;
4123 for (i = 0; i < MAX_PLAYERS; i++)
4125 stored_player[i].connected = FALSE;
4127 // in network game mode, the local player might not be the first player
4128 if (stored_player[i].connected_locally)
4129 local_player = &stored_player[i];
4132 if (!network.enabled)
4133 local_player->connected = TRUE;
4137 for (i = 0; i < MAX_PLAYERS; i++)
4138 stored_player[i].connected = tape.player_participates[i];
4140 else if (network.enabled)
4142 // add team mode players connected over the network (needed for correct
4143 // assignment of player figures from level to locally playing players)
4145 for (i = 0; i < MAX_PLAYERS; i++)
4146 if (stored_player[i].connected_network)
4147 stored_player[i].connected = TRUE;
4149 else if (game.team_mode)
4151 // try to guess locally connected team mode players (needed for correct
4152 // assignment of player figures from level to locally playing players)
4154 for (i = 0; i < MAX_PLAYERS; i++)
4155 if (setup.input[i].use_joystick ||
4156 setup.input[i].key.left != KSYM_UNDEFINED)
4157 stored_player[i].connected = TRUE;
4160 #if DEBUG_INIT_PLAYER
4161 DebugPrintPlayerStatus("Player status after level initialization");
4164 #if DEBUG_INIT_PLAYER
4165 Debug("game:init:player", "Reassigning players ...");
4168 // check if any connected player was not found in playfield
4169 for (i = 0; i < MAX_PLAYERS; i++)
4171 struct PlayerInfo *player = &stored_player[i];
4173 if (player->connected && !player->present)
4175 struct PlayerInfo *field_player = NULL;
4177 #if DEBUG_INIT_PLAYER
4178 Debug("game:init:player",
4179 "- looking for field player for player %d ...", i + 1);
4182 // assign first free player found that is present in the playfield
4184 // first try: look for unmapped playfield player that is not connected
4185 for (j = 0; j < MAX_PLAYERS; j++)
4186 if (field_player == NULL &&
4187 stored_player[j].present &&
4188 !stored_player[j].mapped &&
4189 !stored_player[j].connected)
4190 field_player = &stored_player[j];
4192 // second try: look for *any* unmapped playfield player
4193 for (j = 0; j < MAX_PLAYERS; j++)
4194 if (field_player == NULL &&
4195 stored_player[j].present &&
4196 !stored_player[j].mapped)
4197 field_player = &stored_player[j];
4199 if (field_player != NULL)
4201 int jx = field_player->jx, jy = field_player->jy;
4203 #if DEBUG_INIT_PLAYER
4204 Debug("game:init:player", "- found player %d",
4205 field_player->index_nr + 1);
4208 player->present = FALSE;
4209 player->active = FALSE;
4211 field_player->present = TRUE;
4212 field_player->active = TRUE;
4215 player->initial_element = field_player->initial_element;
4216 player->artwork_element = field_player->artwork_element;
4218 player->block_last_field = field_player->block_last_field;
4219 player->block_delay_adjustment = field_player->block_delay_adjustment;
4222 StorePlayer[jx][jy] = field_player->element_nr;
4224 field_player->jx = field_player->last_jx = jx;
4225 field_player->jy = field_player->last_jy = jy;
4227 if (local_player == player)
4228 local_player = field_player;
4230 map_player_action[field_player->index_nr] = i;
4232 field_player->mapped = TRUE;
4234 #if DEBUG_INIT_PLAYER
4235 Debug("game:init:player", "- map_player_action[%d] == %d",
4236 field_player->index_nr + 1, i + 1);
4241 if (player->connected && player->present)
4242 player->mapped = TRUE;
4245 #if DEBUG_INIT_PLAYER
4246 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4251 // check if any connected player was not found in playfield
4252 for (i = 0; i < MAX_PLAYERS; i++)
4254 struct PlayerInfo *player = &stored_player[i];
4256 if (player->connected && !player->present)
4258 for (j = 0; j < MAX_PLAYERS; j++)
4260 struct PlayerInfo *field_player = &stored_player[j];
4261 int jx = field_player->jx, jy = field_player->jy;
4263 // assign first free player found that is present in the playfield
4264 if (field_player->present && !field_player->connected)
4266 player->present = TRUE;
4267 player->active = TRUE;
4269 field_player->present = FALSE;
4270 field_player->active = FALSE;
4272 player->initial_element = field_player->initial_element;
4273 player->artwork_element = field_player->artwork_element;
4275 player->block_last_field = field_player->block_last_field;
4276 player->block_delay_adjustment = field_player->block_delay_adjustment;
4278 StorePlayer[jx][jy] = player->element_nr;
4280 player->jx = player->last_jx = jx;
4281 player->jy = player->last_jy = jy;
4291 Debug("game:init:player", "local_player->present == %d",
4292 local_player->present);
4295 // set focus to local player for network games, else to all players
4296 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4297 game.centered_player_nr_next = game.centered_player_nr;
4298 game.set_centered_player = FALSE;
4299 game.set_centered_player_wrap = FALSE;
4301 if (network_playing && tape.recording)
4303 // store client dependent player focus when recording network games
4304 tape.centered_player_nr_next = game.centered_player_nr_next;
4305 tape.set_centered_player = TRUE;
4310 // when playing a tape, eliminate all players who do not participate
4312 #if USE_NEW_PLAYER_ASSIGNMENTS
4314 if (!game.team_mode)
4316 for (i = 0; i < MAX_PLAYERS; i++)
4318 if (stored_player[i].active &&
4319 !tape.player_participates[map_player_action[i]])
4321 struct PlayerInfo *player = &stored_player[i];
4322 int jx = player->jx, jy = player->jy;
4324 #if DEBUG_INIT_PLAYER
4325 Debug("game:init:player", "Removing player %d at (%d, %d)",
4329 player->active = FALSE;
4330 StorePlayer[jx][jy] = 0;
4331 Tile[jx][jy] = EL_EMPTY;
4338 for (i = 0; i < MAX_PLAYERS; i++)
4340 if (stored_player[i].active &&
4341 !tape.player_participates[i])
4343 struct PlayerInfo *player = &stored_player[i];
4344 int jx = player->jx, jy = player->jy;
4346 player->active = FALSE;
4347 StorePlayer[jx][jy] = 0;
4348 Tile[jx][jy] = EL_EMPTY;
4353 else if (!network.enabled && !game.team_mode) // && !tape.playing
4355 // when in single player mode, eliminate all but the local player
4357 for (i = 0; i < MAX_PLAYERS; i++)
4359 struct PlayerInfo *player = &stored_player[i];
4361 if (player->active && player != local_player)
4363 int jx = player->jx, jy = player->jy;
4365 player->active = FALSE;
4366 player->present = FALSE;
4368 StorePlayer[jx][jy] = 0;
4369 Tile[jx][jy] = EL_EMPTY;
4374 for (i = 0; i < MAX_PLAYERS; i++)
4375 if (stored_player[i].active)
4376 game.players_still_needed++;
4378 if (level.solved_by_one_player)
4379 game.players_still_needed = 1;
4381 // when recording the game, store which players take part in the game
4384 #if USE_NEW_PLAYER_ASSIGNMENTS
4385 for (i = 0; i < MAX_PLAYERS; i++)
4386 if (stored_player[i].connected)
4387 tape.player_participates[i] = TRUE;
4389 for (i = 0; i < MAX_PLAYERS; i++)
4390 if (stored_player[i].active)
4391 tape.player_participates[i] = TRUE;
4395 #if DEBUG_INIT_PLAYER
4396 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4399 if (BorderElement == EL_EMPTY)
4402 SBX_Right = lev_fieldx - SCR_FIELDX;
4404 SBY_Lower = lev_fieldy - SCR_FIELDY;
4409 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4411 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4414 if (full_lev_fieldx <= SCR_FIELDX)
4415 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4416 if (full_lev_fieldy <= SCR_FIELDY)
4417 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4419 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4421 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4424 // if local player not found, look for custom element that might create
4425 // the player (make some assumptions about the right custom element)
4426 if (!local_player->present)
4428 int start_x = 0, start_y = 0;
4429 int found_rating = 0;
4430 int found_element = EL_UNDEFINED;
4431 int player_nr = local_player->index_nr;
4433 SCAN_PLAYFIELD(x, y)
4435 int element = Tile[x][y];
4440 if (level.use_start_element[player_nr] &&
4441 level.start_element[player_nr] == element &&
4448 found_element = element;
4451 if (!IS_CUSTOM_ELEMENT(element))
4454 if (CAN_CHANGE(element))
4456 for (i = 0; i < element_info[element].num_change_pages; i++)
4458 // check for player created from custom element as single target
4459 content = element_info[element].change_page[i].target_element;
4460 is_player = IS_PLAYER_ELEMENT(content);
4462 if (is_player && (found_rating < 3 ||
4463 (found_rating == 3 && element < found_element)))
4469 found_element = element;
4474 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4476 // check for player created from custom element as explosion content
4477 content = element_info[element].content.e[xx][yy];
4478 is_player = IS_PLAYER_ELEMENT(content);
4480 if (is_player && (found_rating < 2 ||
4481 (found_rating == 2 && element < found_element)))
4483 start_x = x + xx - 1;
4484 start_y = y + yy - 1;
4487 found_element = element;
4490 if (!CAN_CHANGE(element))
4493 for (i = 0; i < element_info[element].num_change_pages; i++)
4495 // check for player created from custom element as extended target
4497 element_info[element].change_page[i].target_content.e[xx][yy];
4499 is_player = IS_PLAYER_ELEMENT(content);
4501 if (is_player && (found_rating < 1 ||
4502 (found_rating == 1 && element < found_element)))
4504 start_x = x + xx - 1;
4505 start_y = y + yy - 1;
4508 found_element = element;
4514 scroll_x = SCROLL_POSITION_X(start_x);
4515 scroll_y = SCROLL_POSITION_Y(start_y);
4519 scroll_x = SCROLL_POSITION_X(local_player->jx);
4520 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4523 if (game.forced_scroll_x != ARG_UNDEFINED_VALUE)
4524 scroll_x = game.forced_scroll_x;
4525 if (game.forced_scroll_y != ARG_UNDEFINED_VALUE)
4526 scroll_y = game.forced_scroll_y;
4528 // !!! FIX THIS (START) !!!
4529 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
4531 InitGameEngine_BD();
4533 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4535 InitGameEngine_EM();
4537 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4539 InitGameEngine_SP();
4541 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4543 InitGameEngine_MM();
4547 DrawLevel(REDRAW_FIELD);
4550 // after drawing the level, correct some elements
4551 if (game.timegate_time_left == 0)
4552 CloseAllOpenTimegates();
4555 // blit playfield from scroll buffer to normal back buffer for fading in
4556 BlitScreenToBitmap(backbuffer);
4557 // !!! FIX THIS (END) !!!
4559 DrawMaskedBorder(fade_mask);
4564 // full screen redraw is required at this point in the following cases:
4565 // - special editor door undrawn when game was started from level editor
4566 // - drawing area (playfield) was changed and has to be removed completely
4567 redraw_mask = REDRAW_ALL;
4571 if (!game.restart_level)
4573 // copy default game door content to main double buffer
4575 // !!! CHECK AGAIN !!!
4576 SetPanelBackground();
4577 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4578 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4581 SetPanelBackground();
4582 SetDrawBackgroundMask(REDRAW_DOOR_1);
4584 UpdateAndDisplayGameControlValues();
4586 if (!game.restart_level)
4592 CreateGameButtons();
4597 // copy actual game door content to door double buffer for OpenDoor()
4598 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4600 OpenDoor(DOOR_OPEN_ALL);
4602 KeyboardAutoRepeatOffUnlessAutoplay();
4604 #if DEBUG_INIT_PLAYER
4605 DebugPrintPlayerStatus("Player status (final)");
4614 if (!game.restart_level && !tape.playing)
4616 LevelStats_incPlayed(level_nr);
4618 SaveLevelSetup_SeriesInfo();
4621 game.restart_level = FALSE;
4622 game.request_active = FALSE;
4623 game.envelope_active = FALSE;
4624 game.any_door_active = FALSE;
4626 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4627 InitGameActions_MM();
4629 SaveEngineSnapshotToListInitial();
4631 if (!game.restart_level)
4633 PlaySound(SND_GAME_STARTING);
4635 if (setup.sound_music)
4639 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4642 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4643 int actual_player_x, int actual_player_y)
4645 // this is used for non-R'n'D game engines to update certain engine values
4647 // needed to determine if sounds are played within the visible screen area
4648 scroll_x = actual_scroll_x;
4649 scroll_y = actual_scroll_y;
4651 // needed to get player position for "follow finger" playing input method
4652 local_player->jx = actual_player_x;
4653 local_player->jy = actual_player_y;
4656 void InitMovDir(int x, int y)
4658 int i, element = Tile[x][y];
4659 static int xy[4][2] =
4666 static int direction[3][4] =
4668 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4669 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4670 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4679 Tile[x][y] = EL_BUG;
4680 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4683 case EL_SPACESHIP_RIGHT:
4684 case EL_SPACESHIP_UP:
4685 case EL_SPACESHIP_LEFT:
4686 case EL_SPACESHIP_DOWN:
4687 Tile[x][y] = EL_SPACESHIP;
4688 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4691 case EL_BD_BUTTERFLY_RIGHT:
4692 case EL_BD_BUTTERFLY_UP:
4693 case EL_BD_BUTTERFLY_LEFT:
4694 case EL_BD_BUTTERFLY_DOWN:
4695 Tile[x][y] = EL_BD_BUTTERFLY;
4696 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4699 case EL_BD_FIREFLY_RIGHT:
4700 case EL_BD_FIREFLY_UP:
4701 case EL_BD_FIREFLY_LEFT:
4702 case EL_BD_FIREFLY_DOWN:
4703 Tile[x][y] = EL_BD_FIREFLY;
4704 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4707 case EL_PACMAN_RIGHT:
4709 case EL_PACMAN_LEFT:
4710 case EL_PACMAN_DOWN:
4711 Tile[x][y] = EL_PACMAN;
4712 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4715 case EL_YAMYAM_LEFT:
4716 case EL_YAMYAM_RIGHT:
4718 case EL_YAMYAM_DOWN:
4719 Tile[x][y] = EL_YAMYAM;
4720 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4723 case EL_SP_SNIKSNAK:
4724 MovDir[x][y] = MV_UP;
4727 case EL_SP_ELECTRON:
4728 MovDir[x][y] = MV_LEFT;
4735 Tile[x][y] = EL_MOLE;
4736 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4739 case EL_SPRING_LEFT:
4740 case EL_SPRING_RIGHT:
4741 Tile[x][y] = EL_SPRING;
4742 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4746 if (IS_CUSTOM_ELEMENT(element))
4748 struct ElementInfo *ei = &element_info[element];
4749 int move_direction_initial = ei->move_direction_initial;
4750 int move_pattern = ei->move_pattern;
4752 if (move_direction_initial == MV_START_PREVIOUS)
4754 if (MovDir[x][y] != MV_NONE)
4757 move_direction_initial = MV_START_AUTOMATIC;
4760 if (move_direction_initial == MV_START_RANDOM)
4761 MovDir[x][y] = 1 << RND(4);
4762 else if (move_direction_initial & MV_ANY_DIRECTION)
4763 MovDir[x][y] = move_direction_initial;
4764 else if (move_pattern == MV_ALL_DIRECTIONS ||
4765 move_pattern == MV_TURNING_LEFT ||
4766 move_pattern == MV_TURNING_RIGHT ||
4767 move_pattern == MV_TURNING_LEFT_RIGHT ||
4768 move_pattern == MV_TURNING_RIGHT_LEFT ||
4769 move_pattern == MV_TURNING_RANDOM)
4770 MovDir[x][y] = 1 << RND(4);
4771 else if (move_pattern == MV_HORIZONTAL)
4772 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4773 else if (move_pattern == MV_VERTICAL)
4774 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4775 else if (move_pattern & MV_ANY_DIRECTION)
4776 MovDir[x][y] = element_info[element].move_pattern;
4777 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4778 move_pattern == MV_ALONG_RIGHT_SIDE)
4780 // use random direction as default start direction
4781 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4782 MovDir[x][y] = 1 << RND(4);
4784 for (i = 0; i < NUM_DIRECTIONS; i++)
4786 int x1 = x + xy[i][0];
4787 int y1 = y + xy[i][1];
4789 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4791 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4792 MovDir[x][y] = direction[0][i];
4794 MovDir[x][y] = direction[1][i];
4803 MovDir[x][y] = 1 << RND(4);
4805 if (element != EL_BUG &&
4806 element != EL_SPACESHIP &&
4807 element != EL_BD_BUTTERFLY &&
4808 element != EL_BD_FIREFLY)
4811 for (i = 0; i < NUM_DIRECTIONS; i++)
4813 int x1 = x + xy[i][0];
4814 int y1 = y + xy[i][1];
4816 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4818 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4820 MovDir[x][y] = direction[0][i];
4823 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4824 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4826 MovDir[x][y] = direction[1][i];
4835 GfxDir[x][y] = MovDir[x][y];
4838 void InitAmoebaNr(int x, int y)
4841 int group_nr = AmoebaNeighbourNr(x, y);
4845 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4847 if (AmoebaCnt[i] == 0)
4855 AmoebaNr[x][y] = group_nr;
4856 AmoebaCnt[group_nr]++;
4857 AmoebaCnt2[group_nr]++;
4860 static void LevelSolved_SetFinalGameValues(void)
4862 game.time_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.time_played :
4863 game.no_level_time_limit ? TimePlayed : TimeLeft);
4864 game.score_time_final = (level.use_step_counter ? TimePlayed :
4865 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4867 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.score :
4868 level.game_engine_type == GAME_ENGINE_TYPE_EM ? game_em.lev->score :
4869 level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.score :
4872 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4873 MM_HEALTH(game_mm.laser_overload_value) :
4876 game.LevelSolved_CountingTime = game.time_final;
4877 game.LevelSolved_CountingScore = game.score_final;
4878 game.LevelSolved_CountingHealth = game.health_final;
4881 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4883 game.LevelSolved_CountingTime = time;
4884 game.LevelSolved_CountingScore = score;
4885 game.LevelSolved_CountingHealth = health;
4887 game_panel_controls[GAME_PANEL_TIME].value = time;
4888 game_panel_controls[GAME_PANEL_SCORE].value = score;
4889 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4891 DisplayGameControlValues();
4894 static void LevelSolved(void)
4896 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4897 game.players_still_needed > 0)
4900 game.LevelSolved = TRUE;
4901 game.GameOver = TRUE;
4905 // needed here to display correct panel values while player walks into exit
4906 LevelSolved_SetFinalGameValues();
4909 static boolean AdvanceToNextLevel(void)
4911 if (setup.increment_levels &&
4912 level_nr < leveldir_current->last_level &&
4915 level_nr++; // advance to next level
4916 TapeErase(); // start with empty tape
4918 if (setup.auto_play_next_level)
4920 scores.continue_playing = TRUE;
4921 scores.next_level_nr = level_nr;
4923 LoadLevel(level_nr);
4925 SaveLevelSetup_SeriesInfo();
4936 static int time_count_steps;
4937 static int time, time_final;
4938 static float score, score_final; // needed for time score < 10 for 10 seconds
4939 static int health, health_final;
4940 static int game_over_delay_1 = 0;
4941 static int game_over_delay_2 = 0;
4942 static int game_over_delay_3 = 0;
4943 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4944 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4946 if (!game.LevelSolved_GameWon)
4950 // do not start end game actions before the player stops moving (to exit)
4951 if (local_player->active && local_player->MovPos)
4954 // calculate final game values after player finished walking into exit
4955 LevelSolved_SetFinalGameValues();
4957 game.LevelSolved_GameWon = TRUE;
4958 game.LevelSolved_SaveTape = tape.recording;
4959 game.LevelSolved_SaveScore = !tape.playing;
4963 LevelStats_incSolved(level_nr);
4965 SaveLevelSetup_SeriesInfo();
4968 if (tape.auto_play) // tape might already be stopped here
4969 tape.auto_play_level_solved = TRUE;
4973 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4974 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4975 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4977 time = time_final = game.time_final;
4978 score = score_final = game.score_final;
4979 health = health_final = game.health_final;
4981 // update game panel values before (delayed) counting of score (if any)
4982 LevelSolved_DisplayFinalGameValues(time, score, health);
4984 // if level has time score defined, calculate new final game values
4987 int time_final_max = 999;
4988 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4989 int time_frames = 0;
4990 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4991 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4996 time_frames = time_frames_left;
4998 else if (game.no_level_time_limit && TimePlayed < time_final_max)
5000 time_final = time_final_max;
5001 time_frames = time_frames_final_max - time_frames_played;
5004 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
5006 time_count_steps = MAX(1, ABS(time_final - time) / 100);
5008 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
5010 // keep previous values (final values already processed here)
5012 score_final = score;
5014 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
5017 score_final += health * time_score;
5020 game.score_final = score_final;
5021 game.health_final = health_final;
5024 // if not counting score after game, immediately update game panel values
5025 if (level_editor_test_game || !setup.count_score_after_game ||
5026 level.game_engine_type == GAME_ENGINE_TYPE_BD)
5029 score = score_final;
5031 LevelSolved_DisplayFinalGameValues(time, score, health);
5034 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
5036 // check if last player has left the level
5037 if (game.exit_x >= 0 &&
5040 int x = game.exit_x;
5041 int y = game.exit_y;
5042 int element = Tile[x][y];
5044 // close exit door after last player
5045 if ((game.all_players_gone &&
5046 (element == EL_EXIT_OPEN ||
5047 element == EL_SP_EXIT_OPEN ||
5048 element == EL_STEEL_EXIT_OPEN)) ||
5049 element == EL_EM_EXIT_OPEN ||
5050 element == EL_EM_STEEL_EXIT_OPEN)
5054 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
5055 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
5056 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
5057 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
5058 EL_EM_STEEL_EXIT_CLOSING);
5060 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
5063 // player disappears
5064 DrawLevelField(x, y);
5067 for (i = 0; i < MAX_PLAYERS; i++)
5069 struct PlayerInfo *player = &stored_player[i];
5071 if (player->present)
5073 RemovePlayer(player);
5075 // player disappears
5076 DrawLevelField(player->jx, player->jy);
5081 PlaySound(SND_GAME_WINNING);
5084 if (setup.count_score_after_game)
5086 if (time != time_final)
5088 if (game_over_delay_1 > 0)
5090 game_over_delay_1--;
5095 int time_to_go = ABS(time_final - time);
5096 int time_count_dir = (time < time_final ? +1 : -1);
5098 if (time_to_go < time_count_steps)
5099 time_count_steps = 1;
5101 time += time_count_steps * time_count_dir;
5102 score += time_count_steps * time_score;
5104 // set final score to correct rounding differences after counting score
5105 if (time == time_final)
5106 score = score_final;
5108 LevelSolved_DisplayFinalGameValues(time, score, health);
5110 if (time == time_final)
5111 StopSound(SND_GAME_LEVELTIME_BONUS);
5112 else if (setup.sound_loops)
5113 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5115 PlaySound(SND_GAME_LEVELTIME_BONUS);
5120 if (health != health_final)
5122 if (game_over_delay_2 > 0)
5124 game_over_delay_2--;
5129 int health_count_dir = (health < health_final ? +1 : -1);
5131 health += health_count_dir;
5132 score += time_score;
5134 LevelSolved_DisplayFinalGameValues(time, score, health);
5136 if (health == health_final)
5137 StopSound(SND_GAME_LEVELTIME_BONUS);
5138 else if (setup.sound_loops)
5139 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5141 PlaySound(SND_GAME_LEVELTIME_BONUS);
5147 game.panel.active = FALSE;
5149 if (game_over_delay_3 > 0)
5151 game_over_delay_3--;
5161 // used instead of "level_nr" (needed for network games)
5162 int last_level_nr = levelset.level_nr;
5163 boolean tape_saved = FALSE;
5164 boolean game_over = checkGameFailed();
5166 // Important note: This function is not only called after "GameWon()", but also after
5167 // "game over" (if automatically asking for restarting the game is disabled in setup)
5169 // do not handle game end if game over and automatically asking for game restart
5170 if (game_over && setup.ask_on_game_over)
5173 // do not handle game end if request dialog is already active
5174 if (checkRequestActive())
5177 if (game.LevelSolved)
5178 game.LevelSolved_GameEnd = TRUE;
5180 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5182 // make sure that request dialog to save tape does not open door again
5183 if (!global.use_envelope_request)
5184 CloseDoor(DOOR_CLOSE_1);
5187 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5189 // set unique basename for score tape (also saved in high score table)
5190 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5193 // if no tape is to be saved, close both doors simultaneously
5194 CloseDoor(DOOR_CLOSE_ALL);
5196 if (level_editor_test_game || score_info_tape_play)
5198 SetGameStatus(GAME_MODE_MAIN);
5205 if (!game.GamePlayed || (!game.LevelSolved_SaveScore && !level.bd_intermission))
5207 SetGameStatus(GAME_MODE_MAIN);
5214 if (level_nr == leveldir_current->handicap_level)
5216 leveldir_current->handicap_level++;
5218 SaveLevelSetup_SeriesInfo();
5221 // save score and score tape before potentially erasing tape below
5222 if (game.LevelSolved_SaveScore)
5223 NewHighScore(last_level_nr, tape_saved);
5225 // increment and load next level (if possible and not configured otherwise)
5226 AdvanceToNextLevel();
5228 if (game.LevelSolved_SaveScore && scores.last_added >= 0 && setup.show_scores_after_game)
5230 SetGameStatus(GAME_MODE_SCORES);
5232 DrawHallOfFame(last_level_nr);
5234 else if (scores.continue_playing)
5236 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5240 SetGameStatus(GAME_MODE_MAIN);
5246 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5247 boolean one_score_entry_per_name)
5251 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5254 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5256 struct ScoreEntry *entry = &list->entry[i];
5257 boolean score_is_better = (new_entry->score > entry->score);
5258 boolean score_is_equal = (new_entry->score == entry->score);
5259 boolean time_is_better = (new_entry->time < entry->time);
5260 boolean time_is_equal = (new_entry->time == entry->time);
5261 boolean better_by_score = (score_is_better ||
5262 (score_is_equal && time_is_better));
5263 boolean better_by_time = (time_is_better ||
5264 (time_is_equal && score_is_better));
5265 boolean is_better = (level.rate_time_over_score ? better_by_time :
5267 boolean entry_is_empty = (entry->score == 0 &&
5270 // prevent adding server score entries if also existing in local score file
5271 // (special case: historic score entries have an empty tape basename entry)
5272 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5273 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5275 // add fields from server score entry not stored in local score entry
5276 // (currently, this means setting platform, version and country fields;
5277 // in rare cases, this may also correct an invalid score value, as
5278 // historic scores might have been truncated to 16-bit values locally)
5279 *entry = *new_entry;
5284 if (is_better || entry_is_empty)
5286 // player has made it to the hall of fame
5288 if (i < MAX_SCORE_ENTRIES - 1)
5290 int m = MAX_SCORE_ENTRIES - 1;
5293 if (one_score_entry_per_name)
5295 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5296 if (strEqual(list->entry[l].name, new_entry->name))
5299 if (m == i) // player's new highscore overwrites his old one
5303 for (l = m; l > i; l--)
5304 list->entry[l] = list->entry[l - 1];
5309 *entry = *new_entry;
5313 else if (one_score_entry_per_name &&
5314 strEqual(entry->name, new_entry->name))
5316 // player already in high score list with better score or time
5322 // special case: new score is beyond the last high score list position
5323 return MAX_SCORE_ENTRIES;
5326 void NewHighScore(int level_nr, boolean tape_saved)
5328 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5329 boolean one_per_name = FALSE;
5331 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5332 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5334 new_entry.score = game.score_final;
5335 new_entry.time = game.score_time_final;
5337 LoadScore(level_nr);
5339 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5341 if (scores.last_added >= MAX_SCORE_ENTRIES)
5343 scores.last_added = MAX_SCORE_ENTRIES - 1;
5344 scores.force_last_added = TRUE;
5346 scores.entry[scores.last_added] = new_entry;
5348 // store last added local score entry (before merging server scores)
5349 scores.last_added_local = scores.last_added;
5354 if (scores.last_added < 0)
5357 SaveScore(level_nr);
5359 // store last added local score entry (before merging server scores)
5360 scores.last_added_local = scores.last_added;
5362 if (!game.LevelSolved_SaveTape)
5365 SaveScoreTape(level_nr);
5367 if (setup.ask_for_using_api_server)
5369 setup.use_api_server =
5370 Request("Upload your score and tape to the high score server?", REQ_ASK);
5372 if (!setup.use_api_server)
5373 Request("Not using high score server! Use setup menu to enable again!",
5376 runtime.use_api_server = setup.use_api_server;
5378 // after asking for using API server once, do not ask again
5379 setup.ask_for_using_api_server = FALSE;
5381 SaveSetup_ServerSetup();
5384 SaveServerScore(level_nr, tape_saved);
5387 void MergeServerScore(void)
5389 struct ScoreEntry last_added_entry;
5390 boolean one_per_name = FALSE;
5393 if (scores.last_added >= 0)
5394 last_added_entry = scores.entry[scores.last_added];
5396 for (i = 0; i < server_scores.num_entries; i++)
5398 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5400 if (pos >= 0 && pos <= scores.last_added)
5401 scores.last_added++;
5404 if (scores.last_added >= MAX_SCORE_ENTRIES)
5406 scores.last_added = MAX_SCORE_ENTRIES - 1;
5407 scores.force_last_added = TRUE;
5409 scores.entry[scores.last_added] = last_added_entry;
5413 static int getElementMoveStepsizeExt(int x, int y, int direction)
5415 int element = Tile[x][y];
5416 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5417 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5418 int horiz_move = (dx != 0);
5419 int sign = (horiz_move ? dx : dy);
5420 int step = sign * element_info[element].move_stepsize;
5422 // special values for move stepsize for spring and things on conveyor belt
5425 if (CAN_FALL(element) &&
5426 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5427 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5428 else if (element == EL_SPRING)
5429 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5435 static int getElementMoveStepsize(int x, int y)
5437 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5440 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5442 if (player->GfxAction != action || player->GfxDir != dir)
5444 player->GfxAction = action;
5445 player->GfxDir = dir;
5447 player->StepFrame = 0;
5451 static void ResetGfxFrame(int x, int y)
5453 // profiling showed that "autotest" spends 10~20% of its time in this function
5454 if (DrawingDeactivatedField())
5457 int element = Tile[x][y];
5458 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5460 if (graphic_info[graphic].anim_global_sync)
5461 GfxFrame[x][y] = FrameCounter;
5462 else if (graphic_info[graphic].anim_global_anim_sync)
5463 GfxFrame[x][y] = getGlobalAnimSyncFrame();
5464 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5465 GfxFrame[x][y] = CustomValue[x][y];
5466 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5467 GfxFrame[x][y] = element_info[element].collect_score;
5468 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5469 GfxFrame[x][y] = ChangeDelay[x][y];
5472 static void ResetGfxAnimation(int x, int y)
5474 GfxAction[x][y] = ACTION_DEFAULT;
5475 GfxDir[x][y] = MovDir[x][y];
5478 ResetGfxFrame(x, y);
5481 static void ResetRandomAnimationValue(int x, int y)
5483 GfxRandom[x][y] = INIT_GFX_RANDOM();
5486 static void InitMovingField(int x, int y, int direction)
5488 int element = Tile[x][y];
5489 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5490 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5493 boolean is_moving_before, is_moving_after;
5495 // check if element was/is moving or being moved before/after mode change
5496 is_moving_before = (WasJustMoving[x][y] != 0);
5497 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5499 // reset animation only for moving elements which change direction of moving
5500 // or which just started or stopped moving
5501 // (else CEs with property "can move" / "not moving" are reset each frame)
5502 if (is_moving_before != is_moving_after ||
5503 direction != MovDir[x][y])
5504 ResetGfxAnimation(x, y);
5506 MovDir[x][y] = direction;
5507 GfxDir[x][y] = direction;
5509 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5510 direction == MV_DOWN && CAN_FALL(element) ?
5511 ACTION_FALLING : ACTION_MOVING);
5513 // this is needed for CEs with property "can move" / "not moving"
5515 if (is_moving_after)
5517 if (Tile[newx][newy] == EL_EMPTY)
5518 Tile[newx][newy] = EL_BLOCKED;
5520 MovDir[newx][newy] = MovDir[x][y];
5522 CustomValue[newx][newy] = CustomValue[x][y];
5524 GfxFrame[newx][newy] = GfxFrame[x][y];
5525 GfxRandom[newx][newy] = GfxRandom[x][y];
5526 GfxAction[newx][newy] = GfxAction[x][y];
5527 GfxDir[newx][newy] = GfxDir[x][y];
5531 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5533 int direction = MovDir[x][y];
5534 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5535 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5541 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5543 int direction = MovDir[x][y];
5544 int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0);
5545 int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0);
5547 *comes_from_x = oldx;
5548 *comes_from_y = oldy;
5551 static int MovingOrBlocked2Element(int x, int y)
5553 int element = Tile[x][y];
5555 if (element == EL_BLOCKED)
5559 Blocked2Moving(x, y, &oldx, &oldy);
5561 return Tile[oldx][oldy];
5567 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5569 // like MovingOrBlocked2Element(), but if element is moving
5570 // and (x, y) is the field the moving element is just leaving,
5571 // return EL_BLOCKED instead of the element value
5572 int element = Tile[x][y];
5574 if (IS_MOVING(x, y))
5576 if (element == EL_BLOCKED)
5580 Blocked2Moving(x, y, &oldx, &oldy);
5581 return Tile[oldx][oldy];
5590 static void RemoveField(int x, int y)
5592 Tile[x][y] = EL_EMPTY;
5598 CustomValue[x][y] = 0;
5601 ChangeDelay[x][y] = 0;
5602 ChangePage[x][y] = -1;
5603 Pushed[x][y] = FALSE;
5605 GfxElement[x][y] = EL_UNDEFINED;
5606 GfxAction[x][y] = ACTION_DEFAULT;
5607 GfxDir[x][y] = MV_NONE;
5610 static void RemoveMovingField(int x, int y)
5612 int oldx = x, oldy = y, newx = x, newy = y;
5613 int element = Tile[x][y];
5614 int next_element = EL_UNDEFINED;
5616 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5619 if (IS_MOVING(x, y))
5621 Moving2Blocked(x, y, &newx, &newy);
5623 if (Tile[newx][newy] != EL_BLOCKED)
5625 // element is moving, but target field is not free (blocked), but
5626 // already occupied by something different (example: acid pool);
5627 // in this case, only remove the moving field, but not the target
5629 RemoveField(oldx, oldy);
5631 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5633 TEST_DrawLevelField(oldx, oldy);
5638 else if (element == EL_BLOCKED)
5640 Blocked2Moving(x, y, &oldx, &oldy);
5641 if (!IS_MOVING(oldx, oldy))
5645 if (element == EL_BLOCKED &&
5646 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5647 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5648 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5649 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5650 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5651 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5652 next_element = get_next_element(Tile[oldx][oldy]);
5654 RemoveField(oldx, oldy);
5655 RemoveField(newx, newy);
5657 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5659 if (next_element != EL_UNDEFINED)
5660 Tile[oldx][oldy] = next_element;
5662 TEST_DrawLevelField(oldx, oldy);
5663 TEST_DrawLevelField(newx, newy);
5666 void DrawDynamite(int x, int y)
5668 int sx = SCREENX(x), sy = SCREENY(y);
5669 int graphic = el2img(Tile[x][y]);
5672 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5675 if (IS_WALKABLE_INSIDE(Back[x][y]))
5679 DrawLevelElement(x, y, Back[x][y]);
5680 else if (Store[x][y])
5681 DrawLevelElement(x, y, Store[x][y]);
5682 else if (game.use_masked_elements)
5683 DrawLevelElement(x, y, EL_EMPTY);
5685 frame = getGraphicAnimationFrameXY(graphic, x, y);
5687 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5688 DrawGraphicThruMask(sx, sy, graphic, frame);
5690 DrawGraphic(sx, sy, graphic, frame);
5693 static void CheckDynamite(int x, int y)
5695 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5699 if (MovDelay[x][y] != 0)
5702 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5708 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5713 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5715 boolean num_checked_players = 0;
5718 for (i = 0; i < MAX_PLAYERS; i++)
5720 if (stored_player[i].active)
5722 int sx = stored_player[i].jx;
5723 int sy = stored_player[i].jy;
5725 if (num_checked_players == 0)
5732 *sx1 = MIN(*sx1, sx);
5733 *sy1 = MIN(*sy1, sy);
5734 *sx2 = MAX(*sx2, sx);
5735 *sy2 = MAX(*sy2, sy);
5738 num_checked_players++;
5743 static boolean checkIfAllPlayersFitToScreen_RND(void)
5745 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5747 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5749 return (sx2 - sx1 < SCR_FIELDX &&
5750 sy2 - sy1 < SCR_FIELDY);
5753 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5755 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5757 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5759 *sx = (sx1 + sx2) / 2;
5760 *sy = (sy1 + sy2) / 2;
5763 static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
5764 boolean center_screen, boolean quick_relocation)
5766 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5767 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5768 boolean no_delay = (tape.warp_forward);
5769 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5770 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5771 int new_scroll_x, new_scroll_y;
5773 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5775 // case 1: quick relocation inside visible screen (without scrolling)
5782 if (!level.shifted_relocation || center_screen)
5784 // relocation _with_ centering of screen
5786 new_scroll_x = SCROLL_POSITION_X(x);
5787 new_scroll_y = SCROLL_POSITION_Y(y);
5791 // relocation _without_ centering of screen
5793 // apply distance between old and new player position to scroll position
5794 int shifted_scroll_x = scroll_x + (x - old_x);
5795 int shifted_scroll_y = scroll_y + (y - old_y);
5797 // make sure that shifted scroll position does not scroll beyond screen
5798 new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
5799 new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
5801 // special case for teleporting from one end of the playfield to the other
5802 // (this kludge prevents the destination area to be shifted by half a tile
5803 // against the source destination for even screen width or screen height;
5804 // probably most useful when used with high "game.forced_scroll_delay_value"
5805 // in combination with "game.forced_scroll_x" and "game.forced_scroll_y")
5806 if (quick_relocation)
5808 if (EVEN(SCR_FIELDX))
5810 // relocate (teleport) between left and right border (half or full)
5811 if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1)
5812 new_scroll_x = SBX_Right;
5813 else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right)
5814 new_scroll_x = SBX_Right - 1;
5815 else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1)
5816 new_scroll_x = SBX_Left;
5817 else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left)
5818 new_scroll_x = SBX_Left + 1;
5821 if (EVEN(SCR_FIELDY))
5823 // relocate (teleport) between top and bottom border (half or full)
5824 if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1)
5825 new_scroll_y = SBY_Lower;
5826 else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower)
5827 new_scroll_y = SBY_Lower - 1;
5828 else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1)
5829 new_scroll_y = SBY_Upper;
5830 else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper)
5831 new_scroll_y = SBY_Upper + 1;
5836 if (quick_relocation)
5838 // case 2: quick relocation (redraw without visible scrolling)
5840 scroll_x = new_scroll_x;
5841 scroll_y = new_scroll_y;
5848 // case 3: visible relocation (with scrolling to new position)
5850 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5852 SetVideoFrameDelay(wait_delay_value);
5854 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5856 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5857 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5859 if (dx == 0 && dy == 0) // no scrolling needed at all
5865 // set values for horizontal/vertical screen scrolling (half tile size)
5866 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5867 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5868 int pos_x = dx * TILEX / 2;
5869 int pos_y = dy * TILEY / 2;
5870 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5871 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5873 ScrollLevel(dx, dy);
5876 // scroll in two steps of half tile size to make things smoother
5877 BlitScreenToBitmapExt_RND(window, fx, fy);
5879 // scroll second step to align at full tile size
5880 BlitScreenToBitmap(window);
5886 SetVideoFrameDelay(frame_delay_value_old);
5889 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5891 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5892 int player_nr = GET_PLAYER_NR(el_player);
5893 struct PlayerInfo *player = &stored_player[player_nr];
5894 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5895 boolean no_delay = (tape.warp_forward);
5896 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5897 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5898 int old_jx = player->jx;
5899 int old_jy = player->jy;
5900 int old_element = Tile[old_jx][old_jy];
5901 int element = Tile[jx][jy];
5902 boolean player_relocated = (old_jx != jx || old_jy != jy);
5904 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5905 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5906 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5907 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5908 int leave_side_horiz = move_dir_horiz;
5909 int leave_side_vert = move_dir_vert;
5910 int enter_side = enter_side_horiz | enter_side_vert;
5911 int leave_side = leave_side_horiz | leave_side_vert;
5913 if (player->buried) // do not reanimate dead player
5916 if (!player_relocated) // no need to relocate the player
5919 if (IS_PLAYER(jx, jy)) // player already placed at new position
5921 RemoveField(jx, jy); // temporarily remove newly placed player
5922 DrawLevelField(jx, jy);
5925 if (player->present)
5927 while (player->MovPos)
5929 ScrollPlayer(player, SCROLL_GO_ON);
5930 ScrollScreen(NULL, SCROLL_GO_ON);
5932 AdvanceFrameAndPlayerCounters(player->index_nr);
5936 BackToFront_WithFrameDelay(wait_delay_value);
5939 DrawPlayer(player); // needed here only to cleanup last field
5940 DrawLevelField(player->jx, player->jy); // remove player graphic
5942 player->is_moving = FALSE;
5945 if (IS_CUSTOM_ELEMENT(old_element))
5946 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5948 player->index_bit, leave_side);
5950 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5952 player->index_bit, leave_side);
5954 Tile[jx][jy] = el_player;
5955 InitPlayerField(jx, jy, el_player, TRUE);
5957 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5958 possible that the relocation target field did not contain a player element,
5959 but a walkable element, to which the new player was relocated -- in this
5960 case, restore that (already initialized!) element on the player field */
5961 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5963 Tile[jx][jy] = element; // restore previously existing element
5966 // only visually relocate centered player
5967 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy,
5968 FALSE, level.instant_relocation);
5970 TestIfPlayerTouchesBadThing(jx, jy);
5971 TestIfPlayerTouchesCustomElement(jx, jy);
5973 if (IS_CUSTOM_ELEMENT(element))
5974 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5975 player->index_bit, enter_side);
5977 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5978 player->index_bit, enter_side);
5980 if (player->is_switching)
5982 /* ensure that relocation while still switching an element does not cause
5983 a new element to be treated as also switched directly after relocation
5984 (this is important for teleporter switches that teleport the player to
5985 a place where another teleporter switch is in the same direction, which
5986 would then incorrectly be treated as immediately switched before the
5987 direction key that caused the switch was released) */
5989 player->switch_x += jx - old_jx;
5990 player->switch_y += jy - old_jy;
5994 static void Explode(int ex, int ey, int phase, int mode)
6000 if (game.explosions_delayed)
6002 ExplodeField[ex][ey] = mode;
6006 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
6008 int center_element = Tile[ex][ey];
6009 int ce_value = CustomValue[ex][ey];
6010 int ce_score = element_info[center_element].collect_score;
6011 int artwork_element, explosion_element; // set these values later
6013 // remove things displayed in background while burning dynamite
6014 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
6017 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
6019 // put moving element to center field (and let it explode there)
6020 center_element = MovingOrBlocked2Element(ex, ey);
6021 RemoveMovingField(ex, ey);
6022 Tile[ex][ey] = center_element;
6025 // now "center_element" is finally determined -- set related values now
6026 artwork_element = center_element; // for custom player artwork
6027 explosion_element = center_element; // for custom player artwork
6029 if (IS_PLAYER(ex, ey))
6031 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
6033 artwork_element = stored_player[player_nr].artwork_element;
6035 if (level.use_explosion_element[player_nr])
6037 explosion_element = level.explosion_element[player_nr];
6038 artwork_element = explosion_element;
6042 if (mode == EX_TYPE_NORMAL ||
6043 mode == EX_TYPE_CENTER ||
6044 mode == EX_TYPE_CROSS)
6045 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
6047 last_phase = element_info[explosion_element].explosion_delay + 1;
6049 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
6051 int xx = x - ex + 1;
6052 int yy = y - ey + 1;
6055 if (!IN_LEV_FIELD(x, y) ||
6056 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
6057 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
6060 element = Tile[x][y];
6062 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
6064 element = MovingOrBlocked2Element(x, y);
6066 if (!IS_EXPLOSION_PROOF(element))
6067 RemoveMovingField(x, y);
6070 // indestructible elements can only explode in center (but not flames)
6071 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
6072 mode == EX_TYPE_BORDER)) ||
6073 element == EL_FLAMES)
6076 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
6077 behaviour, for example when touching a yamyam that explodes to rocks
6078 with active deadly shield, a rock is created under the player !!! */
6079 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
6081 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
6082 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
6083 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
6085 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
6088 if (IS_ACTIVE_BOMB(element))
6090 // re-activate things under the bomb like gate or penguin
6091 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
6098 // save walkable background elements while explosion on same tile
6099 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
6100 (x != ex || y != ey || mode == EX_TYPE_BORDER))
6101 Back[x][y] = element;
6103 // ignite explodable elements reached by other explosion
6104 if (element == EL_EXPLOSION)
6105 element = Store2[x][y];
6107 if (AmoebaNr[x][y] &&
6108 (element == EL_AMOEBA_FULL ||
6109 element == EL_BD_AMOEBA ||
6110 element == EL_AMOEBA_GROWING))
6112 AmoebaCnt[AmoebaNr[x][y]]--;
6113 AmoebaCnt2[AmoebaNr[x][y]]--;
6118 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
6120 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
6122 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
6124 if (PLAYERINFO(ex, ey)->use_murphy)
6125 Store[x][y] = EL_EMPTY;
6128 // !!! check this case -- currently needed for rnd_rado_negundo_v,
6129 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
6130 else if (IS_PLAYER_ELEMENT(center_element))
6131 Store[x][y] = EL_EMPTY;
6132 else if (center_element == EL_YAMYAM)
6133 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
6134 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
6135 Store[x][y] = element_info[center_element].content.e[xx][yy];
6137 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
6138 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
6139 // otherwise) -- FIX THIS !!!
6140 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
6141 Store[x][y] = element_info[element].content.e[1][1];
6143 else if (!CAN_EXPLODE(element))
6144 Store[x][y] = element_info[element].content.e[1][1];
6147 Store[x][y] = EL_EMPTY;
6149 if (IS_CUSTOM_ELEMENT(center_element))
6150 Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value :
6151 Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score :
6152 Store[x][y] >= EL_PREV_CE_8 &&
6153 Store[x][y] <= EL_NEXT_CE_8 ?
6154 RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) :
6157 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
6158 center_element == EL_AMOEBA_TO_DIAMOND)
6159 Store2[x][y] = element;
6161 Tile[x][y] = EL_EXPLOSION;
6162 GfxElement[x][y] = artwork_element;
6164 ExplodePhase[x][y] = 1;
6165 ExplodeDelay[x][y] = last_phase;
6170 if (center_element == EL_YAMYAM)
6171 game.yamyam_content_nr =
6172 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
6184 GfxFrame[x][y] = 0; // restart explosion animation
6186 last_phase = ExplodeDelay[x][y];
6188 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
6190 // this can happen if the player leaves an explosion just in time
6191 if (GfxElement[x][y] == EL_UNDEFINED)
6192 GfxElement[x][y] = EL_EMPTY;
6194 border_element = Store2[x][y];
6195 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6196 border_element = StorePlayer[x][y];
6198 if (phase == element_info[border_element].ignition_delay ||
6199 phase == last_phase)
6201 boolean border_explosion = FALSE;
6203 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6204 !PLAYER_EXPLOSION_PROTECTED(x, y))
6206 KillPlayerUnlessExplosionProtected(x, y);
6207 border_explosion = TRUE;
6209 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6211 Tile[x][y] = Store2[x][y];
6214 border_explosion = TRUE;
6216 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6218 AmoebaToDiamond(x, y);
6220 border_explosion = TRUE;
6223 // if an element just explodes due to another explosion (chain-reaction),
6224 // do not immediately end the new explosion when it was the last frame of
6225 // the explosion (as it would be done in the following "if"-statement!)
6226 if (border_explosion && phase == last_phase)
6230 // this can happen if the player was just killed by an explosion
6231 if (GfxElement[x][y] == EL_UNDEFINED)
6232 GfxElement[x][y] = EL_EMPTY;
6234 if (phase == last_phase)
6238 element = Tile[x][y] = Store[x][y];
6239 Store[x][y] = Store2[x][y] = 0;
6240 GfxElement[x][y] = EL_UNDEFINED;
6242 // player can escape from explosions and might therefore be still alive
6243 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6244 element <= EL_PLAYER_IS_EXPLODING_4)
6246 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6247 int explosion_element = EL_PLAYER_1 + player_nr;
6248 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6249 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6251 if (level.use_explosion_element[player_nr])
6252 explosion_element = level.explosion_element[player_nr];
6254 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6255 element_info[explosion_element].content.e[xx][yy]);
6258 // restore probably existing indestructible background element
6259 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6260 element = Tile[x][y] = Back[x][y];
6263 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6264 GfxDir[x][y] = MV_NONE;
6265 ChangeDelay[x][y] = 0;
6266 ChangePage[x][y] = -1;
6268 CustomValue[x][y] = 0;
6270 InitField_WithBug2(x, y, FALSE);
6272 TEST_DrawLevelField(x, y);
6274 TestIfElementTouchesCustomElement(x, y);
6276 if (GFX_CRUMBLED(element))
6277 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6279 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6280 StorePlayer[x][y] = 0;
6282 if (IS_PLAYER_ELEMENT(element))
6283 RelocatePlayer(x, y, element);
6285 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6287 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6288 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6291 TEST_DrawLevelFieldCrumbled(x, y);
6293 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6295 DrawLevelElement(x, y, Back[x][y]);
6296 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6298 else if (IS_WALKABLE_UNDER(Back[x][y]))
6300 DrawLevelGraphic(x, y, graphic, frame);
6301 DrawLevelElementThruMask(x, y, Back[x][y]);
6303 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6304 DrawLevelGraphic(x, y, graphic, frame);
6308 static void DynaExplode(int ex, int ey)
6311 int dynabomb_element = Tile[ex][ey];
6312 int dynabomb_size = 1;
6313 boolean dynabomb_xl = FALSE;
6314 struct PlayerInfo *player;
6315 struct XY *xy = xy_topdown;
6317 if (IS_ACTIVE_BOMB(dynabomb_element))
6319 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6320 dynabomb_size = player->dynabomb_size;
6321 dynabomb_xl = player->dynabomb_xl;
6322 player->dynabombs_left++;
6325 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6327 for (i = 0; i < NUM_DIRECTIONS; i++)
6329 for (j = 1; j <= dynabomb_size; j++)
6331 int x = ex + j * xy[i].x;
6332 int y = ey + j * xy[i].y;
6335 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6338 element = Tile[x][y];
6340 // do not restart explosions of fields with active bombs
6341 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6344 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6346 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6347 !IS_DIGGABLE(element) && !dynabomb_xl)
6353 void Bang(int x, int y)
6355 int element = MovingOrBlocked2Element(x, y);
6356 int explosion_type = EX_TYPE_NORMAL;
6358 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6360 struct PlayerInfo *player = PLAYERINFO(x, y);
6362 element = Tile[x][y] = player->initial_element;
6364 if (level.use_explosion_element[player->index_nr])
6366 int explosion_element = level.explosion_element[player->index_nr];
6368 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6369 explosion_type = EX_TYPE_CROSS;
6370 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6371 explosion_type = EX_TYPE_CENTER;
6379 case EL_BD_BUTTERFLY:
6382 case EL_DARK_YAMYAM:
6386 RaiseScoreElement(element);
6389 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6390 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6391 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6392 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6393 case EL_DYNABOMB_INCREASE_NUMBER:
6394 case EL_DYNABOMB_INCREASE_SIZE:
6395 case EL_DYNABOMB_INCREASE_POWER:
6396 explosion_type = EX_TYPE_DYNA;
6399 case EL_DC_LANDMINE:
6400 explosion_type = EX_TYPE_CENTER;
6405 case EL_LAMP_ACTIVE:
6406 case EL_AMOEBA_TO_DIAMOND:
6407 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6408 explosion_type = EX_TYPE_CENTER;
6412 if (element_info[element].explosion_type == EXPLODES_CROSS)
6413 explosion_type = EX_TYPE_CROSS;
6414 else if (element_info[element].explosion_type == EXPLODES_1X1)
6415 explosion_type = EX_TYPE_CENTER;
6419 if (explosion_type == EX_TYPE_DYNA)
6422 Explode(x, y, EX_PHASE_START, explosion_type);
6424 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6427 static void SplashAcid(int x, int y)
6429 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6430 (!IN_LEV_FIELD(x - 1, y - 2) ||
6431 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6432 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6434 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6435 (!IN_LEV_FIELD(x + 1, y - 2) ||
6436 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6437 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6439 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6442 static void InitBeltMovement(void)
6444 static int belt_base_element[4] =
6446 EL_CONVEYOR_BELT_1_LEFT,
6447 EL_CONVEYOR_BELT_2_LEFT,
6448 EL_CONVEYOR_BELT_3_LEFT,
6449 EL_CONVEYOR_BELT_4_LEFT
6451 static int belt_base_active_element[4] =
6453 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6454 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6455 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6456 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6461 // set frame order for belt animation graphic according to belt direction
6462 for (i = 0; i < NUM_BELTS; i++)
6466 for (j = 0; j < NUM_BELT_PARTS; j++)
6468 int element = belt_base_active_element[belt_nr] + j;
6469 int graphic_1 = el2img(element);
6470 int graphic_2 = el2panelimg(element);
6472 if (game.belt_dir[i] == MV_LEFT)
6474 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6475 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6479 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6480 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6485 SCAN_PLAYFIELD(x, y)
6487 int element = Tile[x][y];
6489 for (i = 0; i < NUM_BELTS; i++)
6491 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6493 int e_belt_nr = getBeltNrFromBeltElement(element);
6496 if (e_belt_nr == belt_nr)
6498 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6500 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6507 static void ToggleBeltSwitch(int x, int y)
6509 static int belt_base_element[4] =
6511 EL_CONVEYOR_BELT_1_LEFT,
6512 EL_CONVEYOR_BELT_2_LEFT,
6513 EL_CONVEYOR_BELT_3_LEFT,
6514 EL_CONVEYOR_BELT_4_LEFT
6516 static int belt_base_active_element[4] =
6518 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6519 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6520 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6521 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6523 static int belt_base_switch_element[4] =
6525 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6526 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6527 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6528 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6530 static int belt_move_dir[4] =
6538 int element = Tile[x][y];
6539 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6540 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6541 int belt_dir = belt_move_dir[belt_dir_nr];
6544 if (!IS_BELT_SWITCH(element))
6547 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6548 game.belt_dir[belt_nr] = belt_dir;
6550 if (belt_dir_nr == 3)
6553 // set frame order for belt animation graphic according to belt direction
6554 for (i = 0; i < NUM_BELT_PARTS; i++)
6556 int element = belt_base_active_element[belt_nr] + i;
6557 int graphic_1 = el2img(element);
6558 int graphic_2 = el2panelimg(element);
6560 if (belt_dir == MV_LEFT)
6562 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6563 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6567 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6568 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6572 SCAN_PLAYFIELD(xx, yy)
6574 int element = Tile[xx][yy];
6576 if (IS_BELT_SWITCH(element))
6578 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6580 if (e_belt_nr == belt_nr)
6582 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6583 TEST_DrawLevelField(xx, yy);
6586 else if (IS_BELT(element) && belt_dir != MV_NONE)
6588 int e_belt_nr = getBeltNrFromBeltElement(element);
6590 if (e_belt_nr == belt_nr)
6592 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6594 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6595 TEST_DrawLevelField(xx, yy);
6598 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6600 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6602 if (e_belt_nr == belt_nr)
6604 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6606 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6607 TEST_DrawLevelField(xx, yy);
6613 static void ToggleSwitchgateSwitch(void)
6617 game.switchgate_pos = !game.switchgate_pos;
6619 SCAN_PLAYFIELD(xx, yy)
6621 int element = Tile[xx][yy];
6623 if (element == EL_SWITCHGATE_SWITCH_UP)
6625 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6626 TEST_DrawLevelField(xx, yy);
6628 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6630 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6631 TEST_DrawLevelField(xx, yy);
6633 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6635 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6636 TEST_DrawLevelField(xx, yy);
6638 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6640 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6641 TEST_DrawLevelField(xx, yy);
6643 else if (element == EL_SWITCHGATE_OPEN ||
6644 element == EL_SWITCHGATE_OPENING)
6646 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6648 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6650 else if (element == EL_SWITCHGATE_CLOSED ||
6651 element == EL_SWITCHGATE_CLOSING)
6653 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6655 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6660 static int getInvisibleActiveFromInvisibleElement(int element)
6662 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6663 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6664 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6668 static int getInvisibleFromInvisibleActiveElement(int element)
6670 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6671 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6672 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6676 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6680 SCAN_PLAYFIELD(x, y)
6682 int element = Tile[x][y];
6684 if (element == EL_LIGHT_SWITCH &&
6685 game.light_time_left > 0)
6687 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6688 TEST_DrawLevelField(x, y);
6690 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6691 game.light_time_left == 0)
6693 Tile[x][y] = EL_LIGHT_SWITCH;
6694 TEST_DrawLevelField(x, y);
6696 else if (element == EL_EMC_DRIPPER &&
6697 game.light_time_left > 0)
6699 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6700 TEST_DrawLevelField(x, y);
6702 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6703 game.light_time_left == 0)
6705 Tile[x][y] = EL_EMC_DRIPPER;
6706 TEST_DrawLevelField(x, y);
6708 else if (element == EL_INVISIBLE_STEELWALL ||
6709 element == EL_INVISIBLE_WALL ||
6710 element == EL_INVISIBLE_SAND)
6712 if (game.light_time_left > 0)
6713 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6715 TEST_DrawLevelField(x, y);
6717 // uncrumble neighbour fields, if needed
6718 if (element == EL_INVISIBLE_SAND)
6719 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6721 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6722 element == EL_INVISIBLE_WALL_ACTIVE ||
6723 element == EL_INVISIBLE_SAND_ACTIVE)
6725 if (game.light_time_left == 0)
6726 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6728 TEST_DrawLevelField(x, y);
6730 // re-crumble neighbour fields, if needed
6731 if (element == EL_INVISIBLE_SAND)
6732 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6737 static void RedrawAllInvisibleElementsForLenses(void)
6741 SCAN_PLAYFIELD(x, y)
6743 int element = Tile[x][y];
6745 if (element == EL_EMC_DRIPPER &&
6746 game.lenses_time_left > 0)
6748 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6749 TEST_DrawLevelField(x, y);
6751 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6752 game.lenses_time_left == 0)
6754 Tile[x][y] = EL_EMC_DRIPPER;
6755 TEST_DrawLevelField(x, y);
6757 else if (element == EL_INVISIBLE_STEELWALL ||
6758 element == EL_INVISIBLE_WALL ||
6759 element == EL_INVISIBLE_SAND)
6761 if (game.lenses_time_left > 0)
6762 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6764 TEST_DrawLevelField(x, y);
6766 // uncrumble neighbour fields, if needed
6767 if (element == EL_INVISIBLE_SAND)
6768 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6770 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6771 element == EL_INVISIBLE_WALL_ACTIVE ||
6772 element == EL_INVISIBLE_SAND_ACTIVE)
6774 if (game.lenses_time_left == 0)
6775 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6777 TEST_DrawLevelField(x, y);
6779 // re-crumble neighbour fields, if needed
6780 if (element == EL_INVISIBLE_SAND)
6781 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6786 static void RedrawAllInvisibleElementsForMagnifier(void)
6790 SCAN_PLAYFIELD(x, y)
6792 int element = Tile[x][y];
6794 if (element == EL_EMC_FAKE_GRASS &&
6795 game.magnify_time_left > 0)
6797 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6798 TEST_DrawLevelField(x, y);
6800 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6801 game.magnify_time_left == 0)
6803 Tile[x][y] = EL_EMC_FAKE_GRASS;
6804 TEST_DrawLevelField(x, y);
6806 else if (IS_GATE_GRAY(element) &&
6807 game.magnify_time_left > 0)
6809 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6810 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6811 IS_EM_GATE_GRAY(element) ?
6812 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6813 IS_EMC_GATE_GRAY(element) ?
6814 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6815 IS_DC_GATE_GRAY(element) ?
6816 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6818 TEST_DrawLevelField(x, y);
6820 else if (IS_GATE_GRAY_ACTIVE(element) &&
6821 game.magnify_time_left == 0)
6823 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6824 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6825 IS_EM_GATE_GRAY_ACTIVE(element) ?
6826 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6827 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6828 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6829 IS_DC_GATE_GRAY_ACTIVE(element) ?
6830 EL_DC_GATE_WHITE_GRAY :
6832 TEST_DrawLevelField(x, y);
6837 static void ToggleLightSwitch(int x, int y)
6839 int element = Tile[x][y];
6841 game.light_time_left =
6842 (element == EL_LIGHT_SWITCH ?
6843 level.time_light * FRAMES_PER_SECOND : 0);
6845 RedrawAllLightSwitchesAndInvisibleElements();
6848 static void ActivateTimegateSwitch(int x, int y)
6852 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6854 SCAN_PLAYFIELD(xx, yy)
6856 int element = Tile[xx][yy];
6858 if (element == EL_TIMEGATE_CLOSED ||
6859 element == EL_TIMEGATE_CLOSING)
6861 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6862 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6866 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6868 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6869 TEST_DrawLevelField(xx, yy);
6875 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6876 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6879 static void Impact(int x, int y)
6881 boolean last_line = (y == lev_fieldy - 1);
6882 boolean object_hit = FALSE;
6883 boolean impact = (last_line || object_hit);
6884 int element = Tile[x][y];
6885 int smashed = EL_STEELWALL;
6887 if (!last_line) // check if element below was hit
6889 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6892 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6893 MovDir[x][y + 1] != MV_DOWN ||
6894 MovPos[x][y + 1] <= TILEY / 2));
6896 // do not smash moving elements that left the smashed field in time
6897 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6898 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6901 #if USE_QUICKSAND_IMPACT_BUGFIX
6902 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6904 RemoveMovingField(x, y + 1);
6905 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6906 Tile[x][y + 2] = EL_ROCK;
6907 TEST_DrawLevelField(x, y + 2);
6912 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6914 RemoveMovingField(x, y + 1);
6915 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6916 Tile[x][y + 2] = EL_ROCK;
6917 TEST_DrawLevelField(x, y + 2);
6924 smashed = MovingOrBlocked2Element(x, y + 1);
6926 impact = (last_line || object_hit);
6929 if (!last_line && smashed == EL_ACID) // element falls into acid
6931 SplashAcid(x, y + 1);
6935 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6936 // only reset graphic animation if graphic really changes after impact
6938 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6940 ResetGfxAnimation(x, y);
6941 TEST_DrawLevelField(x, y);
6944 if (impact && CAN_EXPLODE_IMPACT(element))
6949 else if (impact && element == EL_PEARL &&
6950 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6952 ResetGfxAnimation(x, y);
6954 Tile[x][y] = EL_PEARL_BREAKING;
6955 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6958 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6960 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6965 if (impact && element == EL_AMOEBA_DROP)
6967 if (object_hit && IS_PLAYER(x, y + 1))
6968 KillPlayerUnlessEnemyProtected(x, y + 1);
6969 else if (object_hit && smashed == EL_PENGUIN)
6973 Tile[x][y] = EL_AMOEBA_GROWING;
6974 Store[x][y] = EL_AMOEBA_WET;
6976 ResetRandomAnimationValue(x, y);
6981 if (object_hit) // check which object was hit
6983 if ((CAN_PASS_MAGIC_WALL(element) &&
6984 (smashed == EL_MAGIC_WALL ||
6985 smashed == EL_BD_MAGIC_WALL)) ||
6986 (CAN_PASS_DC_MAGIC_WALL(element) &&
6987 smashed == EL_DC_MAGIC_WALL))
6990 int activated_magic_wall =
6991 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6992 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6993 EL_DC_MAGIC_WALL_ACTIVE);
6995 // activate magic wall / mill
6996 SCAN_PLAYFIELD(xx, yy)
6998 if (Tile[xx][yy] == smashed)
6999 Tile[xx][yy] = activated_magic_wall;
7002 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
7003 game.magic_wall_active = TRUE;
7005 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
7006 SND_MAGIC_WALL_ACTIVATING :
7007 smashed == EL_BD_MAGIC_WALL ?
7008 SND_BD_MAGIC_WALL_ACTIVATING :
7009 SND_DC_MAGIC_WALL_ACTIVATING));
7012 if (IS_PLAYER(x, y + 1))
7014 if (CAN_SMASH_PLAYER(element))
7016 KillPlayerUnlessEnemyProtected(x, y + 1);
7020 else if (smashed == EL_PENGUIN)
7022 if (CAN_SMASH_PLAYER(element))
7028 else if (element == EL_BD_DIAMOND)
7030 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
7036 else if (((element == EL_SP_INFOTRON ||
7037 element == EL_SP_ZONK) &&
7038 (smashed == EL_SP_SNIKSNAK ||
7039 smashed == EL_SP_ELECTRON ||
7040 smashed == EL_SP_DISK_ORANGE)) ||
7041 (element == EL_SP_INFOTRON &&
7042 smashed == EL_SP_DISK_YELLOW))
7047 else if (CAN_SMASH_EVERYTHING(element))
7049 if (IS_CLASSIC_ENEMY(smashed) ||
7050 CAN_EXPLODE_SMASHED(smashed))
7055 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
7057 if (smashed == EL_LAMP ||
7058 smashed == EL_LAMP_ACTIVE)
7063 else if (smashed == EL_NUT)
7065 Tile[x][y + 1] = EL_NUT_BREAKING;
7066 PlayLevelSound(x, y, SND_NUT_BREAKING);
7067 RaiseScoreElement(EL_NUT);
7070 else if (smashed == EL_PEARL)
7072 ResetGfxAnimation(x, y);
7074 Tile[x][y + 1] = EL_PEARL_BREAKING;
7075 PlayLevelSound(x, y, SND_PEARL_BREAKING);
7078 else if (smashed == EL_DIAMOND)
7080 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
7081 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
7084 else if (IS_BELT_SWITCH(smashed))
7086 ToggleBeltSwitch(x, y + 1);
7088 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
7089 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
7090 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
7091 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
7093 ToggleSwitchgateSwitch();
7095 else if (smashed == EL_LIGHT_SWITCH ||
7096 smashed == EL_LIGHT_SWITCH_ACTIVE)
7098 ToggleLightSwitch(x, y + 1);
7102 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7104 CheckElementChangeBySide(x, y + 1, smashed, element,
7105 CE_SWITCHED, CH_SIDE_TOP);
7106 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
7112 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7117 // play sound of magic wall / mill
7119 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7120 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
7121 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
7123 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7124 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
7125 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7126 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
7127 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7128 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
7133 // play sound of object that hits the ground
7134 if (last_line || object_hit)
7135 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
7138 static void TurnRoundExt(int x, int y)
7150 { 0, 0 }, { 0, 0 }, { 0, 0 },
7155 int left, right, back;
7159 { MV_DOWN, MV_UP, MV_RIGHT },
7160 { MV_UP, MV_DOWN, MV_LEFT },
7162 { MV_LEFT, MV_RIGHT, MV_DOWN },
7166 { MV_RIGHT, MV_LEFT, MV_UP }
7169 int element = Tile[x][y];
7170 int move_pattern = element_info[element].move_pattern;
7172 int old_move_dir = MovDir[x][y];
7173 int left_dir = turn[old_move_dir].left;
7174 int right_dir = turn[old_move_dir].right;
7175 int back_dir = turn[old_move_dir].back;
7177 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
7178 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
7179 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
7180 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
7182 int left_x = x + left_dx, left_y = y + left_dy;
7183 int right_x = x + right_dx, right_y = y + right_dy;
7184 int move_x = x + move_dx, move_y = y + move_dy;
7188 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
7190 TestIfBadThingTouchesOtherBadThing(x, y);
7192 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
7193 MovDir[x][y] = right_dir;
7194 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7195 MovDir[x][y] = left_dir;
7197 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7199 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7202 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7204 TestIfBadThingTouchesOtherBadThing(x, y);
7206 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7207 MovDir[x][y] = left_dir;
7208 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7209 MovDir[x][y] = right_dir;
7211 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7213 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7216 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7218 TestIfBadThingTouchesOtherBadThing(x, y);
7220 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7221 MovDir[x][y] = left_dir;
7222 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7223 MovDir[x][y] = right_dir;
7225 if (MovDir[x][y] != old_move_dir)
7228 else if (element == EL_YAMYAM)
7230 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7231 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7233 if (can_turn_left && can_turn_right)
7234 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7235 else if (can_turn_left)
7236 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7237 else if (can_turn_right)
7238 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7240 MovDir[x][y] = back_dir;
7242 MovDelay[x][y] = 16 + 16 * RND(3);
7244 else if (element == EL_DARK_YAMYAM)
7246 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7248 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7251 if (can_turn_left && can_turn_right)
7252 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7253 else if (can_turn_left)
7254 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7255 else if (can_turn_right)
7256 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7258 MovDir[x][y] = back_dir;
7260 MovDelay[x][y] = 16 + 16 * RND(3);
7262 else if (element == EL_PACMAN)
7264 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7265 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7267 if (can_turn_left && can_turn_right)
7268 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7269 else if (can_turn_left)
7270 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7271 else if (can_turn_right)
7272 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7274 MovDir[x][y] = back_dir;
7276 MovDelay[x][y] = 6 + RND(40);
7278 else if (element == EL_PIG)
7280 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7281 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7282 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7283 boolean should_turn_left, should_turn_right, should_move_on;
7285 int rnd = RND(rnd_value);
7287 should_turn_left = (can_turn_left &&
7289 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7290 y + back_dy + left_dy)));
7291 should_turn_right = (can_turn_right &&
7293 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7294 y + back_dy + right_dy)));
7295 should_move_on = (can_move_on &&
7298 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7299 y + move_dy + left_dy) ||
7300 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7301 y + move_dy + right_dy)));
7303 if (should_turn_left || should_turn_right || should_move_on)
7305 if (should_turn_left && should_turn_right && should_move_on)
7306 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7307 rnd < 2 * rnd_value / 3 ? right_dir :
7309 else if (should_turn_left && should_turn_right)
7310 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7311 else if (should_turn_left && should_move_on)
7312 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7313 else if (should_turn_right && should_move_on)
7314 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7315 else if (should_turn_left)
7316 MovDir[x][y] = left_dir;
7317 else if (should_turn_right)
7318 MovDir[x][y] = right_dir;
7319 else if (should_move_on)
7320 MovDir[x][y] = old_move_dir;
7322 else if (can_move_on && rnd > rnd_value / 8)
7323 MovDir[x][y] = old_move_dir;
7324 else if (can_turn_left && can_turn_right)
7325 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7326 else if (can_turn_left && rnd > rnd_value / 8)
7327 MovDir[x][y] = left_dir;
7328 else if (can_turn_right && rnd > rnd_value/8)
7329 MovDir[x][y] = right_dir;
7331 MovDir[x][y] = back_dir;
7333 xx = x + move_xy[MovDir[x][y]].dx;
7334 yy = y + move_xy[MovDir[x][y]].dy;
7336 if (!IN_LEV_FIELD(xx, yy) ||
7337 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7338 MovDir[x][y] = old_move_dir;
7342 else if (element == EL_DRAGON)
7344 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7345 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7346 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7348 int rnd = RND(rnd_value);
7350 if (can_move_on && rnd > rnd_value / 8)
7351 MovDir[x][y] = old_move_dir;
7352 else if (can_turn_left && can_turn_right)
7353 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7354 else if (can_turn_left && rnd > rnd_value / 8)
7355 MovDir[x][y] = left_dir;
7356 else if (can_turn_right && rnd > rnd_value / 8)
7357 MovDir[x][y] = right_dir;
7359 MovDir[x][y] = back_dir;
7361 xx = x + move_xy[MovDir[x][y]].dx;
7362 yy = y + move_xy[MovDir[x][y]].dy;
7364 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7365 MovDir[x][y] = old_move_dir;
7369 else if (element == EL_MOLE)
7371 boolean can_move_on =
7372 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7373 IS_AMOEBOID(Tile[move_x][move_y]) ||
7374 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7377 boolean can_turn_left =
7378 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7379 IS_AMOEBOID(Tile[left_x][left_y])));
7381 boolean can_turn_right =
7382 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7383 IS_AMOEBOID(Tile[right_x][right_y])));
7385 if (can_turn_left && can_turn_right)
7386 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7387 else if (can_turn_left)
7388 MovDir[x][y] = left_dir;
7390 MovDir[x][y] = right_dir;
7393 if (MovDir[x][y] != old_move_dir)
7396 else if (element == EL_BALLOON)
7398 MovDir[x][y] = game.wind_direction;
7401 else if (element == EL_SPRING)
7403 if (MovDir[x][y] & MV_HORIZONTAL)
7405 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7406 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7408 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7409 ResetGfxAnimation(move_x, move_y);
7410 TEST_DrawLevelField(move_x, move_y);
7412 MovDir[x][y] = back_dir;
7414 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7415 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7416 MovDir[x][y] = MV_NONE;
7421 else if (element == EL_ROBOT ||
7422 element == EL_SATELLITE ||
7423 element == EL_PENGUIN ||
7424 element == EL_EMC_ANDROID)
7426 int attr_x = -1, attr_y = -1;
7428 if (game.all_players_gone)
7430 attr_x = game.exit_x;
7431 attr_y = game.exit_y;
7437 for (i = 0; i < MAX_PLAYERS; i++)
7439 struct PlayerInfo *player = &stored_player[i];
7440 int jx = player->jx, jy = player->jy;
7442 if (!player->active)
7446 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7454 if (element == EL_ROBOT &&
7455 game.robot_wheel_x >= 0 &&
7456 game.robot_wheel_y >= 0 &&
7457 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7458 game.engine_version < VERSION_IDENT(3,1,0,0)))
7460 attr_x = game.robot_wheel_x;
7461 attr_y = game.robot_wheel_y;
7464 if (element == EL_PENGUIN)
7467 struct XY *xy = xy_topdown;
7469 for (i = 0; i < NUM_DIRECTIONS; i++)
7471 int ex = x + xy[i].x;
7472 int ey = y + xy[i].y;
7474 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7475 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7476 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7477 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7486 MovDir[x][y] = MV_NONE;
7488 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7489 else if (attr_x > x)
7490 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7492 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7493 else if (attr_y > y)
7494 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7496 if (element == EL_ROBOT)
7500 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7501 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7502 Moving2Blocked(x, y, &newx, &newy);
7504 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7505 MovDelay[x][y] = 8 + 8 * !RND(3);
7507 MovDelay[x][y] = 16;
7509 else if (element == EL_PENGUIN)
7515 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7517 boolean first_horiz = RND(2);
7518 int new_move_dir = MovDir[x][y];
7521 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7522 Moving2Blocked(x, y, &newx, &newy);
7524 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7528 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7529 Moving2Blocked(x, y, &newx, &newy);
7531 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7534 MovDir[x][y] = old_move_dir;
7538 else if (element == EL_SATELLITE)
7544 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7546 boolean first_horiz = RND(2);
7547 int new_move_dir = MovDir[x][y];
7550 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7551 Moving2Blocked(x, y, &newx, &newy);
7553 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7557 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7558 Moving2Blocked(x, y, &newx, &newy);
7560 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7563 MovDir[x][y] = old_move_dir;
7567 else if (element == EL_EMC_ANDROID)
7569 static int check_pos[16] =
7571 -1, // 0 => (invalid)
7574 -1, // 3 => (invalid)
7576 0, // 5 => MV_LEFT | MV_UP
7577 2, // 6 => MV_RIGHT | MV_UP
7578 -1, // 7 => (invalid)
7580 6, // 9 => MV_LEFT | MV_DOWN
7581 4, // 10 => MV_RIGHT | MV_DOWN
7582 -1, // 11 => (invalid)
7583 -1, // 12 => (invalid)
7584 -1, // 13 => (invalid)
7585 -1, // 14 => (invalid)
7586 -1, // 15 => (invalid)
7594 { -1, -1, MV_LEFT | MV_UP },
7596 { +1, -1, MV_RIGHT | MV_UP },
7597 { +1, 0, MV_RIGHT },
7598 { +1, +1, MV_RIGHT | MV_DOWN },
7600 { -1, +1, MV_LEFT | MV_DOWN },
7603 int start_pos, check_order;
7604 boolean can_clone = FALSE;
7607 // check if there is any free field around current position
7608 for (i = 0; i < 8; i++)
7610 int newx = x + check_xy[i].dx;
7611 int newy = y + check_xy[i].dy;
7613 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7621 if (can_clone) // randomly find an element to clone
7625 start_pos = check_pos[RND(8)];
7626 check_order = (RND(2) ? -1 : +1);
7628 for (i = 0; i < 8; i++)
7630 int pos_raw = start_pos + i * check_order;
7631 int pos = (pos_raw + 8) % 8;
7632 int newx = x + check_xy[pos].dx;
7633 int newy = y + check_xy[pos].dy;
7635 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7637 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7638 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7640 Store[x][y] = Tile[newx][newy];
7649 if (can_clone) // randomly find a direction to move
7653 start_pos = check_pos[RND(8)];
7654 check_order = (RND(2) ? -1 : +1);
7656 for (i = 0; i < 8; i++)
7658 int pos_raw = start_pos + i * check_order;
7659 int pos = (pos_raw + 8) % 8;
7660 int newx = x + check_xy[pos].dx;
7661 int newy = y + check_xy[pos].dy;
7662 int new_move_dir = check_xy[pos].dir;
7664 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7666 MovDir[x][y] = new_move_dir;
7667 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7676 if (can_clone) // cloning and moving successful
7679 // cannot clone -- try to move towards player
7681 start_pos = check_pos[MovDir[x][y] & 0x0f];
7682 check_order = (RND(2) ? -1 : +1);
7684 for (i = 0; i < 3; i++)
7686 // first check start_pos, then previous/next or (next/previous) pos
7687 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7688 int pos = (pos_raw + 8) % 8;
7689 int newx = x + check_xy[pos].dx;
7690 int newy = y + check_xy[pos].dy;
7691 int new_move_dir = check_xy[pos].dir;
7693 if (IS_PLAYER(newx, newy))
7696 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7698 MovDir[x][y] = new_move_dir;
7699 MovDelay[x][y] = level.android_move_time * 8 + 1;
7706 else if (move_pattern == MV_TURNING_LEFT ||
7707 move_pattern == MV_TURNING_RIGHT ||
7708 move_pattern == MV_TURNING_LEFT_RIGHT ||
7709 move_pattern == MV_TURNING_RIGHT_LEFT ||
7710 move_pattern == MV_TURNING_RANDOM ||
7711 move_pattern == MV_ALL_DIRECTIONS)
7713 boolean can_turn_left =
7714 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7715 boolean can_turn_right =
7716 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y);
7718 if (element_info[element].move_stepsize == 0) // "not moving"
7721 if (move_pattern == MV_TURNING_LEFT)
7722 MovDir[x][y] = left_dir;
7723 else if (move_pattern == MV_TURNING_RIGHT)
7724 MovDir[x][y] = right_dir;
7725 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7726 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7727 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7728 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7729 else if (move_pattern == MV_TURNING_RANDOM)
7730 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7731 can_turn_right && !can_turn_left ? right_dir :
7732 RND(2) ? left_dir : right_dir);
7733 else if (can_turn_left && can_turn_right)
7734 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7735 else if (can_turn_left)
7736 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7737 else if (can_turn_right)
7738 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7740 MovDir[x][y] = back_dir;
7742 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7744 else if (move_pattern == MV_HORIZONTAL ||
7745 move_pattern == MV_VERTICAL)
7747 if (move_pattern & old_move_dir)
7748 MovDir[x][y] = back_dir;
7749 else if (move_pattern == MV_HORIZONTAL)
7750 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7751 else if (move_pattern == MV_VERTICAL)
7752 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7754 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7756 else if (move_pattern & MV_ANY_DIRECTION)
7758 MovDir[x][y] = move_pattern;
7759 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7761 else if (move_pattern & MV_WIND_DIRECTION)
7763 MovDir[x][y] = game.wind_direction;
7764 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7766 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7768 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7769 MovDir[x][y] = left_dir;
7770 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7771 MovDir[x][y] = right_dir;
7773 if (MovDir[x][y] != old_move_dir)
7774 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7776 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7778 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7779 MovDir[x][y] = right_dir;
7780 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7781 MovDir[x][y] = left_dir;
7783 if (MovDir[x][y] != old_move_dir)
7784 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7786 else if (move_pattern == MV_TOWARDS_PLAYER ||
7787 move_pattern == MV_AWAY_FROM_PLAYER)
7789 int attr_x = -1, attr_y = -1;
7791 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7793 if (game.all_players_gone)
7795 attr_x = game.exit_x;
7796 attr_y = game.exit_y;
7802 for (i = 0; i < MAX_PLAYERS; i++)
7804 struct PlayerInfo *player = &stored_player[i];
7805 int jx = player->jx, jy = player->jy;
7807 if (!player->active)
7811 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7819 MovDir[x][y] = MV_NONE;
7821 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7822 else if (attr_x > x)
7823 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7825 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7826 else if (attr_y > y)
7827 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7829 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7831 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7833 boolean first_horiz = RND(2);
7834 int new_move_dir = MovDir[x][y];
7836 if (element_info[element].move_stepsize == 0) // "not moving"
7838 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7839 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7845 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7846 Moving2Blocked(x, y, &newx, &newy);
7848 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7852 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7853 Moving2Blocked(x, y, &newx, &newy);
7855 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7858 MovDir[x][y] = old_move_dir;
7861 else if (move_pattern == MV_WHEN_PUSHED ||
7862 move_pattern == MV_WHEN_DROPPED)
7864 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7865 MovDir[x][y] = MV_NONE;
7869 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7871 struct XY *test_xy = xy_topdown;
7872 static int test_dir[4] =
7879 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7880 int move_preference = -1000000; // start with very low preference
7881 int new_move_dir = MV_NONE;
7882 int start_test = RND(4);
7885 for (i = 0; i < NUM_DIRECTIONS; i++)
7887 int j = (start_test + i) % 4;
7888 int move_dir = test_dir[j];
7889 int move_dir_preference;
7891 xx = x + test_xy[j].x;
7892 yy = y + test_xy[j].y;
7894 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7895 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7897 new_move_dir = move_dir;
7902 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7905 move_dir_preference = -1 * RunnerVisit[xx][yy];
7906 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7907 move_dir_preference = PlayerVisit[xx][yy];
7909 if (move_dir_preference > move_preference)
7911 // prefer field that has not been visited for the longest time
7912 move_preference = move_dir_preference;
7913 new_move_dir = move_dir;
7915 else if (move_dir_preference == move_preference &&
7916 move_dir == old_move_dir)
7918 // prefer last direction when all directions are preferred equally
7919 move_preference = move_dir_preference;
7920 new_move_dir = move_dir;
7924 MovDir[x][y] = new_move_dir;
7925 if (old_move_dir != new_move_dir)
7926 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7930 static void TurnRound(int x, int y)
7932 int direction = MovDir[x][y];
7936 GfxDir[x][y] = MovDir[x][y];
7938 if (direction != MovDir[x][y])
7942 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7944 ResetGfxFrame(x, y);
7947 static boolean JustBeingPushed(int x, int y)
7951 for (i = 0; i < MAX_PLAYERS; i++)
7953 struct PlayerInfo *player = &stored_player[i];
7955 if (player->active && player->is_pushing && player->MovPos)
7957 int next_jx = player->jx + (player->jx - player->last_jx);
7958 int next_jy = player->jy + (player->jy - player->last_jy);
7960 if (x == next_jx && y == next_jy)
7968 static void StartMoving(int x, int y)
7970 boolean started_moving = FALSE; // some elements can fall _and_ move
7971 int element = Tile[x][y];
7976 if (MovDelay[x][y] == 0)
7977 GfxAction[x][y] = ACTION_DEFAULT;
7979 if (CAN_FALL(element) && y < lev_fieldy - 1)
7981 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7982 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7983 if (JustBeingPushed(x, y))
7986 if (element == EL_QUICKSAND_FULL)
7988 if (IS_FREE(x, y + 1))
7990 InitMovingField(x, y, MV_DOWN);
7991 started_moving = TRUE;
7993 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7994 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7995 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7996 Store[x][y] = EL_ROCK;
7998 Store[x][y] = EL_ROCK;
8001 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
8003 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8005 if (!MovDelay[x][y])
8007 MovDelay[x][y] = TILEY + 1;
8009 ResetGfxAnimation(x, y);
8010 ResetGfxAnimation(x, y + 1);
8015 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
8016 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8023 Tile[x][y] = EL_QUICKSAND_EMPTY;
8024 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8025 Store[x][y + 1] = Store[x][y];
8028 PlayLevelSoundAction(x, y, ACTION_FILLING);
8030 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8032 if (!MovDelay[x][y])
8034 MovDelay[x][y] = TILEY + 1;
8036 ResetGfxAnimation(x, y);
8037 ResetGfxAnimation(x, y + 1);
8042 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
8043 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8050 Tile[x][y] = EL_QUICKSAND_EMPTY;
8051 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8052 Store[x][y + 1] = Store[x][y];
8055 PlayLevelSoundAction(x, y, ACTION_FILLING);
8058 else if (element == EL_QUICKSAND_FAST_FULL)
8060 if (IS_FREE(x, y + 1))
8062 InitMovingField(x, y, MV_DOWN);
8063 started_moving = TRUE;
8065 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
8066 #if USE_QUICKSAND_BD_ROCK_BUGFIX
8067 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
8068 Store[x][y] = EL_ROCK;
8070 Store[x][y] = EL_ROCK;
8073 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
8075 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8077 if (!MovDelay[x][y])
8079 MovDelay[x][y] = TILEY + 1;
8081 ResetGfxAnimation(x, y);
8082 ResetGfxAnimation(x, y + 1);
8087 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8088 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8095 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8096 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8097 Store[x][y + 1] = Store[x][y];
8100 PlayLevelSoundAction(x, y, ACTION_FILLING);
8102 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8104 if (!MovDelay[x][y])
8106 MovDelay[x][y] = TILEY + 1;
8108 ResetGfxAnimation(x, y);
8109 ResetGfxAnimation(x, y + 1);
8114 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8115 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8122 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8123 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8124 Store[x][y + 1] = Store[x][y];
8127 PlayLevelSoundAction(x, y, ACTION_FILLING);
8130 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8131 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8133 InitMovingField(x, y, MV_DOWN);
8134 started_moving = TRUE;
8136 Tile[x][y] = EL_QUICKSAND_FILLING;
8137 Store[x][y] = element;
8139 PlayLevelSoundAction(x, y, ACTION_FILLING);
8141 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8142 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8144 InitMovingField(x, y, MV_DOWN);
8145 started_moving = TRUE;
8147 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
8148 Store[x][y] = element;
8150 PlayLevelSoundAction(x, y, ACTION_FILLING);
8152 else if (element == EL_MAGIC_WALL_FULL)
8154 if (IS_FREE(x, y + 1))
8156 InitMovingField(x, y, MV_DOWN);
8157 started_moving = TRUE;
8159 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
8160 Store[x][y] = EL_CHANGED(Store[x][y]);
8162 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
8164 if (!MovDelay[x][y])
8165 MovDelay[x][y] = TILEY / 4 + 1;
8174 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
8175 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
8176 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8180 else if (element == EL_BD_MAGIC_WALL_FULL)
8182 if (IS_FREE(x, y + 1))
8184 InitMovingField(x, y, MV_DOWN);
8185 started_moving = TRUE;
8187 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8188 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8190 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8192 if (!MovDelay[x][y])
8193 MovDelay[x][y] = TILEY / 4 + 1;
8202 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8203 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8204 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8208 else if (element == EL_DC_MAGIC_WALL_FULL)
8210 if (IS_FREE(x, y + 1))
8212 InitMovingField(x, y, MV_DOWN);
8213 started_moving = TRUE;
8215 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8216 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8218 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8220 if (!MovDelay[x][y])
8221 MovDelay[x][y] = TILEY / 4 + 1;
8230 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8231 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8232 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8236 else if ((CAN_PASS_MAGIC_WALL(element) &&
8237 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8238 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8239 (CAN_PASS_DC_MAGIC_WALL(element) &&
8240 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8243 InitMovingField(x, y, MV_DOWN);
8244 started_moving = TRUE;
8247 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8248 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8249 EL_DC_MAGIC_WALL_FILLING);
8250 Store[x][y] = element;
8252 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8254 SplashAcid(x, y + 1);
8256 InitMovingField(x, y, MV_DOWN);
8257 started_moving = TRUE;
8259 Store[x][y] = EL_ACID;
8262 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8263 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8264 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8265 CAN_FALL(element) && WasJustFalling[x][y] &&
8266 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8268 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8269 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8270 (Tile[x][y + 1] == EL_BLOCKED)))
8272 /* this is needed for a special case not covered by calling "Impact()"
8273 from "ContinueMoving()": if an element moves to a tile directly below
8274 another element which was just falling on that tile (which was empty
8275 in the previous frame), the falling element above would just stop
8276 instead of smashing the element below (in previous version, the above
8277 element was just checked for "moving" instead of "falling", resulting
8278 in incorrect smashes caused by horizontal movement of the above
8279 element; also, the case of the player being the element to smash was
8280 simply not covered here... :-/ ) */
8282 CheckCollision[x][y] = 0;
8283 CheckImpact[x][y] = 0;
8287 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8289 if (MovDir[x][y] == MV_NONE)
8291 InitMovingField(x, y, MV_DOWN);
8292 started_moving = TRUE;
8295 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8297 if (WasJustFalling[x][y]) // prevent animation from being restarted
8298 MovDir[x][y] = MV_DOWN;
8300 InitMovingField(x, y, MV_DOWN);
8301 started_moving = TRUE;
8303 else if (element == EL_AMOEBA_DROP)
8305 Tile[x][y] = EL_AMOEBA_GROWING;
8306 Store[x][y] = EL_AMOEBA_WET;
8308 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8309 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8310 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8311 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8313 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8314 (IS_FREE(x - 1, y + 1) ||
8315 Tile[x - 1][y + 1] == EL_ACID));
8316 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8317 (IS_FREE(x + 1, y + 1) ||
8318 Tile[x + 1][y + 1] == EL_ACID));
8319 boolean can_fall_any = (can_fall_left || can_fall_right);
8320 boolean can_fall_both = (can_fall_left && can_fall_right);
8321 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8323 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8325 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8326 can_fall_right = FALSE;
8327 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8328 can_fall_left = FALSE;
8329 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8330 can_fall_right = FALSE;
8331 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8332 can_fall_left = FALSE;
8334 can_fall_any = (can_fall_left || can_fall_right);
8335 can_fall_both = FALSE;
8340 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8341 can_fall_right = FALSE; // slip down on left side
8343 can_fall_left = !(can_fall_right = RND(2));
8345 can_fall_both = FALSE;
8350 // if not determined otherwise, prefer left side for slipping down
8351 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8352 started_moving = TRUE;
8355 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8357 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8358 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8359 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8360 int belt_dir = game.belt_dir[belt_nr];
8362 if ((belt_dir == MV_LEFT && left_is_free) ||
8363 (belt_dir == MV_RIGHT && right_is_free))
8365 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8367 InitMovingField(x, y, belt_dir);
8368 started_moving = TRUE;
8370 Pushed[x][y] = TRUE;
8371 Pushed[nextx][y] = TRUE;
8373 GfxAction[x][y] = ACTION_DEFAULT;
8377 MovDir[x][y] = 0; // if element was moving, stop it
8382 // not "else if" because of elements that can fall and move (EL_SPRING)
8383 if (CAN_MOVE(element) && !started_moving)
8385 int move_pattern = element_info[element].move_pattern;
8388 Moving2Blocked(x, y, &newx, &newy);
8390 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8393 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8394 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8396 WasJustMoving[x][y] = 0;
8397 CheckCollision[x][y] = 0;
8399 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8401 if (Tile[x][y] != element) // element has changed
8405 if (!MovDelay[x][y]) // start new movement phase
8407 // all objects that can change their move direction after each step
8408 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8410 if (element != EL_YAMYAM &&
8411 element != EL_DARK_YAMYAM &&
8412 element != EL_PACMAN &&
8413 !(move_pattern & MV_ANY_DIRECTION) &&
8414 move_pattern != MV_TURNING_LEFT &&
8415 move_pattern != MV_TURNING_RIGHT &&
8416 move_pattern != MV_TURNING_LEFT_RIGHT &&
8417 move_pattern != MV_TURNING_RIGHT_LEFT &&
8418 move_pattern != MV_TURNING_RANDOM)
8422 if (MovDelay[x][y] && (element == EL_BUG ||
8423 element == EL_SPACESHIP ||
8424 element == EL_SP_SNIKSNAK ||
8425 element == EL_SP_ELECTRON ||
8426 element == EL_MOLE))
8427 TEST_DrawLevelField(x, y);
8431 if (MovDelay[x][y]) // wait some time before next movement
8435 if (element == EL_ROBOT ||
8436 element == EL_YAMYAM ||
8437 element == EL_DARK_YAMYAM)
8439 DrawLevelElementAnimationIfNeeded(x, y, element);
8440 PlayLevelSoundAction(x, y, ACTION_WAITING);
8442 else if (element == EL_SP_ELECTRON)
8443 DrawLevelElementAnimationIfNeeded(x, y, element);
8444 else if (element == EL_DRAGON)
8447 int dir = MovDir[x][y];
8448 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8449 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8450 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8451 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8452 dir == MV_UP ? IMG_FLAMES_1_UP :
8453 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8454 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8456 GfxAction[x][y] = ACTION_ATTACKING;
8458 if (IS_PLAYER(x, y))
8459 DrawPlayerField(x, y);
8461 TEST_DrawLevelField(x, y);
8463 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8465 for (i = 1; i <= 3; i++)
8467 int xx = x + i * dx;
8468 int yy = y + i * dy;
8469 int sx = SCREENX(xx);
8470 int sy = SCREENY(yy);
8471 int flame_graphic = graphic + (i - 1);
8473 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8478 int flamed = MovingOrBlocked2Element(xx, yy);
8480 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8483 RemoveMovingField(xx, yy);
8485 ChangeDelay[xx][yy] = 0;
8487 Tile[xx][yy] = EL_FLAMES;
8489 if (IN_SCR_FIELD(sx, sy))
8491 TEST_DrawLevelFieldCrumbled(xx, yy);
8492 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8497 if (Tile[xx][yy] == EL_FLAMES)
8498 Tile[xx][yy] = EL_EMPTY;
8499 TEST_DrawLevelField(xx, yy);
8504 if (MovDelay[x][y]) // element still has to wait some time
8506 PlayLevelSoundAction(x, y, ACTION_WAITING);
8512 // now make next step
8514 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8516 if (DONT_COLLIDE_WITH(element) &&
8517 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8518 !PLAYER_ENEMY_PROTECTED(newx, newy))
8520 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8525 else if (CAN_MOVE_INTO_ACID(element) &&
8526 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8527 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8528 (MovDir[x][y] == MV_DOWN ||
8529 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8531 SplashAcid(newx, newy);
8532 Store[x][y] = EL_ACID;
8534 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8536 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8537 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8538 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8539 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8542 TEST_DrawLevelField(x, y);
8544 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8545 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8546 DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0);
8548 game.friends_still_needed--;
8549 if (!game.friends_still_needed &&
8551 game.all_players_gone)
8556 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8558 if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING)
8559 TEST_DrawLevelField(newx, newy);
8561 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8563 else if (!IS_FREE(newx, newy))
8565 GfxAction[x][y] = ACTION_WAITING;
8567 if (IS_PLAYER(x, y))
8568 DrawPlayerField(x, y);
8570 TEST_DrawLevelField(x, y);
8575 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8577 if (IS_FOOD_PIG(Tile[newx][newy]))
8579 if (IS_MOVING(newx, newy))
8580 RemoveMovingField(newx, newy);
8583 Tile[newx][newy] = EL_EMPTY;
8584 TEST_DrawLevelField(newx, newy);
8587 PlayLevelSound(x, y, SND_PIG_DIGGING);
8589 else if (!IS_FREE(newx, newy))
8591 if (IS_PLAYER(x, y))
8592 DrawPlayerField(x, y);
8594 TEST_DrawLevelField(x, y);
8599 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8601 if (Store[x][y] != EL_EMPTY)
8603 boolean can_clone = FALSE;
8606 // check if element to clone is still there
8607 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8609 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8617 // cannot clone or target field not free anymore -- do not clone
8618 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8619 Store[x][y] = EL_EMPTY;
8622 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8624 if (IS_MV_DIAGONAL(MovDir[x][y]))
8626 int diagonal_move_dir = MovDir[x][y];
8627 int stored = Store[x][y];
8628 int change_delay = 8;
8631 // android is moving diagonally
8633 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8635 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8636 GfxElement[x][y] = EL_EMC_ANDROID;
8637 GfxAction[x][y] = ACTION_SHRINKING;
8638 GfxDir[x][y] = diagonal_move_dir;
8639 ChangeDelay[x][y] = change_delay;
8641 if (Store[x][y] == EL_EMPTY)
8642 Store[x][y] = GfxElementEmpty[x][y];
8644 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8647 DrawLevelGraphicAnimation(x, y, graphic);
8648 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8650 if (Tile[newx][newy] == EL_ACID)
8652 SplashAcid(newx, newy);
8657 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8659 Store[newx][newy] = EL_EMC_ANDROID;
8660 GfxElement[newx][newy] = EL_EMC_ANDROID;
8661 GfxAction[newx][newy] = ACTION_GROWING;
8662 GfxDir[newx][newy] = diagonal_move_dir;
8663 ChangeDelay[newx][newy] = change_delay;
8665 graphic = el_act_dir2img(GfxElement[newx][newy],
8666 GfxAction[newx][newy], GfxDir[newx][newy]);
8668 DrawLevelGraphicAnimation(newx, newy, graphic);
8669 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8675 Tile[newx][newy] = EL_EMPTY;
8676 TEST_DrawLevelField(newx, newy);
8678 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8681 else if (!IS_FREE(newx, newy))
8686 else if (IS_CUSTOM_ELEMENT(element) &&
8687 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8689 if (!DigFieldByCE(newx, newy, element))
8692 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8694 RunnerVisit[x][y] = FrameCounter;
8695 PlayerVisit[x][y] /= 8; // expire player visit path
8698 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8700 if (!IS_FREE(newx, newy))
8702 if (IS_PLAYER(x, y))
8703 DrawPlayerField(x, y);
8705 TEST_DrawLevelField(x, y);
8711 boolean wanna_flame = !RND(10);
8712 int dx = newx - x, dy = newy - y;
8713 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8714 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8715 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8716 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8717 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8718 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8721 IS_CLASSIC_ENEMY(element1) ||
8722 IS_CLASSIC_ENEMY(element2)) &&
8723 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8724 element1 != EL_FLAMES && element2 != EL_FLAMES)
8726 ResetGfxAnimation(x, y);
8727 GfxAction[x][y] = ACTION_ATTACKING;
8729 if (IS_PLAYER(x, y))
8730 DrawPlayerField(x, y);
8732 TEST_DrawLevelField(x, y);
8734 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8736 MovDelay[x][y] = 50;
8738 Tile[newx][newy] = EL_FLAMES;
8739 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8740 Tile[newx1][newy1] = EL_FLAMES;
8741 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8742 Tile[newx2][newy2] = EL_FLAMES;
8748 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8749 Tile[newx][newy] == EL_DIAMOND)
8751 if (IS_MOVING(newx, newy))
8752 RemoveMovingField(newx, newy);
8755 Tile[newx][newy] = EL_EMPTY;
8756 TEST_DrawLevelField(newx, newy);
8759 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8761 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8762 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8764 if (AmoebaNr[newx][newy])
8766 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8767 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8768 Tile[newx][newy] == EL_BD_AMOEBA)
8769 AmoebaCnt[AmoebaNr[newx][newy]]--;
8772 if (IS_MOVING(newx, newy))
8774 RemoveMovingField(newx, newy);
8778 Tile[newx][newy] = EL_EMPTY;
8779 TEST_DrawLevelField(newx, newy);
8782 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8784 else if ((element == EL_PACMAN || element == EL_MOLE)
8785 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8787 if (AmoebaNr[newx][newy])
8789 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8790 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8791 Tile[newx][newy] == EL_BD_AMOEBA)
8792 AmoebaCnt[AmoebaNr[newx][newy]]--;
8795 if (element == EL_MOLE)
8797 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8798 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8800 ResetGfxAnimation(x, y);
8801 GfxAction[x][y] = ACTION_DIGGING;
8802 TEST_DrawLevelField(x, y);
8804 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8806 return; // wait for shrinking amoeba
8808 else // element == EL_PACMAN
8810 Tile[newx][newy] = EL_EMPTY;
8811 TEST_DrawLevelField(newx, newy);
8812 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8815 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8816 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8817 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8819 // wait for shrinking amoeba to completely disappear
8822 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8824 // object was running against a wall
8828 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8829 DrawLevelElementAnimation(x, y, element);
8831 if (DONT_TOUCH(element))
8832 TestIfBadThingTouchesPlayer(x, y);
8837 InitMovingField(x, y, MovDir[x][y]);
8839 PlayLevelSoundAction(x, y, ACTION_MOVING);
8843 ContinueMoving(x, y);
8846 void ContinueMoving(int x, int y)
8848 int element = Tile[x][y];
8849 struct ElementInfo *ei = &element_info[element];
8850 int direction = MovDir[x][y];
8851 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8852 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8853 int newx = x + dx, newy = y + dy;
8854 int stored = Store[x][y];
8855 int stored_new = Store[newx][newy];
8856 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8857 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8858 boolean last_line = (newy == lev_fieldy - 1);
8859 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8861 if (pushed_by_player) // special case: moving object pushed by player
8863 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos));
8865 else if (use_step_delay) // special case: moving object has step delay
8867 if (!MovDelay[x][y])
8868 MovPos[x][y] += getElementMoveStepsize(x, y);
8873 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8877 TEST_DrawLevelField(x, y);
8879 return; // element is still waiting
8882 else // normal case: generically moving object
8884 MovPos[x][y] += getElementMoveStepsize(x, y);
8887 if (ABS(MovPos[x][y]) < TILEX)
8889 TEST_DrawLevelField(x, y);
8891 return; // element is still moving
8894 // element reached destination field
8896 Tile[x][y] = EL_EMPTY;
8897 Tile[newx][newy] = element;
8898 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8900 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8902 element = Tile[newx][newy] = EL_ACID;
8904 else if (element == EL_MOLE)
8906 Tile[x][y] = EL_SAND;
8908 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8910 else if (element == EL_QUICKSAND_FILLING)
8912 element = Tile[newx][newy] = get_next_element(element);
8913 Store[newx][newy] = Store[x][y];
8915 else if (element == EL_QUICKSAND_EMPTYING)
8917 Tile[x][y] = get_next_element(element);
8918 element = Tile[newx][newy] = Store[x][y];
8920 else if (element == EL_QUICKSAND_FAST_FILLING)
8922 element = Tile[newx][newy] = get_next_element(element);
8923 Store[newx][newy] = Store[x][y];
8925 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8927 Tile[x][y] = get_next_element(element);
8928 element = Tile[newx][newy] = Store[x][y];
8930 else if (element == EL_MAGIC_WALL_FILLING)
8932 element = Tile[newx][newy] = get_next_element(element);
8933 if (!game.magic_wall_active)
8934 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8935 Store[newx][newy] = Store[x][y];
8937 else if (element == EL_MAGIC_WALL_EMPTYING)
8939 Tile[x][y] = get_next_element(element);
8940 if (!game.magic_wall_active)
8941 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8942 element = Tile[newx][newy] = Store[x][y];
8944 InitField(newx, newy, FALSE);
8946 else if (element == EL_BD_MAGIC_WALL_FILLING)
8948 element = Tile[newx][newy] = get_next_element(element);
8949 if (!game.magic_wall_active)
8950 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8951 Store[newx][newy] = Store[x][y];
8953 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8955 Tile[x][y] = get_next_element(element);
8956 if (!game.magic_wall_active)
8957 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8958 element = Tile[newx][newy] = Store[x][y];
8960 InitField(newx, newy, FALSE);
8962 else if (element == EL_DC_MAGIC_WALL_FILLING)
8964 element = Tile[newx][newy] = get_next_element(element);
8965 if (!game.magic_wall_active)
8966 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8967 Store[newx][newy] = Store[x][y];
8969 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8971 Tile[x][y] = get_next_element(element);
8972 if (!game.magic_wall_active)
8973 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8974 element = Tile[newx][newy] = Store[x][y];
8976 InitField(newx, newy, FALSE);
8978 else if (element == EL_AMOEBA_DROPPING)
8980 Tile[x][y] = get_next_element(element);
8981 element = Tile[newx][newy] = Store[x][y];
8983 else if (element == EL_SOKOBAN_OBJECT)
8986 Tile[x][y] = Back[x][y];
8988 if (Back[newx][newy])
8989 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8991 Back[x][y] = Back[newx][newy] = 0;
8994 Store[x][y] = EL_EMPTY;
8999 MovDelay[newx][newy] = 0;
9001 if (CAN_CHANGE_OR_HAS_ACTION(element))
9003 // copy element change control values to new field
9004 ChangeDelay[newx][newy] = ChangeDelay[x][y];
9005 ChangePage[newx][newy] = ChangePage[x][y];
9006 ChangeCount[newx][newy] = ChangeCount[x][y];
9007 ChangeEvent[newx][newy] = ChangeEvent[x][y];
9010 CustomValue[newx][newy] = CustomValue[x][y];
9012 ChangeDelay[x][y] = 0;
9013 ChangePage[x][y] = -1;
9014 ChangeCount[x][y] = 0;
9015 ChangeEvent[x][y] = -1;
9017 CustomValue[x][y] = 0;
9019 // copy animation control values to new field
9020 GfxFrame[newx][newy] = GfxFrame[x][y];
9021 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
9022 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
9023 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
9025 Pushed[x][y] = Pushed[newx][newy] = FALSE;
9027 // some elements can leave other elements behind after moving
9028 if (ei->move_leave_element != EL_EMPTY &&
9029 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
9030 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
9032 int move_leave_element = ei->move_leave_element;
9034 // this makes it possible to leave the removed element again
9035 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
9036 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
9038 Tile[x][y] = move_leave_element;
9040 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
9041 MovDir[x][y] = direction;
9043 InitField(x, y, FALSE);
9045 if (GFX_CRUMBLED(Tile[x][y]))
9046 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
9048 if (IS_PLAYER_ELEMENT(move_leave_element))
9049 RelocatePlayer(x, y, move_leave_element);
9052 // do this after checking for left-behind element
9053 ResetGfxAnimation(x, y); // reset animation values for old field
9055 if (!CAN_MOVE(element) ||
9056 (CAN_FALL(element) && direction == MV_DOWN &&
9057 (element == EL_SPRING ||
9058 element_info[element].move_pattern == MV_WHEN_PUSHED ||
9059 element_info[element].move_pattern == MV_WHEN_DROPPED)))
9060 GfxDir[x][y] = MovDir[newx][newy] = 0;
9062 TEST_DrawLevelField(x, y);
9063 TEST_DrawLevelField(newx, newy);
9065 Stop[newx][newy] = TRUE; // ignore this element until the next frame
9067 // prevent pushed element from moving on in pushed direction
9068 if (pushed_by_player && CAN_MOVE(element) &&
9069 element_info[element].move_pattern & MV_ANY_DIRECTION &&
9070 !(element_info[element].move_pattern & direction))
9071 TurnRound(newx, newy);
9073 // prevent elements on conveyor belt from moving on in last direction
9074 if (pushed_by_conveyor && CAN_FALL(element) &&
9075 direction & MV_HORIZONTAL)
9076 MovDir[newx][newy] = 0;
9078 if (!pushed_by_player)
9080 int nextx = newx + dx, nexty = newy + dy;
9081 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
9083 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
9085 if (CAN_FALL(element) && direction == MV_DOWN)
9086 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
9088 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
9089 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
9091 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
9092 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
9095 if (DONT_TOUCH(element)) // object may be nasty to player or others
9097 TestIfBadThingTouchesPlayer(newx, newy);
9098 TestIfBadThingTouchesFriend(newx, newy);
9100 if (!IS_CUSTOM_ELEMENT(element))
9101 TestIfBadThingTouchesOtherBadThing(newx, newy);
9103 else if (element == EL_PENGUIN)
9104 TestIfFriendTouchesBadThing(newx, newy);
9106 if (DONT_GET_HIT_BY(element))
9108 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
9111 // give the player one last chance (one more frame) to move away
9112 if (CAN_FALL(element) && direction == MV_DOWN &&
9113 (last_line || (!IS_FREE(x, newy + 1) &&
9114 (!IS_PLAYER(x, newy + 1) ||
9115 game.engine_version < VERSION_IDENT(3,1,1,0)))))
9118 if (pushed_by_player && !game.use_change_when_pushing_bug)
9120 int push_side = MV_DIR_OPPOSITE(direction);
9121 struct PlayerInfo *player = PLAYERINFO(x, y);
9123 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
9124 player->index_bit, push_side);
9125 CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X,
9126 player->index_bit, push_side);
9129 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
9130 MovDelay[newx][newy] = 1;
9132 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
9134 TestIfElementTouchesCustomElement(x, y); // empty or new element
9135 TestIfElementHitsCustomElement(newx, newy, direction);
9136 TestIfPlayerTouchesCustomElement(newx, newy);
9137 TestIfElementTouchesCustomElement(newx, newy);
9139 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
9140 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
9141 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
9142 MV_DIR_OPPOSITE(direction));
9145 int AmoebaNeighbourNr(int ax, int ay)
9148 int element = Tile[ax][ay];
9150 struct XY *xy = xy_topdown;
9152 for (i = 0; i < NUM_DIRECTIONS; i++)
9154 int x = ax + xy[i].x;
9155 int y = ay + xy[i].y;
9157 if (!IN_LEV_FIELD(x, y))
9160 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
9161 group_nr = AmoebaNr[x][y];
9167 static void AmoebaMerge(int ax, int ay)
9169 int i, x, y, xx, yy;
9170 int new_group_nr = AmoebaNr[ax][ay];
9171 struct XY *xy = xy_topdown;
9173 if (new_group_nr == 0)
9176 for (i = 0; i < NUM_DIRECTIONS; i++)
9181 if (!IN_LEV_FIELD(x, y))
9184 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9185 Tile[x][y] == EL_BD_AMOEBA ||
9186 Tile[x][y] == EL_AMOEBA_DEAD) &&
9187 AmoebaNr[x][y] != new_group_nr)
9189 int old_group_nr = AmoebaNr[x][y];
9191 if (old_group_nr == 0)
9194 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9195 AmoebaCnt[old_group_nr] = 0;
9196 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9197 AmoebaCnt2[old_group_nr] = 0;
9199 SCAN_PLAYFIELD(xx, yy)
9201 if (AmoebaNr[xx][yy] == old_group_nr)
9202 AmoebaNr[xx][yy] = new_group_nr;
9208 void AmoebaToDiamond(int ax, int ay)
9212 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9214 int group_nr = AmoebaNr[ax][ay];
9219 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9220 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9226 SCAN_PLAYFIELD(x, y)
9228 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9231 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9235 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9236 SND_AMOEBA_TURNING_TO_GEM :
9237 SND_AMOEBA_TURNING_TO_ROCK));
9242 struct XY *xy = xy_topdown;
9244 for (i = 0; i < NUM_DIRECTIONS; i++)
9249 if (!IN_LEV_FIELD(x, y))
9252 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9254 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9255 SND_AMOEBA_TURNING_TO_GEM :
9256 SND_AMOEBA_TURNING_TO_ROCK));
9263 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9266 int group_nr = AmoebaNr[ax][ay];
9267 boolean done = FALSE;
9272 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9273 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9279 SCAN_PLAYFIELD(x, y)
9281 if (AmoebaNr[x][y] == group_nr &&
9282 (Tile[x][y] == EL_AMOEBA_DEAD ||
9283 Tile[x][y] == EL_BD_AMOEBA ||
9284 Tile[x][y] == EL_AMOEBA_GROWING))
9287 Tile[x][y] = new_element;
9288 InitField(x, y, FALSE);
9289 TEST_DrawLevelField(x, y);
9295 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9296 SND_BD_AMOEBA_TURNING_TO_ROCK :
9297 SND_BD_AMOEBA_TURNING_TO_GEM));
9300 static void AmoebaGrowing(int x, int y)
9302 static DelayCounter sound_delay = { 0 };
9304 if (!MovDelay[x][y]) // start new growing cycle
9308 if (DelayReached(&sound_delay))
9310 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9311 sound_delay.value = 30;
9315 if (MovDelay[x][y]) // wait some time before growing bigger
9318 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9320 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9321 6 - MovDelay[x][y]);
9323 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9326 if (!MovDelay[x][y])
9328 Tile[x][y] = Store[x][y];
9330 TEST_DrawLevelField(x, y);
9335 static void AmoebaShrinking(int x, int y)
9337 static DelayCounter sound_delay = { 0 };
9339 if (!MovDelay[x][y]) // start new shrinking cycle
9343 if (DelayReached(&sound_delay))
9344 sound_delay.value = 30;
9347 if (MovDelay[x][y]) // wait some time before shrinking
9350 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9352 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9353 6 - MovDelay[x][y]);
9355 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9358 if (!MovDelay[x][y])
9360 Tile[x][y] = EL_EMPTY;
9361 TEST_DrawLevelField(x, y);
9363 // don't let mole enter this field in this cycle;
9364 // (give priority to objects falling to this field from above)
9370 static void AmoebaReproduce(int ax, int ay)
9373 int element = Tile[ax][ay];
9374 int graphic = el2img(element);
9375 int newax = ax, neway = ay;
9376 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9377 struct XY *xy = xy_topdown;
9379 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9381 Tile[ax][ay] = EL_AMOEBA_DEAD;
9382 TEST_DrawLevelField(ax, ay);
9386 if (IS_ANIMATED(graphic))
9387 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9389 if (!MovDelay[ax][ay]) // start making new amoeba field
9390 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9392 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9395 if (MovDelay[ax][ay])
9399 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9402 int x = ax + xy[start].x;
9403 int y = ay + xy[start].y;
9405 if (!IN_LEV_FIELD(x, y))
9408 if (IS_FREE(x, y) ||
9409 CAN_GROW_INTO(Tile[x][y]) ||
9410 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9411 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9417 if (newax == ax && neway == ay)
9420 else // normal or "filled" (BD style) amoeba
9423 boolean waiting_for_player = FALSE;
9425 for (i = 0; i < NUM_DIRECTIONS; i++)
9427 int j = (start + i) % 4;
9428 int x = ax + xy[j].x;
9429 int y = ay + xy[j].y;
9431 if (!IN_LEV_FIELD(x, y))
9434 if (IS_FREE(x, y) ||
9435 CAN_GROW_INTO(Tile[x][y]) ||
9436 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9437 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9443 else if (IS_PLAYER(x, y))
9444 waiting_for_player = TRUE;
9447 if (newax == ax && neway == ay) // amoeba cannot grow
9449 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9451 Tile[ax][ay] = EL_AMOEBA_DEAD;
9452 TEST_DrawLevelField(ax, ay);
9453 AmoebaCnt[AmoebaNr[ax][ay]]--;
9455 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9457 if (element == EL_AMOEBA_FULL)
9458 AmoebaToDiamond(ax, ay);
9459 else if (element == EL_BD_AMOEBA)
9460 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9465 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9467 // amoeba gets larger by growing in some direction
9469 int new_group_nr = AmoebaNr[ax][ay];
9472 if (new_group_nr == 0)
9474 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9476 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9482 AmoebaNr[newax][neway] = new_group_nr;
9483 AmoebaCnt[new_group_nr]++;
9484 AmoebaCnt2[new_group_nr]++;
9486 // if amoeba touches other amoeba(s) after growing, unify them
9487 AmoebaMerge(newax, neway);
9489 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9491 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9497 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9498 (neway == lev_fieldy - 1 && newax != ax))
9500 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9501 Store[newax][neway] = element;
9503 else if (neway == ay || element == EL_EMC_DRIPPER)
9505 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9507 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9511 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9512 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9513 Store[ax][ay] = EL_AMOEBA_DROP;
9514 ContinueMoving(ax, ay);
9518 TEST_DrawLevelField(newax, neway);
9521 static void Life(int ax, int ay)
9525 int element = Tile[ax][ay];
9526 int graphic = el2img(element);
9527 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9529 boolean changed = FALSE;
9531 if (IS_ANIMATED(graphic))
9532 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9537 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9538 MovDelay[ax][ay] = life_time;
9540 if (MovDelay[ax][ay]) // wait some time before next cycle
9543 if (MovDelay[ax][ay])
9547 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9549 int xx = ax + x1, yy = ay + y1;
9550 int old_element = Tile[xx][yy];
9551 int num_neighbours = 0;
9553 if (!IN_LEV_FIELD(xx, yy))
9556 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9558 int x = xx + x2, y = yy + y2;
9560 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9563 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9564 boolean is_neighbour = FALSE;
9566 if (level.use_life_bugs)
9568 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9569 (IS_FREE(x, y) && Stop[x][y]));
9572 (Last[x][y] == element || is_player_cell);
9578 boolean is_free = FALSE;
9580 if (level.use_life_bugs)
9581 is_free = (IS_FREE(xx, yy));
9583 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9585 if (xx == ax && yy == ay) // field in the middle
9587 if (num_neighbours < life_parameter[0] ||
9588 num_neighbours > life_parameter[1])
9590 Tile[xx][yy] = EL_EMPTY;
9591 if (Tile[xx][yy] != old_element)
9592 TEST_DrawLevelField(xx, yy);
9593 Stop[xx][yy] = TRUE;
9597 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9598 { // free border field
9599 if (num_neighbours >= life_parameter[2] &&
9600 num_neighbours <= life_parameter[3])
9602 Tile[xx][yy] = element;
9603 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9604 if (Tile[xx][yy] != old_element)
9605 TEST_DrawLevelField(xx, yy);
9606 Stop[xx][yy] = TRUE;
9613 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9614 SND_GAME_OF_LIFE_GROWING);
9617 static void InitRobotWheel(int x, int y)
9619 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9622 static void RunRobotWheel(int x, int y)
9624 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9627 static void StopRobotWheel(int x, int y)
9629 if (game.robot_wheel_x == x &&
9630 game.robot_wheel_y == y)
9632 game.robot_wheel_x = -1;
9633 game.robot_wheel_y = -1;
9634 game.robot_wheel_active = FALSE;
9638 static void InitTimegateWheel(int x, int y)
9640 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9643 static void RunTimegateWheel(int x, int y)
9645 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9648 static void InitMagicBallDelay(int x, int y)
9650 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9653 static void ActivateMagicBall(int bx, int by)
9657 if (level.ball_random)
9659 int pos_border = RND(8); // select one of the eight border elements
9660 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9661 int xx = pos_content % 3;
9662 int yy = pos_content / 3;
9667 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9668 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9672 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9674 int xx = x - bx + 1;
9675 int yy = y - by + 1;
9677 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9678 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9682 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9685 static void CheckExit(int x, int y)
9687 if (game.gems_still_needed > 0 ||
9688 game.sokoban_fields_still_needed > 0 ||
9689 game.sokoban_objects_still_needed > 0 ||
9690 game.lights_still_needed > 0)
9692 int element = Tile[x][y];
9693 int graphic = el2img(element);
9695 if (IS_ANIMATED(graphic))
9696 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9701 // do not re-open exit door closed after last player
9702 if (game.all_players_gone)
9705 Tile[x][y] = EL_EXIT_OPENING;
9707 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9710 static void CheckExitEM(int x, int y)
9712 if (game.gems_still_needed > 0 ||
9713 game.sokoban_fields_still_needed > 0 ||
9714 game.sokoban_objects_still_needed > 0 ||
9715 game.lights_still_needed > 0)
9717 int element = Tile[x][y];
9718 int graphic = el2img(element);
9720 if (IS_ANIMATED(graphic))
9721 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9726 // do not re-open exit door closed after last player
9727 if (game.all_players_gone)
9730 Tile[x][y] = EL_EM_EXIT_OPENING;
9732 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9735 static void CheckExitSteel(int x, int y)
9737 if (game.gems_still_needed > 0 ||
9738 game.sokoban_fields_still_needed > 0 ||
9739 game.sokoban_objects_still_needed > 0 ||
9740 game.lights_still_needed > 0)
9742 int element = Tile[x][y];
9743 int graphic = el2img(element);
9745 if (IS_ANIMATED(graphic))
9746 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9751 // do not re-open exit door closed after last player
9752 if (game.all_players_gone)
9755 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9757 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9760 static void CheckExitSteelEM(int x, int y)
9762 if (game.gems_still_needed > 0 ||
9763 game.sokoban_fields_still_needed > 0 ||
9764 game.sokoban_objects_still_needed > 0 ||
9765 game.lights_still_needed > 0)
9767 int element = Tile[x][y];
9768 int graphic = el2img(element);
9770 if (IS_ANIMATED(graphic))
9771 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9776 // do not re-open exit door closed after last player
9777 if (game.all_players_gone)
9780 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9782 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9785 static void CheckExitSP(int x, int y)
9787 if (game.gems_still_needed > 0)
9789 int element = Tile[x][y];
9790 int graphic = el2img(element);
9792 if (IS_ANIMATED(graphic))
9793 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9798 // do not re-open exit door closed after last player
9799 if (game.all_players_gone)
9802 Tile[x][y] = EL_SP_EXIT_OPENING;
9804 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9807 static void CloseAllOpenTimegates(void)
9811 SCAN_PLAYFIELD(x, y)
9813 int element = Tile[x][y];
9815 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9817 Tile[x][y] = EL_TIMEGATE_CLOSING;
9819 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9824 static void DrawTwinkleOnField(int x, int y)
9826 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9829 if (Tile[x][y] == EL_BD_DIAMOND)
9832 if (MovDelay[x][y] == 0) // next animation frame
9833 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9835 if (MovDelay[x][y] != 0) // wait some time before next frame
9839 DrawLevelElementAnimation(x, y, Tile[x][y]);
9841 if (MovDelay[x][y] != 0)
9843 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9844 10 - MovDelay[x][y]);
9846 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9851 static void WallGrowing(int x, int y)
9855 if (!MovDelay[x][y]) // next animation frame
9856 MovDelay[x][y] = 3 * delay;
9858 if (MovDelay[x][y]) // wait some time before next frame
9862 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9864 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9865 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9867 DrawLevelGraphic(x, y, graphic, frame);
9870 if (!MovDelay[x][y])
9872 if (MovDir[x][y] == MV_LEFT)
9874 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9875 TEST_DrawLevelField(x - 1, y);
9877 else if (MovDir[x][y] == MV_RIGHT)
9879 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9880 TEST_DrawLevelField(x + 1, y);
9882 else if (MovDir[x][y] == MV_UP)
9884 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9885 TEST_DrawLevelField(x, y - 1);
9889 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9890 TEST_DrawLevelField(x, y + 1);
9893 Tile[x][y] = Store[x][y];
9895 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9896 TEST_DrawLevelField(x, y);
9901 static void CheckWallGrowing(int ax, int ay)
9903 int element = Tile[ax][ay];
9904 int graphic = el2img(element);
9905 boolean free_top = FALSE;
9906 boolean free_bottom = FALSE;
9907 boolean free_left = FALSE;
9908 boolean free_right = FALSE;
9909 boolean stop_top = FALSE;
9910 boolean stop_bottom = FALSE;
9911 boolean stop_left = FALSE;
9912 boolean stop_right = FALSE;
9913 boolean new_wall = FALSE;
9915 boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9916 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9917 element == EL_EXPANDABLE_STEELWALL_ANY);
9919 boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9920 element == EL_EXPANDABLE_WALL_ANY ||
9921 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9922 element == EL_EXPANDABLE_STEELWALL_ANY);
9924 boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9925 element == EL_EXPANDABLE_WALL_ANY ||
9926 element == EL_EXPANDABLE_WALL ||
9927 element == EL_BD_EXPANDABLE_WALL ||
9928 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9929 element == EL_EXPANDABLE_STEELWALL_ANY);
9931 boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9932 element == EL_EXPANDABLE_STEELWALL_VERTICAL);
9934 boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9935 element == EL_EXPANDABLE_WALL ||
9936 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL);
9938 int wall_growing = (is_steelwall ?
9939 EL_EXPANDABLE_STEELWALL_GROWING :
9940 EL_EXPANDABLE_WALL_GROWING);
9942 int gfx_wall_growing_up = (is_steelwall ?
9943 IMG_EXPANDABLE_STEELWALL_GROWING_UP :
9944 IMG_EXPANDABLE_WALL_GROWING_UP);
9945 int gfx_wall_growing_down = (is_steelwall ?
9946 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN :
9947 IMG_EXPANDABLE_WALL_GROWING_DOWN);
9948 int gfx_wall_growing_left = (is_steelwall ?
9949 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT :
9950 IMG_EXPANDABLE_WALL_GROWING_LEFT);
9951 int gfx_wall_growing_right = (is_steelwall ?
9952 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT :
9953 IMG_EXPANDABLE_WALL_GROWING_RIGHT);
9955 if (IS_ANIMATED(graphic))
9956 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9958 if (!MovDelay[ax][ay]) // start building new wall
9959 MovDelay[ax][ay] = 6;
9961 if (MovDelay[ax][ay]) // wait some time before building new wall
9964 if (MovDelay[ax][ay])
9968 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9970 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9972 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9974 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9981 Tile[ax][ay - 1] = wall_growing;
9982 Store[ax][ay - 1] = element;
9983 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9985 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9986 DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0);
9993 Tile[ax][ay + 1] = wall_growing;
9994 Store[ax][ay + 1] = element;
9995 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9997 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9998 DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0);
10004 if (grow_horizontal)
10008 Tile[ax - 1][ay] = wall_growing;
10009 Store[ax - 1][ay] = element;
10010 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
10012 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
10013 DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0);
10020 Tile[ax + 1][ay] = wall_growing;
10021 Store[ax + 1][ay] = element;
10022 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
10024 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
10025 DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0);
10031 if (element == EL_EXPANDABLE_WALL && (free_left || free_right))
10032 TEST_DrawLevelField(ax, ay);
10034 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
10036 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
10037 stop_bottom = TRUE;
10038 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
10040 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
10043 if (((stop_top && stop_bottom) || stop_horizontal) &&
10044 ((stop_left && stop_right) || stop_vertical))
10045 Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL);
10048 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
10051 static void CheckForDragon(int x, int y)
10054 boolean dragon_found = FALSE;
10055 struct XY *xy = xy_topdown;
10057 for (i = 0; i < NUM_DIRECTIONS; i++)
10059 for (j = 0; j < 4; j++)
10061 int xx = x + j * xy[i].x;
10062 int yy = y + j * xy[i].y;
10064 if (IN_LEV_FIELD(xx, yy) &&
10065 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
10067 if (Tile[xx][yy] == EL_DRAGON)
10068 dragon_found = TRUE;
10077 for (i = 0; i < NUM_DIRECTIONS; i++)
10079 for (j = 0; j < 3; j++)
10081 int xx = x + j * xy[i].x;
10082 int yy = y + j * xy[i].y;
10084 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
10086 Tile[xx][yy] = EL_EMPTY;
10087 TEST_DrawLevelField(xx, yy);
10096 static void InitBuggyBase(int x, int y)
10098 int element = Tile[x][y];
10099 int activating_delay = FRAMES_PER_SECOND / 4;
10101 ChangeDelay[x][y] =
10102 (element == EL_SP_BUGGY_BASE ?
10103 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10104 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10106 element == EL_SP_BUGGY_BASE_ACTIVE ?
10107 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10110 static void WarnBuggyBase(int x, int y)
10113 struct XY *xy = xy_topdown;
10115 for (i = 0; i < NUM_DIRECTIONS; i++)
10117 int xx = x + xy[i].x;
10118 int yy = y + xy[i].y;
10120 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10122 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10129 static void InitTrap(int x, int y)
10131 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10134 static void ActivateTrap(int x, int y)
10136 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10139 static void ChangeActiveTrap(int x, int y)
10141 int graphic = IMG_TRAP_ACTIVE;
10143 // if new animation frame was drawn, correct crumbled sand border
10144 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10145 TEST_DrawLevelFieldCrumbled(x, y);
10148 static int getSpecialActionElement(int element, int number, int base_element)
10150 return (element != EL_EMPTY ? element :
10151 number != -1 ? base_element + number - 1 :
10155 static int getModifiedActionNumber(int value_old, int operator, int operand,
10156 int value_min, int value_max)
10158 int value_new = (operator == CA_MODE_SET ? operand :
10159 operator == CA_MODE_ADD ? value_old + operand :
10160 operator == CA_MODE_SUBTRACT ? value_old - operand :
10161 operator == CA_MODE_MULTIPLY ? value_old * operand :
10162 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10163 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10166 return (value_new < value_min ? value_min :
10167 value_new > value_max ? value_max :
10171 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10173 struct ElementInfo *ei = &element_info[element];
10174 struct ElementChangeInfo *change = &ei->change_page[page];
10175 int target_element = change->target_element;
10176 int action_type = change->action_type;
10177 int action_mode = change->action_mode;
10178 int action_arg = change->action_arg;
10179 int action_element = change->action_element;
10182 if (!change->has_action)
10185 // ---------- determine action paramater values -----------------------------
10187 int level_time_value =
10188 (level.time > 0 ? TimeLeft :
10191 int action_arg_element_raw =
10192 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10193 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10194 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10195 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10196 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10197 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10198 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10200 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10202 int action_arg_direction =
10203 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10204 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10205 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10206 change->actual_trigger_side :
10207 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10208 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10211 int action_arg_number_min =
10212 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10215 int action_arg_number_max =
10216 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10217 action_type == CA_SET_LEVEL_GEMS ? 999 :
10218 action_type == CA_SET_LEVEL_TIME ? 9999 :
10219 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10220 action_type == CA_SET_CE_VALUE ? 9999 :
10221 action_type == CA_SET_CE_SCORE ? 9999 :
10224 int action_arg_number_reset =
10225 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10226 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10227 action_type == CA_SET_LEVEL_TIME ? level.time :
10228 action_type == CA_SET_LEVEL_SCORE ? 0 :
10229 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10230 action_type == CA_SET_CE_SCORE ? 0 :
10233 int action_arg_number =
10234 (action_arg <= CA_ARG_MAX ? action_arg :
10235 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10236 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10237 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10238 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10239 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10240 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10241 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10242 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10243 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10244 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10245 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10246 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10247 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10248 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10249 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10250 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10251 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10252 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10253 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10254 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10255 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10258 int action_arg_number_old =
10259 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10260 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10261 action_type == CA_SET_LEVEL_SCORE ? game.score :
10262 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10263 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10266 int action_arg_number_new =
10267 getModifiedActionNumber(action_arg_number_old,
10268 action_mode, action_arg_number,
10269 action_arg_number_min, action_arg_number_max);
10271 int trigger_player_bits =
10272 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10273 change->actual_trigger_player_bits : change->trigger_player);
10275 int action_arg_player_bits =
10276 (action_arg >= CA_ARG_PLAYER_1 &&
10277 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10278 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10279 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10282 // ---------- execute action -----------------------------------------------
10284 switch (action_type)
10291 // ---------- level actions ----------------------------------------------
10293 case CA_RESTART_LEVEL:
10295 game.restart_level = TRUE;
10300 case CA_SHOW_ENVELOPE:
10302 int element = getSpecialActionElement(action_arg_element,
10303 action_arg_number, EL_ENVELOPE_1);
10305 if (IS_ENVELOPE(element))
10306 local_player->show_envelope = element;
10311 case CA_SET_LEVEL_TIME:
10313 if (level.time > 0) // only modify limited time value
10315 TimeLeft = action_arg_number_new;
10317 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10319 DisplayGameControlValues();
10321 if (!TimeLeft && game.time_limit)
10322 for (i = 0; i < MAX_PLAYERS; i++)
10323 KillPlayer(&stored_player[i]);
10329 case CA_SET_LEVEL_SCORE:
10331 game.score = action_arg_number_new;
10333 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10335 DisplayGameControlValues();
10340 case CA_SET_LEVEL_GEMS:
10342 game.gems_still_needed = action_arg_number_new;
10344 game.snapshot.collected_item = TRUE;
10346 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10348 DisplayGameControlValues();
10353 case CA_SET_LEVEL_WIND:
10355 game.wind_direction = action_arg_direction;
10360 case CA_SET_LEVEL_RANDOM_SEED:
10362 // ensure that setting a new random seed while playing is predictable
10363 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10368 // ---------- player actions ---------------------------------------------
10370 case CA_MOVE_PLAYER:
10371 case CA_MOVE_PLAYER_NEW:
10373 // automatically move to the next field in specified direction
10374 for (i = 0; i < MAX_PLAYERS; i++)
10375 if (trigger_player_bits & (1 << i))
10376 if (action_type == CA_MOVE_PLAYER ||
10377 stored_player[i].MovPos == 0)
10378 stored_player[i].programmed_action = action_arg_direction;
10383 case CA_EXIT_PLAYER:
10385 for (i = 0; i < MAX_PLAYERS; i++)
10386 if (action_arg_player_bits & (1 << i))
10387 ExitPlayer(&stored_player[i]);
10389 if (game.players_still_needed == 0)
10395 case CA_KILL_PLAYER:
10397 for (i = 0; i < MAX_PLAYERS; i++)
10398 if (action_arg_player_bits & (1 << i))
10399 KillPlayer(&stored_player[i]);
10404 case CA_SET_PLAYER_KEYS:
10406 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10407 int element = getSpecialActionElement(action_arg_element,
10408 action_arg_number, EL_KEY_1);
10410 if (IS_KEY(element))
10412 for (i = 0; i < MAX_PLAYERS; i++)
10414 if (trigger_player_bits & (1 << i))
10416 stored_player[i].key[KEY_NR(element)] = key_state;
10418 DrawGameDoorValues();
10426 case CA_SET_PLAYER_SPEED:
10428 for (i = 0; i < MAX_PLAYERS; i++)
10430 if (trigger_player_bits & (1 << i))
10432 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10434 if (action_arg == CA_ARG_SPEED_FASTER &&
10435 stored_player[i].cannot_move)
10437 action_arg_number = STEPSIZE_VERY_SLOW;
10439 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10440 action_arg == CA_ARG_SPEED_FASTER)
10442 action_arg_number = 2;
10443 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10446 else if (action_arg == CA_ARG_NUMBER_RESET)
10448 action_arg_number = level.initial_player_stepsize[i];
10452 getModifiedActionNumber(move_stepsize,
10455 action_arg_number_min,
10456 action_arg_number_max);
10458 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10465 case CA_SET_PLAYER_SHIELD:
10467 for (i = 0; i < MAX_PLAYERS; i++)
10469 if (trigger_player_bits & (1 << i))
10471 if (action_arg == CA_ARG_SHIELD_OFF)
10473 stored_player[i].shield_normal_time_left = 0;
10474 stored_player[i].shield_deadly_time_left = 0;
10476 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10478 stored_player[i].shield_normal_time_left = 999999;
10480 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10482 stored_player[i].shield_normal_time_left = 999999;
10483 stored_player[i].shield_deadly_time_left = 999999;
10491 case CA_SET_PLAYER_GRAVITY:
10493 for (i = 0; i < MAX_PLAYERS; i++)
10495 if (trigger_player_bits & (1 << i))
10497 stored_player[i].gravity =
10498 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10499 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10500 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10501 stored_player[i].gravity);
10508 case CA_SET_PLAYER_ARTWORK:
10510 for (i = 0; i < MAX_PLAYERS; i++)
10512 if (trigger_player_bits & (1 << i))
10514 int artwork_element = action_arg_element;
10516 if (action_arg == CA_ARG_ELEMENT_RESET)
10518 (level.use_artwork_element[i] ? level.artwork_element[i] :
10519 stored_player[i].element_nr);
10521 if (stored_player[i].artwork_element != artwork_element)
10522 stored_player[i].Frame = 0;
10524 stored_player[i].artwork_element = artwork_element;
10526 SetPlayerWaiting(&stored_player[i], FALSE);
10528 // set number of special actions for bored and sleeping animation
10529 stored_player[i].num_special_action_bored =
10530 get_num_special_action(artwork_element,
10531 ACTION_BORING_1, ACTION_BORING_LAST);
10532 stored_player[i].num_special_action_sleeping =
10533 get_num_special_action(artwork_element,
10534 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10541 case CA_SET_PLAYER_INVENTORY:
10543 for (i = 0; i < MAX_PLAYERS; i++)
10545 struct PlayerInfo *player = &stored_player[i];
10548 if (trigger_player_bits & (1 << i))
10550 int inventory_element = action_arg_element;
10552 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10553 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10554 action_arg == CA_ARG_ELEMENT_ACTION)
10556 int element = inventory_element;
10557 int collect_count = element_info[element].collect_count_initial;
10559 if (!IS_CUSTOM_ELEMENT(element))
10562 if (collect_count == 0)
10563 player->inventory_infinite_element = element;
10565 for (k = 0; k < collect_count; k++)
10566 if (player->inventory_size < MAX_INVENTORY_SIZE)
10567 player->inventory_element[player->inventory_size++] =
10570 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10571 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10572 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10574 if (player->inventory_infinite_element != EL_UNDEFINED &&
10575 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10576 action_arg_element_raw))
10577 player->inventory_infinite_element = EL_UNDEFINED;
10579 for (k = 0, j = 0; j < player->inventory_size; j++)
10581 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10582 action_arg_element_raw))
10583 player->inventory_element[k++] = player->inventory_element[j];
10586 player->inventory_size = k;
10588 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10590 if (player->inventory_size > 0)
10592 for (j = 0; j < player->inventory_size - 1; j++)
10593 player->inventory_element[j] = player->inventory_element[j + 1];
10595 player->inventory_size--;
10598 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10600 if (player->inventory_size > 0)
10601 player->inventory_size--;
10603 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10605 player->inventory_infinite_element = EL_UNDEFINED;
10606 player->inventory_size = 0;
10608 else if (action_arg == CA_ARG_INVENTORY_RESET)
10610 player->inventory_infinite_element = EL_UNDEFINED;
10611 player->inventory_size = 0;
10613 if (level.use_initial_inventory[i])
10615 for (j = 0; j < level.initial_inventory_size[i]; j++)
10617 int element = level.initial_inventory_content[i][j];
10618 int collect_count = element_info[element].collect_count_initial;
10620 if (!IS_CUSTOM_ELEMENT(element))
10623 if (collect_count == 0)
10624 player->inventory_infinite_element = element;
10626 for (k = 0; k < collect_count; k++)
10627 if (player->inventory_size < MAX_INVENTORY_SIZE)
10628 player->inventory_element[player->inventory_size++] =
10639 // ---------- CE actions -------------------------------------------------
10641 case CA_SET_CE_VALUE:
10643 int last_ce_value = CustomValue[x][y];
10645 CustomValue[x][y] = action_arg_number_new;
10647 if (CustomValue[x][y] != last_ce_value)
10649 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10650 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10652 if (CustomValue[x][y] == 0)
10654 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10655 ChangeCount[x][y] = 0; // allow at least one more change
10657 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10658 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10665 case CA_SET_CE_SCORE:
10667 int last_ce_score = ei->collect_score;
10669 ei->collect_score = action_arg_number_new;
10671 if (ei->collect_score != last_ce_score)
10673 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10674 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10676 if (ei->collect_score == 0)
10680 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10681 ChangeCount[x][y] = 0; // allow at least one more change
10683 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10684 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10687 This is a very special case that seems to be a mixture between
10688 CheckElementChange() and CheckTriggeredElementChange(): while
10689 the first one only affects single elements that are triggered
10690 directly, the second one affects multiple elements in the playfield
10691 that are triggered indirectly by another element. This is a third
10692 case: Changing the CE score always affects multiple identical CEs,
10693 so every affected CE must be checked, not only the single CE for
10694 which the CE score was changed in the first place (as every instance
10695 of that CE shares the same CE score, and therefore also can change)!
10697 SCAN_PLAYFIELD(xx, yy)
10699 if (Tile[xx][yy] == element)
10700 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10701 CE_SCORE_GETS_ZERO);
10709 case CA_SET_CE_ARTWORK:
10711 int artwork_element = action_arg_element;
10712 boolean reset_frame = FALSE;
10715 if (action_arg == CA_ARG_ELEMENT_RESET)
10716 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10719 if (ei->gfx_element != artwork_element)
10720 reset_frame = TRUE;
10722 ei->gfx_element = artwork_element;
10724 SCAN_PLAYFIELD(xx, yy)
10726 if (Tile[xx][yy] == element)
10730 ResetGfxAnimation(xx, yy);
10731 ResetRandomAnimationValue(xx, yy);
10734 TEST_DrawLevelField(xx, yy);
10741 // ---------- engine actions ---------------------------------------------
10743 case CA_SET_ENGINE_SCAN_MODE:
10745 InitPlayfieldScanMode(action_arg);
10755 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10757 int old_element = Tile[x][y];
10758 int new_element = GetElementFromGroupElement(element);
10759 int previous_move_direction = MovDir[x][y];
10760 int last_ce_value = CustomValue[x][y];
10761 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10762 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10763 boolean add_player_onto_element = (new_element_is_player &&
10764 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10765 IS_WALKABLE(old_element));
10767 if (!add_player_onto_element)
10769 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10770 RemoveMovingField(x, y);
10774 Tile[x][y] = new_element;
10776 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10777 MovDir[x][y] = previous_move_direction;
10779 if (element_info[new_element].use_last_ce_value)
10780 CustomValue[x][y] = last_ce_value;
10782 InitField_WithBug1(x, y, FALSE);
10784 new_element = Tile[x][y]; // element may have changed
10786 ResetGfxAnimation(x, y);
10787 ResetRandomAnimationValue(x, y);
10789 TEST_DrawLevelField(x, y);
10791 if (GFX_CRUMBLED(new_element))
10792 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10794 if (old_element == EL_EXPLOSION)
10796 Store[x][y] = Store2[x][y] = 0;
10798 // check if new element replaces an exploding player, requiring cleanup
10799 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
10800 StorePlayer[x][y] = 0;
10803 // check if element under the player changes from accessible to unaccessible
10804 // (needed for special case of dropping element which then changes)
10805 // (must be checked after creating new element for walkable group elements)
10806 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10807 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10809 KillPlayer(PLAYERINFO(x, y));
10815 // "ChangeCount" not set yet to allow "entered by player" change one time
10816 if (new_element_is_player)
10817 RelocatePlayer(x, y, new_element);
10820 ChangeCount[x][y]++; // count number of changes in the same frame
10822 TestIfBadThingTouchesPlayer(x, y);
10823 TestIfPlayerTouchesCustomElement(x, y);
10824 TestIfElementTouchesCustomElement(x, y);
10827 static void CreateField(int x, int y, int element)
10829 CreateFieldExt(x, y, element, FALSE);
10832 static void CreateElementFromChange(int x, int y, int element)
10834 element = GET_VALID_RUNTIME_ELEMENT(element);
10836 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10838 int old_element = Tile[x][y];
10840 // prevent changed element from moving in same engine frame
10841 // unless both old and new element can either fall or move
10842 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10843 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10847 CreateFieldExt(x, y, element, TRUE);
10850 static boolean ChangeElement(int x, int y, int element, int page)
10852 struct ElementInfo *ei = &element_info[element];
10853 struct ElementChangeInfo *change = &ei->change_page[page];
10854 int ce_value = CustomValue[x][y];
10855 int ce_score = ei->collect_score;
10856 int target_element;
10857 int old_element = Tile[x][y];
10859 // always use default change event to prevent running into a loop
10860 if (ChangeEvent[x][y] == -1)
10861 ChangeEvent[x][y] = CE_DELAY;
10863 if (ChangeEvent[x][y] == CE_DELAY)
10865 // reset actual trigger element, trigger player and action element
10866 change->actual_trigger_element = EL_EMPTY;
10867 change->actual_trigger_player = EL_EMPTY;
10868 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10869 change->actual_trigger_side = CH_SIDE_NONE;
10870 change->actual_trigger_ce_value = 0;
10871 change->actual_trigger_ce_score = 0;
10872 change->actual_trigger_x = -1;
10873 change->actual_trigger_y = -1;
10876 // do not change elements more than a specified maximum number of changes
10877 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10880 ChangeCount[x][y]++; // count number of changes in the same frame
10882 if (ei->has_anim_event)
10883 HandleGlobalAnimEventByElementChange(element, page, x, y,
10884 change->actual_trigger_x,
10885 change->actual_trigger_y);
10887 if (change->explode)
10894 if (change->use_target_content)
10896 boolean complete_replace = TRUE;
10897 boolean can_replace[3][3];
10900 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10903 boolean is_walkable;
10904 boolean is_diggable;
10905 boolean is_collectible;
10906 boolean is_removable;
10907 boolean is_destructible;
10908 int ex = x + xx - 1;
10909 int ey = y + yy - 1;
10910 int content_element = change->target_content.e[xx][yy];
10913 can_replace[xx][yy] = TRUE;
10915 if (ex == x && ey == y) // do not check changing element itself
10918 if (content_element == EL_EMPTY_SPACE)
10920 can_replace[xx][yy] = FALSE; // do not replace border with space
10925 if (!IN_LEV_FIELD(ex, ey))
10927 can_replace[xx][yy] = FALSE;
10928 complete_replace = FALSE;
10935 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10936 e = MovingOrBlocked2Element(ex, ey);
10938 is_empty = (IS_FREE(ex, ey) ||
10939 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10941 is_walkable = (is_empty || IS_WALKABLE(e));
10942 is_diggable = (is_empty || IS_DIGGABLE(e));
10943 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10944 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10945 is_removable = (is_diggable || is_collectible);
10947 can_replace[xx][yy] =
10948 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10949 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10950 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10951 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10952 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10953 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10954 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10956 if (!can_replace[xx][yy])
10957 complete_replace = FALSE;
10960 if (!change->only_if_complete || complete_replace)
10962 boolean something_has_changed = FALSE;
10964 if (change->only_if_complete && change->use_random_replace &&
10965 RND(100) < change->random_percentage)
10968 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10970 int ex = x + xx - 1;
10971 int ey = y + yy - 1;
10972 int content_element;
10974 if (can_replace[xx][yy] && (!change->use_random_replace ||
10975 RND(100) < change->random_percentage))
10977 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10978 RemoveMovingField(ex, ey);
10980 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10982 content_element = change->target_content.e[xx][yy];
10983 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10984 ce_value, ce_score);
10986 CreateElementFromChange(ex, ey, target_element);
10988 something_has_changed = TRUE;
10990 // for symmetry reasons, freeze newly created border elements
10991 if (ex != x || ey != y)
10992 Stop[ex][ey] = TRUE; // no more moving in this frame
10996 if (something_has_changed)
10998 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10999 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
11005 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
11006 ce_value, ce_score);
11008 if (element == EL_DIAGONAL_GROWING ||
11009 element == EL_DIAGONAL_SHRINKING)
11011 target_element = Store[x][y];
11013 Store[x][y] = EL_EMPTY;
11016 // special case: element changes to player (and may be kept if walkable)
11017 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
11018 CreateElementFromChange(x, y, EL_EMPTY);
11020 CreateElementFromChange(x, y, target_element);
11022 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
11023 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
11026 // this uses direct change before indirect change
11027 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
11032 static void HandleElementChange(int x, int y, int page)
11034 int element = MovingOrBlocked2Element(x, y);
11035 struct ElementInfo *ei = &element_info[element];
11036 struct ElementChangeInfo *change = &ei->change_page[page];
11037 boolean handle_action_before_change = FALSE;
11040 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
11041 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
11043 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
11044 x, y, element, element_info[element].token_name);
11045 Debug("game:playing:HandleElementChange", "This should never happen!");
11049 // this can happen with classic bombs on walkable, changing elements
11050 if (!CAN_CHANGE_OR_HAS_ACTION(element))
11055 if (ChangeDelay[x][y] == 0) // initialize element change
11057 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
11059 if (change->can_change)
11061 // !!! not clear why graphic animation should be reset at all here !!!
11062 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
11063 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
11066 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
11068 When using an animation frame delay of 1 (this only happens with
11069 "sp_zonk.moving.left/right" in the classic graphics), the default
11070 (non-moving) animation shows wrong animation frames (while the
11071 moving animation, like "sp_zonk.moving.left/right", is correct,
11072 so this graphical bug never shows up with the classic graphics).
11073 For an animation with 4 frames, this causes wrong frames 0,0,1,2
11074 be drawn instead of the correct frames 0,1,2,3. This is caused by
11075 "GfxFrame[][]" being reset *twice* (in two successive frames) after
11076 an element change: First when the change delay ("ChangeDelay[][]")
11077 counter has reached zero after decrementing, then a second time in
11078 the next frame (after "GfxFrame[][]" was already incremented) when
11079 "ChangeDelay[][]" is reset to the initial delay value again.
11081 This causes frame 0 to be drawn twice, while the last frame won't
11082 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
11084 As some animations may already be cleverly designed around this bug
11085 (at least the "Snake Bite" snake tail animation does this), it cannot
11086 simply be fixed here without breaking such existing animations.
11087 Unfortunately, it cannot easily be detected if a graphics set was
11088 designed "before" or "after" the bug was fixed. As a workaround,
11089 a new graphics set option "game.graphics_engine_version" was added
11090 to be able to specify the game's major release version for which the
11091 graphics set was designed, which can then be used to decide if the
11092 bugfix should be used (version 4 and above) or not (version 3 or
11093 below, or if no version was specified at all, as with old sets).
11095 (The wrong/fixed animation frames can be tested with the test level set
11096 "test_gfxframe" and level "000", which contains a specially prepared
11097 custom element at level position (x/y) == (11/9) which uses the zonk
11098 animation mentioned above. Using "game.graphics_engine_version: 4"
11099 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
11100 This can also be seen from the debug output for this test element.)
11103 // when a custom element is about to change (for example by change delay),
11104 // do not reset graphic animation when the custom element is moving
11105 if (game.graphics_engine_version < 4 &&
11108 ResetGfxAnimation(x, y);
11109 ResetRandomAnimationValue(x, y);
11112 if (change->pre_change_function)
11113 change->pre_change_function(x, y);
11117 ChangeDelay[x][y]--;
11119 if (ChangeDelay[x][y] != 0) // continue element change
11121 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11123 // also needed if CE can not change, but has CE delay with CE action
11124 if (IS_ANIMATED(graphic))
11125 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11127 if (change->can_change)
11129 if (change->change_function)
11130 change->change_function(x, y);
11133 else // finish element change
11135 if (ChangePage[x][y] != -1) // remember page from delayed change
11137 page = ChangePage[x][y];
11138 ChangePage[x][y] = -1;
11140 change = &ei->change_page[page];
11143 if (IS_MOVING(x, y)) // never change a running system ;-)
11145 ChangeDelay[x][y] = 1; // try change after next move step
11146 ChangePage[x][y] = page; // remember page to use for change
11151 // special case: set new level random seed before changing element
11152 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11153 handle_action_before_change = TRUE;
11155 if (change->has_action && handle_action_before_change)
11156 ExecuteCustomElementAction(x, y, element, page);
11158 if (change->can_change)
11160 if (ChangeElement(x, y, element, page))
11162 if (change->post_change_function)
11163 change->post_change_function(x, y);
11167 if (change->has_action && !handle_action_before_change)
11168 ExecuteCustomElementAction(x, y, element, page);
11172 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11173 int trigger_element,
11175 int trigger_player,
11179 boolean change_done_any = FALSE;
11180 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11183 if (!(trigger_events[trigger_element][trigger_event]))
11186 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11188 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11190 int element = EL_CUSTOM_START + i;
11191 boolean change_done = FALSE;
11194 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11195 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11198 for (p = 0; p < element_info[element].num_change_pages; p++)
11200 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11202 if (change->can_change_or_has_action &&
11203 change->has_event[trigger_event] &&
11204 change->trigger_side & trigger_side &&
11205 change->trigger_player & trigger_player &&
11206 change->trigger_page & trigger_page_bits &&
11207 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11209 change->actual_trigger_element = trigger_element;
11210 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11211 change->actual_trigger_player_bits = trigger_player;
11212 change->actual_trigger_side = trigger_side;
11213 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11214 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11215 change->actual_trigger_x = trigger_x;
11216 change->actual_trigger_y = trigger_y;
11218 if ((change->can_change && !change_done) || change->has_action)
11222 SCAN_PLAYFIELD(x, y)
11224 if (Tile[x][y] == element)
11226 if (change->can_change && !change_done)
11228 // if element already changed in this frame, not only prevent
11229 // another element change (checked in ChangeElement()), but
11230 // also prevent additional element actions for this element
11232 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11233 !level.use_action_after_change_bug)
11236 ChangeDelay[x][y] = 1;
11237 ChangeEvent[x][y] = trigger_event;
11239 HandleElementChange(x, y, p);
11241 else if (change->has_action)
11243 // if element already changed in this frame, not only prevent
11244 // another element change (checked in ChangeElement()), but
11245 // also prevent additional element actions for this element
11247 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11248 !level.use_action_after_change_bug)
11251 ExecuteCustomElementAction(x, y, element, p);
11252 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11257 if (change->can_change)
11259 change_done = TRUE;
11260 change_done_any = TRUE;
11267 RECURSION_LOOP_DETECTION_END();
11269 return change_done_any;
11272 static boolean CheckElementChangeExt(int x, int y,
11274 int trigger_element,
11276 int trigger_player,
11279 boolean change_done = FALSE;
11282 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11283 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11286 if (Tile[x][y] == EL_BLOCKED)
11288 Blocked2Moving(x, y, &x, &y);
11289 element = Tile[x][y];
11292 // check if element has already changed or is about to change after moving
11293 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11294 Tile[x][y] != element) ||
11296 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11297 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11298 ChangePage[x][y] != -1)))
11301 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11303 for (p = 0; p < element_info[element].num_change_pages; p++)
11305 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11307 /* check trigger element for all events where the element that is checked
11308 for changing interacts with a directly adjacent element -- this is
11309 different to element changes that affect other elements to change on the
11310 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11311 boolean check_trigger_element =
11312 (trigger_event == CE_NEXT_TO_X ||
11313 trigger_event == CE_TOUCHING_X ||
11314 trigger_event == CE_HITTING_X ||
11315 trigger_event == CE_HIT_BY_X ||
11316 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11318 if (change->can_change_or_has_action &&
11319 change->has_event[trigger_event] &&
11320 change->trigger_side & trigger_side &&
11321 change->trigger_player & trigger_player &&
11322 (!check_trigger_element ||
11323 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11325 change->actual_trigger_element = trigger_element;
11326 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11327 change->actual_trigger_player_bits = trigger_player;
11328 change->actual_trigger_side = trigger_side;
11329 change->actual_trigger_ce_value = CustomValue[x][y];
11330 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11331 change->actual_trigger_x = x;
11332 change->actual_trigger_y = y;
11334 // special case: trigger element not at (x,y) position for some events
11335 if (check_trigger_element)
11347 { 0, 0 }, { 0, 0 }, { 0, 0 },
11351 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11352 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11354 change->actual_trigger_ce_value = CustomValue[xx][yy];
11355 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11356 change->actual_trigger_x = xx;
11357 change->actual_trigger_y = yy;
11360 if (change->can_change && !change_done)
11362 ChangeDelay[x][y] = 1;
11363 ChangeEvent[x][y] = trigger_event;
11365 HandleElementChange(x, y, p);
11367 change_done = TRUE;
11369 else if (change->has_action)
11371 ExecuteCustomElementAction(x, y, element, p);
11372 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11377 RECURSION_LOOP_DETECTION_END();
11379 return change_done;
11382 static void PlayPlayerSound(struct PlayerInfo *player)
11384 int jx = player->jx, jy = player->jy;
11385 int sound_element = player->artwork_element;
11386 int last_action = player->last_action_waiting;
11387 int action = player->action_waiting;
11389 if (player->is_waiting)
11391 if (action != last_action)
11392 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11394 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11398 if (action != last_action)
11399 StopSound(element_info[sound_element].sound[last_action]);
11401 if (last_action == ACTION_SLEEPING)
11402 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11406 static void PlayAllPlayersSound(void)
11410 for (i = 0; i < MAX_PLAYERS; i++)
11411 if (stored_player[i].active)
11412 PlayPlayerSound(&stored_player[i]);
11415 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11417 boolean last_waiting = player->is_waiting;
11418 int move_dir = player->MovDir;
11420 player->dir_waiting = move_dir;
11421 player->last_action_waiting = player->action_waiting;
11425 if (!last_waiting) // not waiting -> waiting
11427 player->is_waiting = TRUE;
11429 player->frame_counter_bored =
11431 game.player_boring_delay_fixed +
11432 GetSimpleRandom(game.player_boring_delay_random);
11433 player->frame_counter_sleeping =
11435 game.player_sleeping_delay_fixed +
11436 GetSimpleRandom(game.player_sleeping_delay_random);
11438 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11441 if (game.player_sleeping_delay_fixed +
11442 game.player_sleeping_delay_random > 0 &&
11443 player->anim_delay_counter == 0 &&
11444 player->post_delay_counter == 0 &&
11445 FrameCounter >= player->frame_counter_sleeping)
11446 player->is_sleeping = TRUE;
11447 else if (game.player_boring_delay_fixed +
11448 game.player_boring_delay_random > 0 &&
11449 FrameCounter >= player->frame_counter_bored)
11450 player->is_bored = TRUE;
11452 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11453 player->is_bored ? ACTION_BORING :
11456 if (player->is_sleeping && player->use_murphy)
11458 // special case for sleeping Murphy when leaning against non-free tile
11460 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11461 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11462 !IS_MOVING(player->jx - 1, player->jy)))
11463 move_dir = MV_LEFT;
11464 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11465 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11466 !IS_MOVING(player->jx + 1, player->jy)))
11467 move_dir = MV_RIGHT;
11469 player->is_sleeping = FALSE;
11471 player->dir_waiting = move_dir;
11474 if (player->is_sleeping)
11476 if (player->num_special_action_sleeping > 0)
11478 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11480 int last_special_action = player->special_action_sleeping;
11481 int num_special_action = player->num_special_action_sleeping;
11482 int special_action =
11483 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11484 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11485 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11486 last_special_action + 1 : ACTION_SLEEPING);
11487 int special_graphic =
11488 el_act_dir2img(player->artwork_element, special_action, move_dir);
11490 player->anim_delay_counter =
11491 graphic_info[special_graphic].anim_delay_fixed +
11492 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11493 player->post_delay_counter =
11494 graphic_info[special_graphic].post_delay_fixed +
11495 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11497 player->special_action_sleeping = special_action;
11500 if (player->anim_delay_counter > 0)
11502 player->action_waiting = player->special_action_sleeping;
11503 player->anim_delay_counter--;
11505 else if (player->post_delay_counter > 0)
11507 player->post_delay_counter--;
11511 else if (player->is_bored)
11513 if (player->num_special_action_bored > 0)
11515 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11517 int special_action =
11518 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11519 int special_graphic =
11520 el_act_dir2img(player->artwork_element, special_action, move_dir);
11522 player->anim_delay_counter =
11523 graphic_info[special_graphic].anim_delay_fixed +
11524 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11525 player->post_delay_counter =
11526 graphic_info[special_graphic].post_delay_fixed +
11527 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11529 player->special_action_bored = special_action;
11532 if (player->anim_delay_counter > 0)
11534 player->action_waiting = player->special_action_bored;
11535 player->anim_delay_counter--;
11537 else if (player->post_delay_counter > 0)
11539 player->post_delay_counter--;
11544 else if (last_waiting) // waiting -> not waiting
11546 player->is_waiting = FALSE;
11547 player->is_bored = FALSE;
11548 player->is_sleeping = FALSE;
11550 player->frame_counter_bored = -1;
11551 player->frame_counter_sleeping = -1;
11553 player->anim_delay_counter = 0;
11554 player->post_delay_counter = 0;
11556 player->dir_waiting = player->MovDir;
11557 player->action_waiting = ACTION_DEFAULT;
11559 player->special_action_bored = ACTION_DEFAULT;
11560 player->special_action_sleeping = ACTION_DEFAULT;
11564 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11566 if ((!player->is_moving && player->was_moving) ||
11567 (player->MovPos == 0 && player->was_moving) ||
11568 (player->is_snapping && !player->was_snapping) ||
11569 (player->is_dropping && !player->was_dropping))
11571 if (!CheckSaveEngineSnapshotToList())
11574 player->was_moving = FALSE;
11575 player->was_snapping = TRUE;
11576 player->was_dropping = TRUE;
11580 if (player->is_moving)
11581 player->was_moving = TRUE;
11583 if (!player->is_snapping)
11584 player->was_snapping = FALSE;
11586 if (!player->is_dropping)
11587 player->was_dropping = FALSE;
11590 static struct MouseActionInfo mouse_action_last = { 0 };
11591 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11592 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11595 CheckSaveEngineSnapshotToList();
11597 mouse_action_last = mouse_action;
11600 static void CheckSingleStepMode(struct PlayerInfo *player)
11602 if (tape.single_step && tape.recording && !tape.pausing)
11604 // as it is called "single step mode", just return to pause mode when the
11605 // player stopped moving after one tile (or never starts moving at all)
11606 // (reverse logic needed here in case single step mode used in team mode)
11607 if (player->is_moving ||
11608 player->is_pushing ||
11609 player->is_dropping_pressed ||
11610 player->effective_mouse_action.button)
11611 game.enter_single_step_mode = FALSE;
11614 CheckSaveEngineSnapshot(player);
11617 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11619 int left = player_action & JOY_LEFT;
11620 int right = player_action & JOY_RIGHT;
11621 int up = player_action & JOY_UP;
11622 int down = player_action & JOY_DOWN;
11623 int button1 = player_action & JOY_BUTTON_1;
11624 int button2 = player_action & JOY_BUTTON_2;
11625 int dx = (left ? -1 : right ? 1 : 0);
11626 int dy = (up ? -1 : down ? 1 : 0);
11628 if (!player->active || tape.pausing)
11634 SnapField(player, dx, dy);
11638 DropElement(player);
11640 MovePlayer(player, dx, dy);
11643 CheckSingleStepMode(player);
11645 SetPlayerWaiting(player, FALSE);
11647 return player_action;
11651 // no actions for this player (no input at player's configured device)
11653 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11654 SnapField(player, 0, 0);
11655 CheckGravityMovementWhenNotMoving(player);
11657 if (player->MovPos == 0)
11658 SetPlayerWaiting(player, TRUE);
11660 if (player->MovPos == 0) // needed for tape.playing
11661 player->is_moving = FALSE;
11663 player->is_dropping = FALSE;
11664 player->is_dropping_pressed = FALSE;
11665 player->drop_pressed_delay = 0;
11667 CheckSingleStepMode(player);
11673 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11676 if (!tape.use_mouse_actions)
11679 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11680 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11681 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11684 static void SetTapeActionFromMouseAction(byte *tape_action,
11685 struct MouseActionInfo *mouse_action)
11687 if (!tape.use_mouse_actions)
11690 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11691 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11692 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11695 static void CheckLevelSolved(void)
11697 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11699 if (game_bd.level_solved &&
11700 !game_bd.game_over) // game won
11704 game_bd.game_over = TRUE;
11706 game.all_players_gone = TRUE;
11709 if (game_bd.game_over) // game lost
11710 game.all_players_gone = TRUE;
11712 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11714 if (game_em.level_solved &&
11715 !game_em.game_over) // game won
11719 game_em.game_over = TRUE;
11721 game.all_players_gone = TRUE;
11724 if (game_em.game_over) // game lost
11725 game.all_players_gone = TRUE;
11727 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11729 if (game_sp.level_solved &&
11730 !game_sp.game_over) // game won
11734 game_sp.game_over = TRUE;
11736 game.all_players_gone = TRUE;
11739 if (game_sp.game_over) // game lost
11740 game.all_players_gone = TRUE;
11742 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11744 if (game_mm.level_solved &&
11745 !game_mm.game_over) // game won
11749 game_mm.game_over = TRUE;
11751 game.all_players_gone = TRUE;
11754 if (game_mm.game_over) // game lost
11755 game.all_players_gone = TRUE;
11759 static void PlayTimeoutSound(int seconds_left)
11761 // will be played directly by BD engine (for classic bonus time sounds)
11762 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && checkBonusTime_BD())
11765 // try to use individual "running out of time" sound for each second left
11766 int sound = SND_GAME_RUNNING_OUT_OF_TIME_0 - seconds_left;
11768 // if special sound per second not defined, use default sound
11769 if (getSoundInfoEntryFilename(sound) == NULL)
11770 sound = SND_GAME_RUNNING_OUT_OF_TIME;
11772 // if out of time, but player still alive, play special "timeout" sound, if defined
11773 if (seconds_left == 0 && !checkGameFailed())
11774 if (getSoundInfoEntryFilename(SND_GAME_TIMEOUT) != NULL)
11775 sound = SND_GAME_TIMEOUT;
11780 static void CheckLevelTime_StepCounter(void)
11790 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11791 PlayTimeoutSound(TimeLeft);
11793 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11795 DisplayGameControlValues();
11797 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11798 for (i = 0; i < MAX_PLAYERS; i++)
11799 KillPlayer(&stored_player[i]);
11801 else if (game.no_level_time_limit && !game.all_players_gone)
11803 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11805 DisplayGameControlValues();
11809 static void CheckLevelTime(void)
11811 int frames_per_second = FRAMES_PER_SECOND;
11814 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11816 // level time may be running slower in native BD engine
11817 frames_per_second = getFramesPerSecond_BD();
11819 // if native engine time changed, force main engine time change
11820 if (getTimeLeft_BD() < TimeLeft)
11821 TimeFrames = frames_per_second;
11823 // if last second running, wait for native engine time to exactly reach zero
11824 if (getTimeLeft_BD() == 1 && TimeLeft == 1)
11825 TimeFrames = frames_per_second - 1;
11828 if (TimeFrames >= frames_per_second)
11832 for (i = 0; i < MAX_PLAYERS; i++)
11834 struct PlayerInfo *player = &stored_player[i];
11836 if (SHIELD_ON(player))
11838 player->shield_normal_time_left--;
11840 if (player->shield_deadly_time_left > 0)
11841 player->shield_deadly_time_left--;
11845 if (!game.LevelSolved && !level.use_step_counter)
11853 if (TimeLeft <= 10 && game.time_limit)
11854 PlayTimeoutSound(TimeLeft);
11856 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11857 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11859 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11861 if (!TimeLeft && game.time_limit)
11863 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11865 if (game_bd.game->cave->player_state == GD_PL_LIVING)
11866 game_bd.game->cave->player_state = GD_PL_TIMEOUT;
11868 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11870 game_em.lev->killed_out_of_time = TRUE;
11874 for (i = 0; i < MAX_PLAYERS; i++)
11875 KillPlayer(&stored_player[i]);
11879 else if (game.no_level_time_limit && !game.all_players_gone)
11881 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11884 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11888 if (TapeTimeFrames >= FRAMES_PER_SECOND)
11890 TapeTimeFrames = 0;
11893 if (tape.recording || tape.playing)
11894 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11897 if (tape.recording || tape.playing)
11898 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11900 UpdateAndDisplayGameControlValues();
11903 void AdvanceFrameAndPlayerCounters(int player_nr)
11907 // handle game and tape time differently for native BD game engine
11909 // tape time is running in native BD engine even if player is not hatched yet
11910 if (!checkGameRunning())
11913 // advance frame counters (global frame counter and tape time frame counter)
11917 // level time is running in native BD engine after player is being hatched
11918 if (!checkGamePlaying())
11921 // advance time frame counter (used to control available time to solve level)
11924 // advance player counters (counters for move delay, move animation etc.)
11925 for (i = 0; i < MAX_PLAYERS; i++)
11927 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11928 int move_delay_value = stored_player[i].move_delay_value;
11929 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11931 if (!advance_player_counters) // not all players may be affected
11934 if (move_frames == 0) // less than one move per game frame
11936 int stepsize = TILEX / move_delay_value;
11937 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11938 int count = (stored_player[i].is_moving ?
11939 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11941 if (count % delay == 0)
11945 stored_player[i].Frame += move_frames;
11947 if (stored_player[i].MovPos != 0)
11948 stored_player[i].StepFrame += move_frames;
11950 if (stored_player[i].move_delay > 0)
11951 stored_player[i].move_delay--;
11953 // due to bugs in previous versions, counter must count up, not down
11954 if (stored_player[i].push_delay != -1)
11955 stored_player[i].push_delay++;
11957 if (stored_player[i].drop_delay > 0)
11958 stored_player[i].drop_delay--;
11960 if (stored_player[i].is_dropping_pressed)
11961 stored_player[i].drop_pressed_delay++;
11965 void AdvanceFrameCounter(void)
11970 void AdvanceGfxFrame(void)
11974 SCAN_PLAYFIELD(x, y)
11980 static void HandleMouseAction(struct MouseActionInfo *mouse_action,
11981 struct MouseActionInfo *mouse_action_last)
11983 if (mouse_action->button)
11985 int new_button = (mouse_action->button && mouse_action_last->button == 0);
11986 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button);
11987 int x = mouse_action->lx;
11988 int y = mouse_action->ly;
11989 int element = Tile[x][y];
11993 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
11994 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
11998 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
11999 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12002 if (level.use_step_counter)
12004 boolean counted_click = FALSE;
12006 // element clicked that can change when clicked/pressed
12007 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
12008 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
12009 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
12010 counted_click = TRUE;
12012 // element clicked that can trigger change when clicked/pressed
12013 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
12014 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
12015 counted_click = TRUE;
12017 if (new_button && counted_click)
12018 CheckLevelTime_StepCounter();
12023 void StartGameActions(boolean init_network_game, boolean record_tape,
12026 unsigned int new_random_seed = InitRND(random_seed);
12029 TapeStartRecording(new_random_seed);
12031 if (setup.auto_pause_on_start && !tape.pausing)
12032 TapeTogglePause(TAPE_TOGGLE_MANUAL);
12034 if (init_network_game)
12036 SendToServer_LevelFile();
12037 SendToServer_StartPlaying();
12045 static void GameActionsExt(void)
12048 static unsigned int game_frame_delay = 0;
12050 unsigned int game_frame_delay_value;
12051 byte *recorded_player_action;
12052 byte summarized_player_action = 0;
12053 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
12056 // detect endless loops, caused by custom element programming
12057 if (recursion_loop_detected && recursion_loop_depth == 0)
12059 char *message = getStringCat3("Internal Error! Element ",
12060 EL_NAME(recursion_loop_element),
12061 " caused endless loop! Quit the game?");
12063 Warn("element '%s' caused endless loop in game engine",
12064 EL_NAME(recursion_loop_element));
12066 RequestQuitGameExt(program.headless, level_editor_test_game, message);
12068 recursion_loop_detected = FALSE; // if game should be continued
12075 if (game.restart_level)
12076 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
12078 CheckLevelSolved();
12080 if (game.LevelSolved && !game.LevelSolved_GameEnd)
12083 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
12086 if (game_status != GAME_MODE_PLAYING) // status might have changed
12089 game_frame_delay_value =
12090 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
12092 if (tape.playing && tape.warp_forward && !tape.pausing)
12093 game_frame_delay_value = 0;
12095 SetVideoFrameDelay(game_frame_delay_value);
12097 // (de)activate virtual buttons depending on current game status
12098 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
12100 if (game.all_players_gone) // if no players there to be controlled anymore
12101 SetOverlayActive(FALSE);
12102 else if (!tape.playing) // if game continues after tape stopped playing
12103 SetOverlayActive(TRUE);
12108 // ---------- main game synchronization point ----------
12110 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12112 Debug("game:playing:skip", "skip == %d", skip);
12115 // ---------- main game synchronization point ----------
12117 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12121 if (network_playing && !network_player_action_received)
12123 // try to get network player actions in time
12125 // last chance to get network player actions without main loop delay
12126 HandleNetworking();
12128 // game was quit by network peer
12129 if (game_status != GAME_MODE_PLAYING)
12132 // check if network player actions still missing and game still running
12133 if (!network_player_action_received && !checkGameEnded())
12134 return; // failed to get network player actions in time
12136 // do not yet reset "network_player_action_received" (for tape.pausing)
12142 // at this point we know that we really continue executing the game
12144 network_player_action_received = FALSE;
12146 // when playing tape, read previously recorded player input from tape data
12147 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
12149 local_player->effective_mouse_action = local_player->mouse_action;
12151 if (recorded_player_action != NULL)
12152 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
12153 recorded_player_action);
12155 // TapePlayAction() may return NULL when toggling to "pause before death"
12159 if (tape.set_centered_player)
12161 game.centered_player_nr_next = tape.centered_player_nr_next;
12162 game.set_centered_player = TRUE;
12165 for (i = 0; i < MAX_PLAYERS; i++)
12167 summarized_player_action |= stored_player[i].action;
12169 if (!network_playing && (game.team_mode || tape.playing))
12170 stored_player[i].effective_action = stored_player[i].action;
12173 if (network_playing && !checkGameEnded())
12174 SendToServer_MovePlayer(summarized_player_action);
12176 // summarize all actions at local players mapped input device position
12177 // (this allows using different input devices in single player mode)
12178 if (!network.enabled && !game.team_mode)
12179 stored_player[map_player_action[local_player->index_nr]].effective_action =
12180 summarized_player_action;
12182 // summarize all actions at centered player in local team mode
12183 if (tape.recording &&
12184 setup.team_mode && !network.enabled &&
12185 setup.input_on_focus &&
12186 game.centered_player_nr != -1)
12188 for (i = 0; i < MAX_PLAYERS; i++)
12189 stored_player[map_player_action[i]].effective_action =
12190 (i == game.centered_player_nr ? summarized_player_action : 0);
12193 if (recorded_player_action != NULL)
12194 for (i = 0; i < MAX_PLAYERS; i++)
12195 stored_player[i].effective_action = recorded_player_action[i];
12197 for (i = 0; i < MAX_PLAYERS; i++)
12199 tape_action[i] = stored_player[i].effective_action;
12201 /* (this may happen in the RND game engine if a player was not present on
12202 the playfield on level start, but appeared later from a custom element */
12203 if (setup.team_mode &&
12206 !tape.player_participates[i])
12207 tape.player_participates[i] = TRUE;
12210 SetTapeActionFromMouseAction(tape_action,
12211 &local_player->effective_mouse_action);
12213 // only record actions from input devices, but not programmed actions
12214 if (tape.recording)
12215 TapeRecordAction(tape_action);
12217 // remember if game was played (especially after tape stopped playing)
12218 if (!tape.playing && summarized_player_action && !checkGameFailed())
12219 game.GamePlayed = TRUE;
12221 #if USE_NEW_PLAYER_ASSIGNMENTS
12222 // !!! also map player actions in single player mode !!!
12223 // if (game.team_mode)
12226 byte mapped_action[MAX_PLAYERS];
12228 #if DEBUG_PLAYER_ACTIONS
12229 for (i = 0; i < MAX_PLAYERS; i++)
12230 DebugContinued("", "%d, ", stored_player[i].effective_action);
12233 for (i = 0; i < MAX_PLAYERS; i++)
12234 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
12236 for (i = 0; i < MAX_PLAYERS; i++)
12237 stored_player[i].effective_action = mapped_action[i];
12239 #if DEBUG_PLAYER_ACTIONS
12240 DebugContinued("", "=> ");
12241 for (i = 0; i < MAX_PLAYERS; i++)
12242 DebugContinued("", "%d, ", stored_player[i].effective_action);
12243 DebugContinued("game:playing:player", "\n");
12246 #if DEBUG_PLAYER_ACTIONS
12249 for (i = 0; i < MAX_PLAYERS; i++)
12250 DebugContinued("", "%d, ", stored_player[i].effective_action);
12251 DebugContinued("game:playing:player", "\n");
12256 for (i = 0; i < MAX_PLAYERS; i++)
12258 // allow engine snapshot in case of changed movement attempt
12259 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12260 (stored_player[i].effective_action & KEY_MOTION))
12261 game.snapshot.changed_action = TRUE;
12263 // allow engine snapshot in case of snapping/dropping attempt
12264 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12265 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12266 game.snapshot.changed_action = TRUE;
12268 game.snapshot.last_action[i] = stored_player[i].effective_action;
12271 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
12273 GameActions_BD_Main();
12275 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12277 GameActions_EM_Main();
12279 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12281 GameActions_SP_Main();
12283 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12285 GameActions_MM_Main();
12289 GameActions_RND_Main();
12292 BlitScreenToBitmap(backbuffer);
12294 CheckLevelSolved();
12297 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12299 if (global.show_frames_per_second)
12301 static unsigned int fps_counter = 0;
12302 static int fps_frames = 0;
12303 unsigned int fps_delay_ms = Counter() - fps_counter;
12307 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12309 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12312 fps_counter = Counter();
12314 // always draw FPS to screen after FPS value was updated
12315 redraw_mask |= REDRAW_FPS;
12318 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12319 if (GetDrawDeactivationMask() == REDRAW_NONE)
12320 redraw_mask |= REDRAW_FPS;
12324 static void GameActions_CheckSaveEngineSnapshot(void)
12326 if (!game.snapshot.save_snapshot)
12329 // clear flag for saving snapshot _before_ saving snapshot
12330 game.snapshot.save_snapshot = FALSE;
12332 SaveEngineSnapshotToList();
12335 void GameActions(void)
12339 GameActions_CheckSaveEngineSnapshot();
12342 void GameActions_BD_Main(void)
12344 byte effective_action[MAX_PLAYERS];
12347 for (i = 0; i < MAX_PLAYERS; i++)
12348 effective_action[i] = stored_player[i].effective_action;
12350 GameActions_BD(effective_action);
12353 void GameActions_EM_Main(void)
12355 byte effective_action[MAX_PLAYERS];
12358 for (i = 0; i < MAX_PLAYERS; i++)
12359 effective_action[i] = stored_player[i].effective_action;
12361 GameActions_EM(effective_action);
12364 void GameActions_SP_Main(void)
12366 byte effective_action[MAX_PLAYERS];
12369 for (i = 0; i < MAX_PLAYERS; i++)
12370 effective_action[i] = stored_player[i].effective_action;
12372 GameActions_SP(effective_action);
12374 for (i = 0; i < MAX_PLAYERS; i++)
12376 if (stored_player[i].force_dropping)
12377 stored_player[i].action |= KEY_BUTTON_DROP;
12379 stored_player[i].force_dropping = FALSE;
12383 void GameActions_MM_Main(void)
12387 GameActions_MM(local_player->effective_mouse_action);
12390 void GameActions_RND_Main(void)
12395 void GameActions_RND(void)
12397 static struct MouseActionInfo mouse_action_last = { 0 };
12398 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12399 int magic_wall_x = 0, magic_wall_y = 0;
12400 int i, x, y, element, graphic, last_gfx_frame;
12402 InitPlayfieldScanModeVars();
12404 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12406 SCAN_PLAYFIELD(x, y)
12408 ChangeCount[x][y] = 0;
12409 ChangeEvent[x][y] = -1;
12413 if (game.set_centered_player)
12415 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12417 // switching to "all players" only possible if all players fit to screen
12418 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12420 game.centered_player_nr_next = game.centered_player_nr;
12421 game.set_centered_player = FALSE;
12424 // do not switch focus to non-existing (or non-active) player
12425 if (game.centered_player_nr_next >= 0 &&
12426 !stored_player[game.centered_player_nr_next].active)
12428 game.centered_player_nr_next = game.centered_player_nr;
12429 game.set_centered_player = FALSE;
12433 if (game.set_centered_player &&
12434 ScreenMovPos == 0) // screen currently aligned at tile position
12438 if (game.centered_player_nr_next == -1)
12440 setScreenCenteredToAllPlayers(&sx, &sy);
12444 sx = stored_player[game.centered_player_nr_next].jx;
12445 sy = stored_player[game.centered_player_nr_next].jy;
12448 game.centered_player_nr = game.centered_player_nr_next;
12449 game.set_centered_player = FALSE;
12451 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12452 DrawGameDoorValues();
12455 // check single step mode (set flag and clear again if any player is active)
12456 game.enter_single_step_mode =
12457 (tape.single_step && tape.recording && !tape.pausing);
12459 for (i = 0; i < MAX_PLAYERS; i++)
12461 int actual_player_action = stored_player[i].effective_action;
12464 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12465 - rnd_equinox_tetrachloride 048
12466 - rnd_equinox_tetrachloride_ii 096
12467 - rnd_emanuel_schmieg 002
12468 - doctor_sloan_ww 001, 020
12470 if (stored_player[i].MovPos == 0)
12471 CheckGravityMovement(&stored_player[i]);
12474 // overwrite programmed action with tape action
12475 if (stored_player[i].programmed_action)
12476 actual_player_action = stored_player[i].programmed_action;
12478 PlayerActions(&stored_player[i], actual_player_action);
12480 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12483 // single step pause mode may already have been toggled by "ScrollPlayer()"
12484 if (game.enter_single_step_mode && !tape.pausing)
12485 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12487 ScrollScreen(NULL, SCROLL_GO_ON);
12489 /* for backwards compatibility, the following code emulates a fixed bug that
12490 occured when pushing elements (causing elements that just made their last
12491 pushing step to already (if possible) make their first falling step in the
12492 same game frame, which is bad); this code is also needed to use the famous
12493 "spring push bug" which is used in older levels and might be wanted to be
12494 used also in newer levels, but in this case the buggy pushing code is only
12495 affecting the "spring" element and no other elements */
12497 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12499 for (i = 0; i < MAX_PLAYERS; i++)
12501 struct PlayerInfo *player = &stored_player[i];
12502 int x = player->jx;
12503 int y = player->jy;
12505 if (player->active && player->is_pushing && player->is_moving &&
12507 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12508 Tile[x][y] == EL_SPRING))
12510 ContinueMoving(x, y);
12512 // continue moving after pushing (this is actually a bug)
12513 if (!IS_MOVING(x, y))
12514 Stop[x][y] = FALSE;
12519 SCAN_PLAYFIELD(x, y)
12521 Last[x][y] = Tile[x][y];
12523 ChangeCount[x][y] = 0;
12524 ChangeEvent[x][y] = -1;
12526 // this must be handled before main playfield loop
12527 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12530 if (MovDelay[x][y] <= 0)
12534 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12537 if (MovDelay[x][y] <= 0)
12539 int element = Store[x][y];
12540 int move_direction = MovDir[x][y];
12541 int player_index_bit = Store2[x][y];
12547 TEST_DrawLevelField(x, y);
12549 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12551 if (IS_ENVELOPE(element))
12552 local_player->show_envelope = element;
12557 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12559 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12561 Debug("game:playing:GameActions_RND", "This should never happen!");
12563 ChangePage[x][y] = -1;
12567 Stop[x][y] = FALSE;
12568 if (WasJustMoving[x][y] > 0)
12569 WasJustMoving[x][y]--;
12570 if (WasJustFalling[x][y] > 0)
12571 WasJustFalling[x][y]--;
12572 if (CheckCollision[x][y] > 0)
12573 CheckCollision[x][y]--;
12574 if (CheckImpact[x][y] > 0)
12575 CheckImpact[x][y]--;
12579 /* reset finished pushing action (not done in ContinueMoving() to allow
12580 continuous pushing animation for elements with zero push delay) */
12581 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12583 ResetGfxAnimation(x, y);
12584 TEST_DrawLevelField(x, y);
12588 if (IS_BLOCKED(x, y))
12592 Blocked2Moving(x, y, &oldx, &oldy);
12593 if (!IS_MOVING(oldx, oldy))
12595 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12596 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12597 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12598 Debug("game:playing:GameActions_RND", "This should never happen!");
12604 HandleMouseAction(&mouse_action, &mouse_action_last);
12606 SCAN_PLAYFIELD(x, y)
12608 element = Tile[x][y];
12609 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12610 last_gfx_frame = GfxFrame[x][y];
12612 if (element == EL_EMPTY)
12613 graphic = el2img(GfxElementEmpty[x][y]);
12615 ResetGfxFrame(x, y);
12617 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12618 DrawLevelGraphicAnimation(x, y, graphic);
12620 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12621 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12622 ResetRandomAnimationValue(x, y);
12624 SetRandomAnimationValue(x, y);
12626 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12628 if (IS_INACTIVE(element))
12630 if (IS_ANIMATED(graphic))
12631 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12636 // this may take place after moving, so 'element' may have changed
12637 if (IS_CHANGING(x, y) &&
12638 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12640 int page = element_info[element].event_page_nr[CE_DELAY];
12642 HandleElementChange(x, y, page);
12644 element = Tile[x][y];
12645 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12648 CheckNextToConditions(x, y);
12650 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12654 element = Tile[x][y];
12655 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12657 if (IS_ANIMATED(graphic) &&
12658 !IS_MOVING(x, y) &&
12660 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12662 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12663 TEST_DrawTwinkleOnField(x, y);
12665 else if (element == EL_ACID)
12668 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12670 else if ((element == EL_EXIT_OPEN ||
12671 element == EL_EM_EXIT_OPEN ||
12672 element == EL_SP_EXIT_OPEN ||
12673 element == EL_STEEL_EXIT_OPEN ||
12674 element == EL_EM_STEEL_EXIT_OPEN ||
12675 element == EL_SP_TERMINAL ||
12676 element == EL_SP_TERMINAL_ACTIVE ||
12677 element == EL_EXTRA_TIME ||
12678 element == EL_SHIELD_NORMAL ||
12679 element == EL_SHIELD_DEADLY) &&
12680 IS_ANIMATED(graphic))
12681 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12682 else if (IS_MOVING(x, y))
12683 ContinueMoving(x, y);
12684 else if (IS_ACTIVE_BOMB(element))
12685 CheckDynamite(x, y);
12686 else if (element == EL_AMOEBA_GROWING)
12687 AmoebaGrowing(x, y);
12688 else if (element == EL_AMOEBA_SHRINKING)
12689 AmoebaShrinking(x, y);
12691 #if !USE_NEW_AMOEBA_CODE
12692 else if (IS_AMOEBALIVE(element))
12693 AmoebaReproduce(x, y);
12696 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12698 else if (element == EL_EXIT_CLOSED)
12700 else if (element == EL_EM_EXIT_CLOSED)
12702 else if (element == EL_STEEL_EXIT_CLOSED)
12703 CheckExitSteel(x, y);
12704 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12705 CheckExitSteelEM(x, y);
12706 else if (element == EL_SP_EXIT_CLOSED)
12708 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12709 element == EL_EXPANDABLE_STEELWALL_GROWING)
12711 else if (element == EL_EXPANDABLE_WALL ||
12712 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12713 element == EL_EXPANDABLE_WALL_VERTICAL ||
12714 element == EL_EXPANDABLE_WALL_ANY ||
12715 element == EL_BD_EXPANDABLE_WALL ||
12716 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12717 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12718 element == EL_EXPANDABLE_STEELWALL_ANY)
12719 CheckWallGrowing(x, y);
12720 else if (element == EL_FLAMES)
12721 CheckForDragon(x, y);
12722 else if (element == EL_EXPLOSION)
12723 ; // drawing of correct explosion animation is handled separately
12724 else if (element == EL_ELEMENT_SNAPPING ||
12725 element == EL_DIAGONAL_SHRINKING ||
12726 element == EL_DIAGONAL_GROWING)
12728 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]);
12730 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12732 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12733 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12735 if (IS_BELT_ACTIVE(element))
12736 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12738 if (game.magic_wall_active)
12740 int jx = local_player->jx, jy = local_player->jy;
12742 // play the element sound at the position nearest to the player
12743 if ((element == EL_MAGIC_WALL_FULL ||
12744 element == EL_MAGIC_WALL_ACTIVE ||
12745 element == EL_MAGIC_WALL_EMPTYING ||
12746 element == EL_BD_MAGIC_WALL_FULL ||
12747 element == EL_BD_MAGIC_WALL_ACTIVE ||
12748 element == EL_BD_MAGIC_WALL_EMPTYING ||
12749 element == EL_DC_MAGIC_WALL_FULL ||
12750 element == EL_DC_MAGIC_WALL_ACTIVE ||
12751 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12752 ABS(x - jx) + ABS(y - jy) <
12753 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12761 #if USE_NEW_AMOEBA_CODE
12762 // new experimental amoeba growth stuff
12763 if (!(FrameCounter % 8))
12765 static unsigned int random = 1684108901;
12767 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12769 x = RND(lev_fieldx);
12770 y = RND(lev_fieldy);
12771 element = Tile[x][y];
12773 if (!IS_PLAYER(x, y) &&
12774 (element == EL_EMPTY ||
12775 CAN_GROW_INTO(element) ||
12776 element == EL_QUICKSAND_EMPTY ||
12777 element == EL_QUICKSAND_FAST_EMPTY ||
12778 element == EL_ACID_SPLASH_LEFT ||
12779 element == EL_ACID_SPLASH_RIGHT))
12781 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12782 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12783 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12784 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12785 Tile[x][y] = EL_AMOEBA_DROP;
12788 random = random * 129 + 1;
12793 game.explosions_delayed = FALSE;
12795 SCAN_PLAYFIELD(x, y)
12797 element = Tile[x][y];
12799 if (ExplodeField[x][y])
12800 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12801 else if (element == EL_EXPLOSION)
12802 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12804 ExplodeField[x][y] = EX_TYPE_NONE;
12807 game.explosions_delayed = TRUE;
12809 if (game.magic_wall_active)
12811 if (!(game.magic_wall_time_left % 4))
12813 int element = Tile[magic_wall_x][magic_wall_y];
12815 if (element == EL_BD_MAGIC_WALL_FULL ||
12816 element == EL_BD_MAGIC_WALL_ACTIVE ||
12817 element == EL_BD_MAGIC_WALL_EMPTYING)
12818 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12819 else if (element == EL_DC_MAGIC_WALL_FULL ||
12820 element == EL_DC_MAGIC_WALL_ACTIVE ||
12821 element == EL_DC_MAGIC_WALL_EMPTYING)
12822 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12824 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12827 if (game.magic_wall_time_left > 0)
12829 game.magic_wall_time_left--;
12831 if (!game.magic_wall_time_left)
12833 SCAN_PLAYFIELD(x, y)
12835 element = Tile[x][y];
12837 if (element == EL_MAGIC_WALL_ACTIVE ||
12838 element == EL_MAGIC_WALL_FULL)
12840 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12841 TEST_DrawLevelField(x, y);
12843 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12844 element == EL_BD_MAGIC_WALL_FULL)
12846 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12847 TEST_DrawLevelField(x, y);
12849 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12850 element == EL_DC_MAGIC_WALL_FULL)
12852 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12853 TEST_DrawLevelField(x, y);
12857 game.magic_wall_active = FALSE;
12862 if (game.light_time_left > 0)
12864 game.light_time_left--;
12866 if (game.light_time_left == 0)
12867 RedrawAllLightSwitchesAndInvisibleElements();
12870 if (game.timegate_time_left > 0)
12872 game.timegate_time_left--;
12874 if (game.timegate_time_left == 0)
12875 CloseAllOpenTimegates();
12878 if (game.lenses_time_left > 0)
12880 game.lenses_time_left--;
12882 if (game.lenses_time_left == 0)
12883 RedrawAllInvisibleElementsForLenses();
12886 if (game.magnify_time_left > 0)
12888 game.magnify_time_left--;
12890 if (game.magnify_time_left == 0)
12891 RedrawAllInvisibleElementsForMagnifier();
12894 for (i = 0; i < MAX_PLAYERS; i++)
12896 struct PlayerInfo *player = &stored_player[i];
12898 if (SHIELD_ON(player))
12900 if (player->shield_deadly_time_left)
12901 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12902 else if (player->shield_normal_time_left)
12903 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12907 #if USE_DELAYED_GFX_REDRAW
12908 SCAN_PLAYFIELD(x, y)
12910 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12912 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12913 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12915 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12916 DrawLevelField(x, y);
12918 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12919 DrawLevelFieldCrumbled(x, y);
12921 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12922 DrawLevelFieldCrumbledNeighbours(x, y);
12924 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12925 DrawTwinkleOnField(x, y);
12928 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12933 PlayAllPlayersSound();
12935 for (i = 0; i < MAX_PLAYERS; i++)
12937 struct PlayerInfo *player = &stored_player[i];
12939 if (player->show_envelope != 0 && (!player->active ||
12940 player->MovPos == 0))
12942 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12944 player->show_envelope = 0;
12948 // use random number generator in every frame to make it less predictable
12949 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12952 mouse_action_last = mouse_action;
12955 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12957 int min_x = x, min_y = y, max_x = x, max_y = y;
12958 int scr_fieldx = getScreenFieldSizeX();
12959 int scr_fieldy = getScreenFieldSizeY();
12962 for (i = 0; i < MAX_PLAYERS; i++)
12964 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12966 if (!stored_player[i].active || &stored_player[i] == player)
12969 min_x = MIN(min_x, jx);
12970 min_y = MIN(min_y, jy);
12971 max_x = MAX(max_x, jx);
12972 max_y = MAX(max_y, jy);
12975 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12978 static boolean AllPlayersInVisibleScreen(void)
12982 for (i = 0; i < MAX_PLAYERS; i++)
12984 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12986 if (!stored_player[i].active)
12989 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12996 void ScrollLevel(int dx, int dy)
12998 int scroll_offset = 2 * TILEX_VAR;
13001 BlitBitmap(drawto_field, drawto_field,
13002 FX + TILEX_VAR * (dx == -1) - scroll_offset,
13003 FY + TILEY_VAR * (dy == -1) - scroll_offset,
13004 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
13005 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
13006 FX + TILEX_VAR * (dx == 1) - scroll_offset,
13007 FY + TILEY_VAR * (dy == 1) - scroll_offset);
13011 x = (dx == 1 ? BX1 : BX2);
13012 for (y = BY1; y <= BY2; y++)
13013 DrawScreenField(x, y);
13018 y = (dy == 1 ? BY1 : BY2);
13019 for (x = BX1; x <= BX2; x++)
13020 DrawScreenField(x, y);
13023 redraw_mask |= REDRAW_FIELD;
13026 static boolean canFallDown(struct PlayerInfo *player)
13028 int jx = player->jx, jy = player->jy;
13030 return (IN_LEV_FIELD(jx, jy + 1) &&
13031 (IS_FREE(jx, jy + 1) ||
13032 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
13033 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
13034 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
13037 static boolean canPassField(int x, int y, int move_dir)
13039 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13040 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13041 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13042 int nextx = x + dx;
13043 int nexty = y + dy;
13044 int element = Tile[x][y];
13046 return (IS_PASSABLE_FROM(element, opposite_dir) &&
13047 !CAN_MOVE(element) &&
13048 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
13049 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
13050 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
13053 static boolean canMoveToValidFieldWithGravity(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);
13061 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
13062 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
13063 (IS_DIGGABLE(Tile[newx][newy]) ||
13064 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
13065 canPassField(newx, newy, move_dir)));
13068 static void CheckGravityMovement(struct PlayerInfo *player)
13070 if (player->gravity && !player->programmed_action)
13072 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
13073 int move_dir_vertical = player->effective_action & MV_VERTICAL;
13074 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
13075 int jx = player->jx, jy = player->jy;
13076 boolean player_is_moving_to_valid_field =
13077 (!player_is_snapping &&
13078 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
13079 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
13080 boolean player_can_fall_down = canFallDown(player);
13082 if (player_can_fall_down &&
13083 !player_is_moving_to_valid_field)
13084 player->programmed_action = MV_DOWN;
13088 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
13090 return CheckGravityMovement(player);
13092 if (player->gravity && !player->programmed_action)
13094 int jx = player->jx, jy = player->jy;
13095 boolean field_under_player_is_free =
13096 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
13097 boolean player_is_standing_on_valid_field =
13098 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
13099 (IS_WALKABLE(Tile[jx][jy]) &&
13100 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
13102 if (field_under_player_is_free && !player_is_standing_on_valid_field)
13103 player->programmed_action = MV_DOWN;
13108 MovePlayerOneStep()
13109 -----------------------------------------------------------------------------
13110 dx, dy: direction (non-diagonal) to try to move the player to
13111 real_dx, real_dy: direction as read from input device (can be diagonal)
13114 boolean MovePlayerOneStep(struct PlayerInfo *player,
13115 int dx, int dy, int real_dx, int real_dy)
13117 int jx = player->jx, jy = player->jy;
13118 int new_jx = jx + dx, new_jy = jy + dy;
13120 boolean player_can_move = !player->cannot_move;
13122 if (!player->active || (!dx && !dy))
13123 return MP_NO_ACTION;
13125 player->MovDir = (dx < 0 ? MV_LEFT :
13126 dx > 0 ? MV_RIGHT :
13128 dy > 0 ? MV_DOWN : MV_NONE);
13130 if (!IN_LEV_FIELD(new_jx, new_jy))
13131 return MP_NO_ACTION;
13133 if (!player_can_move)
13135 if (player->MovPos == 0)
13137 player->is_moving = FALSE;
13138 player->is_digging = FALSE;
13139 player->is_collecting = FALSE;
13140 player->is_snapping = FALSE;
13141 player->is_pushing = FALSE;
13145 if (!network.enabled && game.centered_player_nr == -1 &&
13146 !AllPlayersInSight(player, new_jx, new_jy))
13147 return MP_NO_ACTION;
13149 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG);
13150 if (can_move != MP_MOVING)
13153 // check if DigField() has caused relocation of the player
13154 if (player->jx != jx || player->jy != jy)
13155 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
13157 StorePlayer[jx][jy] = 0;
13158 player->last_jx = jx;
13159 player->last_jy = jy;
13160 player->jx = new_jx;
13161 player->jy = new_jy;
13162 StorePlayer[new_jx][new_jy] = player->element_nr;
13164 if (player->move_delay_value_next != -1)
13166 player->move_delay_value = player->move_delay_value_next;
13167 player->move_delay_value_next = -1;
13171 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
13173 player->step_counter++;
13175 PlayerVisit[jx][jy] = FrameCounter;
13177 player->is_moving = TRUE;
13180 // should better be called in MovePlayer(), but this breaks some tapes
13181 ScrollPlayer(player, SCROLL_INIT);
13187 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
13189 int jx = player->jx, jy = player->jy;
13190 int old_jx = jx, old_jy = jy;
13191 int moved = MP_NO_ACTION;
13193 if (!player->active)
13198 if (player->MovPos == 0)
13200 player->is_moving = FALSE;
13201 player->is_digging = FALSE;
13202 player->is_collecting = FALSE;
13203 player->is_snapping = FALSE;
13204 player->is_pushing = FALSE;
13210 if (player->move_delay > 0)
13213 player->move_delay = -1; // set to "uninitialized" value
13215 // store if player is automatically moved to next field
13216 player->is_auto_moving = (player->programmed_action != MV_NONE);
13218 // remove the last programmed player action
13219 player->programmed_action = 0;
13221 if (player->MovPos)
13223 // should only happen if pre-1.2 tape recordings are played
13224 // this is only for backward compatibility
13226 int original_move_delay_value = player->move_delay_value;
13229 Debug("game:playing:MovePlayer",
13230 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13234 // scroll remaining steps with finest movement resolution
13235 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13237 while (player->MovPos)
13239 ScrollPlayer(player, SCROLL_GO_ON);
13240 ScrollScreen(NULL, SCROLL_GO_ON);
13242 AdvanceFrameAndPlayerCounters(player->index_nr);
13245 BackToFront_WithFrameDelay(0);
13248 player->move_delay_value = original_move_delay_value;
13251 player->is_active = FALSE;
13253 if (player->last_move_dir & MV_HORIZONTAL)
13255 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13256 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13260 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13261 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13264 if (!moved && !player->is_active)
13266 player->is_moving = FALSE;
13267 player->is_digging = FALSE;
13268 player->is_collecting = FALSE;
13269 player->is_snapping = FALSE;
13270 player->is_pushing = FALSE;
13276 if (moved & MP_MOVING && !ScreenMovPos &&
13277 (player->index_nr == game.centered_player_nr ||
13278 game.centered_player_nr == -1))
13280 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13282 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13284 // actual player has left the screen -- scroll in that direction
13285 if (jx != old_jx) // player has moved horizontally
13286 scroll_x += (jx - old_jx);
13287 else // player has moved vertically
13288 scroll_y += (jy - old_jy);
13292 int offset_raw = game.scroll_delay_value;
13294 if (jx != old_jx) // player has moved horizontally
13296 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13297 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13298 int new_scroll_x = jx - MIDPOSX + offset_x;
13300 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13301 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13302 scroll_x = new_scroll_x;
13304 // don't scroll over playfield boundaries
13305 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13307 // don't scroll more than one field at a time
13308 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13310 // don't scroll against the player's moving direction
13311 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13312 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13313 scroll_x = old_scroll_x;
13315 else // player has moved vertically
13317 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13318 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13319 int new_scroll_y = jy - MIDPOSY + offset_y;
13321 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13322 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13323 scroll_y = new_scroll_y;
13325 // don't scroll over playfield boundaries
13326 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13328 // don't scroll more than one field at a time
13329 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13331 // don't scroll against the player's moving direction
13332 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13333 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13334 scroll_y = old_scroll_y;
13338 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13340 if (!network.enabled && game.centered_player_nr == -1 &&
13341 !AllPlayersInVisibleScreen())
13343 scroll_x = old_scroll_x;
13344 scroll_y = old_scroll_y;
13348 ScrollScreen(player, SCROLL_INIT);
13349 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13354 player->StepFrame = 0;
13356 if (moved & MP_MOVING)
13358 if (old_jx != jx && old_jy == jy)
13359 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13360 else if (old_jx == jx && old_jy != jy)
13361 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13363 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13365 player->last_move_dir = player->MovDir;
13366 player->is_moving = TRUE;
13367 player->is_snapping = FALSE;
13368 player->is_switching = FALSE;
13369 player->is_dropping = FALSE;
13370 player->is_dropping_pressed = FALSE;
13371 player->drop_pressed_delay = 0;
13374 // should better be called here than above, but this breaks some tapes
13375 ScrollPlayer(player, SCROLL_INIT);
13380 CheckGravityMovementWhenNotMoving(player);
13382 player->is_moving = FALSE;
13384 /* at this point, the player is allowed to move, but cannot move right now
13385 (e.g. because of something blocking the way) -- ensure that the player
13386 is also allowed to move in the next frame (in old versions before 3.1.1,
13387 the player was forced to wait again for eight frames before next try) */
13389 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13390 player->move_delay = 0; // allow direct movement in the next frame
13393 if (player->move_delay == -1) // not yet initialized by DigField()
13394 player->move_delay = player->move_delay_value;
13396 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13398 TestIfPlayerTouchesBadThing(jx, jy);
13399 TestIfPlayerTouchesCustomElement(jx, jy);
13402 if (!player->active)
13403 RemovePlayer(player);
13408 void ScrollPlayer(struct PlayerInfo *player, int mode)
13410 int jx = player->jx, jy = player->jy;
13411 int last_jx = player->last_jx, last_jy = player->last_jy;
13412 int move_stepsize = TILEX / player->move_delay_value;
13414 if (!player->active)
13417 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13420 if (mode == SCROLL_INIT)
13422 player->actual_frame_counter.count = FrameCounter;
13423 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13425 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13426 Tile[last_jx][last_jy] == EL_EMPTY)
13428 int last_field_block_delay = 0; // start with no blocking at all
13429 int block_delay_adjustment = player->block_delay_adjustment;
13431 // if player blocks last field, add delay for exactly one move
13432 if (player->block_last_field)
13434 last_field_block_delay += player->move_delay_value;
13436 // when blocking enabled, prevent moving up despite gravity
13437 if (player->gravity && player->MovDir == MV_UP)
13438 block_delay_adjustment = -1;
13441 // add block delay adjustment (also possible when not blocking)
13442 last_field_block_delay += block_delay_adjustment;
13444 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13445 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13448 if (player->MovPos != 0) // player has not yet reached destination
13451 else if (!FrameReached(&player->actual_frame_counter))
13454 if (player->MovPos != 0)
13456 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13457 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13459 // before DrawPlayer() to draw correct player graphic for this case
13460 if (player->MovPos == 0)
13461 CheckGravityMovement(player);
13464 if (player->MovPos == 0) // player reached destination field
13466 if (player->move_delay_reset_counter > 0)
13468 player->move_delay_reset_counter--;
13470 if (player->move_delay_reset_counter == 0)
13472 // continue with normal speed after quickly moving through gate
13473 HALVE_PLAYER_SPEED(player);
13475 // be able to make the next move without delay
13476 player->move_delay = 0;
13480 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13481 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13482 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13483 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13484 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13485 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13486 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13487 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13489 ExitPlayer(player);
13491 if (game.players_still_needed == 0 &&
13492 (game.friends_still_needed == 0 ||
13493 IS_SP_ELEMENT(Tile[jx][jy])))
13497 player->last_jx = jx;
13498 player->last_jy = jy;
13500 // this breaks one level: "machine", level 000
13502 int move_direction = player->MovDir;
13503 int enter_side = MV_DIR_OPPOSITE(move_direction);
13504 int leave_side = move_direction;
13505 int old_jx = last_jx;
13506 int old_jy = last_jy;
13507 int old_element = Tile[old_jx][old_jy];
13508 int new_element = Tile[jx][jy];
13510 if (IS_CUSTOM_ELEMENT(old_element))
13511 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13513 player->index_bit, leave_side);
13515 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13516 CE_PLAYER_LEAVES_X,
13517 player->index_bit, leave_side);
13519 // needed because pushed element has not yet reached its destination,
13520 // so it would trigger a change event at its previous field location
13521 if (!player->is_pushing)
13523 if (IS_CUSTOM_ELEMENT(new_element))
13524 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13525 player->index_bit, enter_side);
13527 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13528 CE_PLAYER_ENTERS_X,
13529 player->index_bit, enter_side);
13532 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13533 CE_MOVE_OF_X, move_direction);
13536 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13538 TestIfPlayerTouchesBadThing(jx, jy);
13539 TestIfPlayerTouchesCustomElement(jx, jy);
13541 // needed because pushed element has not yet reached its destination,
13542 // so it would trigger a change event at its previous field location
13543 if (!player->is_pushing)
13544 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13546 if (level.finish_dig_collect &&
13547 (player->is_digging || player->is_collecting))
13549 int last_element = player->last_removed_element;
13550 int move_direction = player->MovDir;
13551 int enter_side = MV_DIR_OPPOSITE(move_direction);
13552 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13553 CE_PLAYER_COLLECTS_X);
13555 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13556 player->index_bit, enter_side);
13558 player->last_removed_element = EL_UNDEFINED;
13561 if (!player->active)
13562 RemovePlayer(player);
13565 if (level.use_step_counter)
13566 CheckLevelTime_StepCounter();
13568 if (tape.single_step && tape.recording && !tape.pausing &&
13569 !player->programmed_action)
13570 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13572 if (!player->programmed_action)
13573 CheckSaveEngineSnapshot(player);
13577 void ScrollScreen(struct PlayerInfo *player, int mode)
13579 static DelayCounter screen_frame_counter = { 0 };
13581 if (mode == SCROLL_INIT)
13583 // set scrolling step size according to actual player's moving speed
13584 ScrollStepSize = TILEX / player->move_delay_value;
13586 screen_frame_counter.count = FrameCounter;
13587 screen_frame_counter.value = 1;
13589 ScreenMovDir = player->MovDir;
13590 ScreenMovPos = player->MovPos;
13591 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13594 else if (!FrameReached(&screen_frame_counter))
13599 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13600 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13601 redraw_mask |= REDRAW_FIELD;
13604 ScreenMovDir = MV_NONE;
13607 void CheckNextToConditions(int x, int y)
13609 int element = Tile[x][y];
13611 if (IS_PLAYER(x, y))
13612 TestIfPlayerNextToCustomElement(x, y);
13614 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13615 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13616 TestIfElementNextToCustomElement(x, y);
13619 void TestIfPlayerNextToCustomElement(int x, int y)
13621 struct XY *xy = xy_topdown;
13622 static int trigger_sides[4][2] =
13624 // center side border side
13625 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13626 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13627 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13628 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13632 if (!IS_PLAYER(x, y))
13635 struct PlayerInfo *player = PLAYERINFO(x, y);
13637 if (player->is_moving)
13640 for (i = 0; i < NUM_DIRECTIONS; i++)
13642 int xx = x + xy[i].x;
13643 int yy = y + xy[i].y;
13644 int border_side = trigger_sides[i][1];
13645 int border_element;
13647 if (!IN_LEV_FIELD(xx, yy))
13650 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13651 continue; // center and border element not connected
13653 border_element = Tile[xx][yy];
13655 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13656 player->index_bit, border_side);
13657 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13658 CE_PLAYER_NEXT_TO_X,
13659 player->index_bit, border_side);
13661 /* use player element that is initially defined in the level playfield,
13662 not the player element that corresponds to the runtime player number
13663 (example: a level that contains EL_PLAYER_3 as the only player would
13664 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13666 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13667 CE_NEXT_TO_X, border_side);
13671 void TestIfPlayerTouchesCustomElement(int x, int y)
13673 struct XY *xy = xy_topdown;
13674 static int trigger_sides[4][2] =
13676 // center side border side
13677 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13678 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13679 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13680 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13682 static int touch_dir[4] =
13684 MV_LEFT | MV_RIGHT,
13689 int center_element = Tile[x][y]; // should always be non-moving!
13692 for (i = 0; i < NUM_DIRECTIONS; i++)
13694 int xx = x + xy[i].x;
13695 int yy = y + xy[i].y;
13696 int center_side = trigger_sides[i][0];
13697 int border_side = trigger_sides[i][1];
13698 int border_element;
13700 if (!IN_LEV_FIELD(xx, yy))
13703 if (IS_PLAYER(x, y)) // player found at center element
13705 struct PlayerInfo *player = PLAYERINFO(x, y);
13707 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13708 border_element = Tile[xx][yy]; // may be moving!
13709 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13710 border_element = Tile[xx][yy];
13711 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13712 border_element = MovingOrBlocked2Element(xx, yy);
13714 continue; // center and border element do not touch
13716 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13717 player->index_bit, border_side);
13718 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13719 CE_PLAYER_TOUCHES_X,
13720 player->index_bit, border_side);
13723 /* use player element that is initially defined in the level playfield,
13724 not the player element that corresponds to the runtime player number
13725 (example: a level that contains EL_PLAYER_3 as the only player would
13726 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13727 int player_element = PLAYERINFO(x, y)->initial_element;
13729 // as element "X" is the player here, check opposite (center) side
13730 CheckElementChangeBySide(xx, yy, border_element, player_element,
13731 CE_TOUCHING_X, center_side);
13734 else if (IS_PLAYER(xx, yy)) // player found at border element
13736 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13738 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13740 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13741 continue; // center and border element do not touch
13744 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13745 player->index_bit, center_side);
13746 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13747 CE_PLAYER_TOUCHES_X,
13748 player->index_bit, center_side);
13751 /* use player element that is initially defined in the level playfield,
13752 not the player element that corresponds to the runtime player number
13753 (example: a level that contains EL_PLAYER_3 as the only player would
13754 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13755 int player_element = PLAYERINFO(xx, yy)->initial_element;
13757 // as element "X" is the player here, check opposite (border) side
13758 CheckElementChangeBySide(x, y, center_element, player_element,
13759 CE_TOUCHING_X, border_side);
13767 void TestIfElementNextToCustomElement(int x, int y)
13769 struct XY *xy = xy_topdown;
13770 static int trigger_sides[4][2] =
13772 // center side border side
13773 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13774 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13775 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13776 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13778 int center_element = Tile[x][y]; // should always be non-moving!
13781 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13784 for (i = 0; i < NUM_DIRECTIONS; i++)
13786 int xx = x + xy[i].x;
13787 int yy = y + xy[i].y;
13788 int border_side = trigger_sides[i][1];
13789 int border_element;
13791 if (!IN_LEV_FIELD(xx, yy))
13794 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13795 continue; // center and border element not connected
13797 border_element = Tile[xx][yy];
13799 // check for change of center element (but change it only once)
13800 if (CheckElementChangeBySide(x, y, center_element, border_element,
13801 CE_NEXT_TO_X, border_side))
13806 void TestIfElementTouchesCustomElement(int x, int y)
13808 struct XY *xy = xy_topdown;
13809 static int trigger_sides[4][2] =
13811 // center side border side
13812 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13813 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13814 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13815 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13817 static int touch_dir[4] =
13819 MV_LEFT | MV_RIGHT,
13824 boolean change_center_element = FALSE;
13825 int center_element = Tile[x][y]; // should always be non-moving!
13826 int border_element_old[NUM_DIRECTIONS];
13829 for (i = 0; i < NUM_DIRECTIONS; i++)
13831 int xx = x + xy[i].x;
13832 int yy = y + xy[i].y;
13833 int border_element;
13835 border_element_old[i] = -1;
13837 if (!IN_LEV_FIELD(xx, yy))
13840 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13841 border_element = Tile[xx][yy]; // may be moving!
13842 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13843 border_element = Tile[xx][yy];
13844 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13845 border_element = MovingOrBlocked2Element(xx, yy);
13847 continue; // center and border element do not touch
13849 border_element_old[i] = border_element;
13852 for (i = 0; i < NUM_DIRECTIONS; i++)
13854 int xx = x + xy[i].x;
13855 int yy = y + xy[i].y;
13856 int center_side = trigger_sides[i][0];
13857 int border_element = border_element_old[i];
13859 if (border_element == -1)
13862 // check for change of border element
13863 CheckElementChangeBySide(xx, yy, border_element, center_element,
13864 CE_TOUCHING_X, center_side);
13866 // (center element cannot be player, so we don't have to check this here)
13869 for (i = 0; i < NUM_DIRECTIONS; i++)
13871 int xx = x + xy[i].x;
13872 int yy = y + xy[i].y;
13873 int border_side = trigger_sides[i][1];
13874 int border_element = border_element_old[i];
13876 if (border_element == -1)
13879 // check for change of center element (but change it only once)
13880 if (!change_center_element)
13881 change_center_element =
13882 CheckElementChangeBySide(x, y, center_element, border_element,
13883 CE_TOUCHING_X, border_side);
13885 if (IS_PLAYER(xx, yy))
13887 /* use player element that is initially defined in the level playfield,
13888 not the player element that corresponds to the runtime player number
13889 (example: a level that contains EL_PLAYER_3 as the only player would
13890 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13891 int player_element = PLAYERINFO(xx, yy)->initial_element;
13893 // as element "X" is the player here, check opposite (border) side
13894 CheckElementChangeBySide(x, y, center_element, player_element,
13895 CE_TOUCHING_X, border_side);
13900 void TestIfElementHitsCustomElement(int x, int y, int direction)
13902 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13903 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13904 int hitx = x + dx, hity = y + dy;
13905 int hitting_element = Tile[x][y];
13906 int touched_element;
13908 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13911 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13912 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13914 if (IN_LEV_FIELD(hitx, hity))
13916 int opposite_direction = MV_DIR_OPPOSITE(direction);
13917 int hitting_side = direction;
13918 int touched_side = opposite_direction;
13919 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13920 MovDir[hitx][hity] != direction ||
13921 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13927 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13928 CE_HITTING_X, touched_side);
13930 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13931 CE_HIT_BY_X, hitting_side);
13933 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13934 CE_HIT_BY_SOMETHING, opposite_direction);
13936 if (IS_PLAYER(hitx, hity))
13938 /* use player element that is initially defined in the level playfield,
13939 not the player element that corresponds to the runtime player number
13940 (example: a level that contains EL_PLAYER_3 as the only player would
13941 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13942 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13944 CheckElementChangeBySide(x, y, hitting_element, player_element,
13945 CE_HITTING_X, touched_side);
13950 // "hitting something" is also true when hitting the playfield border
13951 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13952 CE_HITTING_SOMETHING, direction);
13955 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13957 int i, kill_x = -1, kill_y = -1;
13959 int bad_element = -1;
13960 struct XY *test_xy = xy_topdown;
13961 static int test_dir[4] =
13969 for (i = 0; i < NUM_DIRECTIONS; i++)
13971 int test_x, test_y, test_move_dir, test_element;
13973 test_x = good_x + test_xy[i].x;
13974 test_y = good_y + test_xy[i].y;
13976 if (!IN_LEV_FIELD(test_x, test_y))
13980 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13982 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13984 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13985 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13987 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13988 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13992 bad_element = test_element;
13998 if (kill_x != -1 || kill_y != -1)
14000 if (IS_PLAYER(good_x, good_y))
14002 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
14004 if (player->shield_deadly_time_left > 0 &&
14005 !IS_INDESTRUCTIBLE(bad_element))
14006 Bang(kill_x, kill_y);
14007 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
14008 KillPlayer(player);
14011 Bang(good_x, good_y);
14015 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
14017 int i, kill_x = -1, kill_y = -1;
14018 int bad_element = Tile[bad_x][bad_y];
14019 struct XY *test_xy = xy_topdown;
14020 static int touch_dir[4] =
14022 MV_LEFT | MV_RIGHT,
14027 static int test_dir[4] =
14035 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
14038 for (i = 0; i < NUM_DIRECTIONS; i++)
14040 int test_x, test_y, test_move_dir, test_element;
14042 test_x = bad_x + test_xy[i].x;
14043 test_y = bad_y + test_xy[i].y;
14045 if (!IN_LEV_FIELD(test_x, test_y))
14049 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14051 test_element = Tile[test_x][test_y];
14053 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
14054 2nd case: DONT_TOUCH style bad thing does not move away from good thing
14056 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
14057 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
14059 // good thing is player or penguin that does not move away
14060 if (IS_PLAYER(test_x, test_y))
14062 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14064 if (bad_element == EL_ROBOT && player->is_moving)
14065 continue; // robot does not kill player if he is moving
14067 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
14069 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
14070 continue; // center and border element do not touch
14078 else if (test_element == EL_PENGUIN)
14088 if (kill_x != -1 || kill_y != -1)
14090 if (IS_PLAYER(kill_x, kill_y))
14092 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14094 if (player->shield_deadly_time_left > 0 &&
14095 !IS_INDESTRUCTIBLE(bad_element))
14096 Bang(bad_x, bad_y);
14097 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14098 KillPlayer(player);
14101 Bang(kill_x, kill_y);
14105 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
14107 int bad_element = Tile[bad_x][bad_y];
14108 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
14109 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
14110 int test_x = bad_x + dx, test_y = bad_y + dy;
14111 int test_move_dir, test_element;
14112 int kill_x = -1, kill_y = -1;
14114 if (!IN_LEV_FIELD(test_x, test_y))
14118 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14120 test_element = Tile[test_x][test_y];
14122 if (test_move_dir != bad_move_dir)
14124 // good thing can be player or penguin that does not move away
14125 if (IS_PLAYER(test_x, test_y))
14127 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14129 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
14130 player as being hit when he is moving towards the bad thing, because
14131 the "get hit by" condition would be lost after the player stops) */
14132 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
14133 return; // player moves away from bad thing
14138 else if (test_element == EL_PENGUIN)
14145 if (kill_x != -1 || kill_y != -1)
14147 if (IS_PLAYER(kill_x, kill_y))
14149 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14151 if (player->shield_deadly_time_left > 0 &&
14152 !IS_INDESTRUCTIBLE(bad_element))
14153 Bang(bad_x, bad_y);
14154 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14155 KillPlayer(player);
14158 Bang(kill_x, kill_y);
14162 void TestIfPlayerTouchesBadThing(int x, int y)
14164 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14167 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
14169 TestIfGoodThingHitsBadThing(x, y, move_dir);
14172 void TestIfBadThingTouchesPlayer(int x, int y)
14174 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14177 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
14179 TestIfBadThingHitsGoodThing(x, y, move_dir);
14182 void TestIfFriendTouchesBadThing(int x, int y)
14184 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14187 void TestIfBadThingTouchesFriend(int x, int y)
14189 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14192 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
14194 int i, kill_x = bad_x, kill_y = bad_y;
14195 struct XY *xy = xy_topdown;
14197 for (i = 0; i < NUM_DIRECTIONS; i++)
14201 x = bad_x + xy[i].x;
14202 y = bad_y + xy[i].y;
14203 if (!IN_LEV_FIELD(x, y))
14206 element = Tile[x][y];
14207 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14208 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14216 if (kill_x != bad_x || kill_y != bad_y)
14217 Bang(bad_x, bad_y);
14220 void KillPlayer(struct PlayerInfo *player)
14222 int jx = player->jx, jy = player->jy;
14224 if (!player->active)
14228 Debug("game:playing:KillPlayer",
14229 "0: killed == %d, active == %d, reanimated == %d",
14230 player->killed, player->active, player->reanimated);
14233 /* the following code was introduced to prevent an infinite loop when calling
14235 -> CheckTriggeredElementChangeExt()
14236 -> ExecuteCustomElementAction()
14238 -> (infinitely repeating the above sequence of function calls)
14239 which occurs when killing the player while having a CE with the setting
14240 "kill player X when explosion of <player X>"; the solution using a new
14241 field "player->killed" was chosen for backwards compatibility, although
14242 clever use of the fields "player->active" etc. would probably also work */
14244 if (player->killed)
14248 player->killed = TRUE;
14250 // remove accessible field at the player's position
14251 RemoveField(jx, jy);
14253 // deactivate shield (else Bang()/Explode() would not work right)
14254 player->shield_normal_time_left = 0;
14255 player->shield_deadly_time_left = 0;
14258 Debug("game:playing:KillPlayer",
14259 "1: killed == %d, active == %d, reanimated == %d",
14260 player->killed, player->active, player->reanimated);
14266 Debug("game:playing:KillPlayer",
14267 "2: killed == %d, active == %d, reanimated == %d",
14268 player->killed, player->active, player->reanimated);
14271 if (player->reanimated) // killed player may have been reanimated
14272 player->killed = player->reanimated = FALSE;
14274 BuryPlayer(player);
14277 static void KillPlayerUnlessEnemyProtected(int x, int y)
14279 if (!PLAYER_ENEMY_PROTECTED(x, y))
14280 KillPlayer(PLAYERINFO(x, y));
14283 static void KillPlayerUnlessExplosionProtected(int x, int y)
14285 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14286 KillPlayer(PLAYERINFO(x, y));
14289 void BuryPlayer(struct PlayerInfo *player)
14291 int jx = player->jx, jy = player->jy;
14293 if (!player->active)
14296 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14298 RemovePlayer(player);
14300 player->buried = TRUE;
14302 if (game.all_players_gone)
14303 game.GameOver = TRUE;
14306 void RemovePlayer(struct PlayerInfo *player)
14308 int jx = player->jx, jy = player->jy;
14309 int i, found = FALSE;
14311 player->present = FALSE;
14312 player->active = FALSE;
14314 // required for some CE actions (even if the player is not active anymore)
14315 player->MovPos = 0;
14317 if (!ExplodeField[jx][jy])
14318 StorePlayer[jx][jy] = 0;
14320 if (player->is_moving)
14321 TEST_DrawLevelField(player->last_jx, player->last_jy);
14323 for (i = 0; i < MAX_PLAYERS; i++)
14324 if (stored_player[i].active)
14329 game.all_players_gone = TRUE;
14330 game.GameOver = TRUE;
14333 game.exit_x = game.robot_wheel_x = jx;
14334 game.exit_y = game.robot_wheel_y = jy;
14337 void ExitPlayer(struct PlayerInfo *player)
14339 DrawPlayer(player); // needed here only to cleanup last field
14340 RemovePlayer(player);
14342 if (game.players_still_needed > 0)
14343 game.players_still_needed--;
14346 static void SetFieldForSnapping(int x, int y, int element, int direction,
14347 int player_index_bit)
14349 struct ElementInfo *ei = &element_info[element];
14350 int direction_bit = MV_DIR_TO_BIT(direction);
14351 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14352 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14353 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14355 Tile[x][y] = EL_ELEMENT_SNAPPING;
14356 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14357 MovDir[x][y] = direction;
14358 Store[x][y] = element;
14359 Store2[x][y] = player_index_bit;
14361 ResetGfxAnimation(x, y);
14363 GfxElement[x][y] = element;
14364 GfxAction[x][y] = action;
14365 GfxDir[x][y] = direction;
14366 GfxFrame[x][y] = -1;
14369 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14370 int player_index_bit)
14372 TestIfElementTouchesCustomElement(x, y); // for empty space
14374 if (level.finish_dig_collect)
14376 int dig_side = MV_DIR_OPPOSITE(direction);
14377 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14378 CE_PLAYER_COLLECTS_X);
14380 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14381 player_index_bit, dig_side);
14382 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14383 player_index_bit, dig_side);
14388 =============================================================================
14389 checkDiagonalPushing()
14390 -----------------------------------------------------------------------------
14391 check if diagonal input device direction results in pushing of object
14392 (by checking if the alternative direction is walkable, diggable, ...)
14393 =============================================================================
14396 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14397 int x, int y, int real_dx, int real_dy)
14399 int jx, jy, dx, dy, xx, yy;
14401 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14404 // diagonal direction: check alternative direction
14409 xx = jx + (dx == 0 ? real_dx : 0);
14410 yy = jy + (dy == 0 ? real_dy : 0);
14412 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14416 =============================================================================
14418 -----------------------------------------------------------------------------
14419 x, y: field next to player (non-diagonal) to try to dig to
14420 real_dx, real_dy: direction as read from input device (can be diagonal)
14421 =============================================================================
14424 static int DigField(struct PlayerInfo *player,
14425 int oldx, int oldy, int x, int y,
14426 int real_dx, int real_dy, int mode)
14428 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14429 boolean player_was_pushing = player->is_pushing;
14430 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14431 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14432 int jx = oldx, jy = oldy;
14433 int dx = x - jx, dy = y - jy;
14434 int nextx = x + dx, nexty = y + dy;
14435 int move_direction = (dx == -1 ? MV_LEFT :
14436 dx == +1 ? MV_RIGHT :
14438 dy == +1 ? MV_DOWN : MV_NONE);
14439 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14440 int dig_side = MV_DIR_OPPOSITE(move_direction);
14441 int old_element = Tile[jx][jy];
14442 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14445 if (is_player) // function can also be called by EL_PENGUIN
14447 if (player->MovPos == 0)
14449 player->is_digging = FALSE;
14450 player->is_collecting = FALSE;
14453 if (player->MovPos == 0) // last pushing move finished
14454 player->is_pushing = FALSE;
14456 if (mode == DF_NO_PUSH) // player just stopped pushing
14458 player->is_switching = FALSE;
14459 player->push_delay = -1;
14461 return MP_NO_ACTION;
14464 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14465 old_element = Back[jx][jy];
14467 // in case of element dropped at player position, check background
14468 else if (Back[jx][jy] != EL_EMPTY &&
14469 game.engine_version >= VERSION_IDENT(2,2,0,0))
14470 old_element = Back[jx][jy];
14472 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14473 return MP_NO_ACTION; // field has no opening in this direction
14475 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction))
14476 return MP_NO_ACTION; // field has no opening in this direction
14478 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14482 Tile[jx][jy] = player->artwork_element;
14483 InitMovingField(jx, jy, MV_DOWN);
14484 Store[jx][jy] = EL_ACID;
14485 ContinueMoving(jx, jy);
14486 BuryPlayer(player);
14488 return MP_DONT_RUN_INTO;
14491 if (player_can_move && DONT_RUN_INTO(element))
14493 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14495 return MP_DONT_RUN_INTO;
14498 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14499 return MP_NO_ACTION;
14501 collect_count = element_info[element].collect_count_initial;
14503 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14504 return MP_NO_ACTION;
14506 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14507 player_can_move = player_can_move_or_snap;
14509 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14510 game.engine_version >= VERSION_IDENT(2,2,0,0))
14512 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14513 player->index_bit, dig_side);
14514 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14515 player->index_bit, dig_side);
14517 if (element == EL_DC_LANDMINE)
14520 if (Tile[x][y] != element) // field changed by snapping
14523 return MP_NO_ACTION;
14526 if (player->gravity && is_player && !player->is_auto_moving &&
14527 canFallDown(player) && move_direction != MV_DOWN &&
14528 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14529 return MP_NO_ACTION; // player cannot walk here due to gravity
14531 if (player_can_move &&
14532 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14534 int sound_element = SND_ELEMENT(element);
14535 int sound_action = ACTION_WALKING;
14537 if (IS_RND_GATE(element))
14539 if (!player->key[RND_GATE_NR(element)])
14540 return MP_NO_ACTION;
14542 else if (IS_RND_GATE_GRAY(element))
14544 if (!player->key[RND_GATE_GRAY_NR(element)])
14545 return MP_NO_ACTION;
14547 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14549 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14550 return MP_NO_ACTION;
14552 else if (element == EL_EXIT_OPEN ||
14553 element == EL_EM_EXIT_OPEN ||
14554 element == EL_EM_EXIT_OPENING ||
14555 element == EL_STEEL_EXIT_OPEN ||
14556 element == EL_EM_STEEL_EXIT_OPEN ||
14557 element == EL_EM_STEEL_EXIT_OPENING ||
14558 element == EL_SP_EXIT_OPEN ||
14559 element == EL_SP_EXIT_OPENING)
14561 sound_action = ACTION_PASSING; // player is passing exit
14563 else if (element == EL_EMPTY)
14565 sound_action = ACTION_MOVING; // nothing to walk on
14568 // play sound from background or player, whatever is available
14569 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14570 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14572 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14574 else if (player_can_move &&
14575 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14577 if (!ACCESS_FROM(element, opposite_direction))
14578 return MP_NO_ACTION; // field not accessible from this direction
14580 if (CAN_MOVE(element)) // only fixed elements can be passed!
14581 return MP_NO_ACTION;
14583 if (IS_EM_GATE(element))
14585 if (!player->key[EM_GATE_NR(element)])
14586 return MP_NO_ACTION;
14588 else if (IS_EM_GATE_GRAY(element))
14590 if (!player->key[EM_GATE_GRAY_NR(element)])
14591 return MP_NO_ACTION;
14593 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14595 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14596 return MP_NO_ACTION;
14598 else if (IS_EMC_GATE(element))
14600 if (!player->key[EMC_GATE_NR(element)])
14601 return MP_NO_ACTION;
14603 else if (IS_EMC_GATE_GRAY(element))
14605 if (!player->key[EMC_GATE_GRAY_NR(element)])
14606 return MP_NO_ACTION;
14608 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14610 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14611 return MP_NO_ACTION;
14613 else if (element == EL_DC_GATE_WHITE ||
14614 element == EL_DC_GATE_WHITE_GRAY ||
14615 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14617 if (player->num_white_keys == 0)
14618 return MP_NO_ACTION;
14620 player->num_white_keys--;
14622 else if (IS_SP_PORT(element))
14624 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14625 element == EL_SP_GRAVITY_PORT_RIGHT ||
14626 element == EL_SP_GRAVITY_PORT_UP ||
14627 element == EL_SP_GRAVITY_PORT_DOWN)
14628 player->gravity = !player->gravity;
14629 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14630 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14631 element == EL_SP_GRAVITY_ON_PORT_UP ||
14632 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14633 player->gravity = TRUE;
14634 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14635 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14636 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14637 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14638 player->gravity = FALSE;
14641 // automatically move to the next field with double speed
14642 player->programmed_action = move_direction;
14644 if (player->move_delay_reset_counter == 0)
14646 player->move_delay_reset_counter = 2; // two double speed steps
14648 DOUBLE_PLAYER_SPEED(player);
14651 PlayLevelSoundAction(x, y, ACTION_PASSING);
14653 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14657 if (mode != DF_SNAP)
14659 GfxElement[x][y] = GFX_ELEMENT(element);
14660 player->is_digging = TRUE;
14663 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14665 // use old behaviour for old levels (digging)
14666 if (!level.finish_dig_collect)
14668 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14669 player->index_bit, dig_side);
14671 // if digging triggered player relocation, finish digging tile
14672 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14673 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14676 if (mode == DF_SNAP)
14678 if (level.block_snap_field)
14679 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14681 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14683 // use old behaviour for old levels (snapping)
14684 if (!level.finish_dig_collect)
14685 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14686 player->index_bit, dig_side);
14689 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14693 if (is_player && mode != DF_SNAP)
14695 GfxElement[x][y] = element;
14696 player->is_collecting = TRUE;
14699 if (element == EL_SPEED_PILL)
14701 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14703 else if (element == EL_EXTRA_TIME && level.time > 0)
14705 TimeLeft += level.extra_time;
14707 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14709 DisplayGameControlValues();
14711 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14713 int shield_time = (element == EL_SHIELD_DEADLY ?
14714 level.shield_deadly_time :
14715 level.shield_normal_time);
14717 player->shield_normal_time_left += shield_time;
14718 if (element == EL_SHIELD_DEADLY)
14719 player->shield_deadly_time_left += shield_time;
14721 else if (element == EL_DYNAMITE ||
14722 element == EL_EM_DYNAMITE ||
14723 element == EL_SP_DISK_RED)
14725 if (player->inventory_size < MAX_INVENTORY_SIZE)
14726 player->inventory_element[player->inventory_size++] = element;
14728 DrawGameDoorValues();
14730 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14732 player->dynabomb_count++;
14733 player->dynabombs_left++;
14735 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14737 player->dynabomb_size++;
14739 else if (element == EL_DYNABOMB_INCREASE_POWER)
14741 player->dynabomb_xl = TRUE;
14743 else if (IS_KEY(element))
14745 player->key[KEY_NR(element)] = TRUE;
14747 DrawGameDoorValues();
14749 else if (element == EL_DC_KEY_WHITE)
14751 player->num_white_keys++;
14753 // display white keys?
14754 // DrawGameDoorValues();
14756 else if (IS_ENVELOPE(element))
14758 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14760 if (!wait_for_snapping)
14761 player->show_envelope = element;
14763 else if (element == EL_EMC_LENSES)
14765 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14767 RedrawAllInvisibleElementsForLenses();
14769 else if (element == EL_EMC_MAGNIFIER)
14771 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14773 RedrawAllInvisibleElementsForMagnifier();
14775 else if (IS_DROPPABLE(element) ||
14776 IS_THROWABLE(element)) // can be collected and dropped
14780 if (collect_count == 0)
14781 player->inventory_infinite_element = element;
14783 for (i = 0; i < collect_count; i++)
14784 if (player->inventory_size < MAX_INVENTORY_SIZE)
14785 player->inventory_element[player->inventory_size++] = element;
14787 DrawGameDoorValues();
14789 else if (collect_count > 0)
14791 game.gems_still_needed -= collect_count;
14792 if (game.gems_still_needed < 0)
14793 game.gems_still_needed = 0;
14795 game.snapshot.collected_item = TRUE;
14797 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14799 DisplayGameControlValues();
14802 RaiseScoreElement(element);
14803 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14805 // use old behaviour for old levels (collecting)
14806 if (!level.finish_dig_collect && is_player)
14808 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14809 player->index_bit, dig_side);
14811 // if collecting triggered player relocation, finish collecting tile
14812 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14813 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14816 if (mode == DF_SNAP)
14818 if (level.block_snap_field)
14819 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14821 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14823 // use old behaviour for old levels (snapping)
14824 if (!level.finish_dig_collect)
14825 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14826 player->index_bit, dig_side);
14829 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14831 if (mode == DF_SNAP && element != EL_BD_ROCK)
14832 return MP_NO_ACTION;
14834 if (CAN_FALL(element) && dy)
14835 return MP_NO_ACTION;
14837 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14838 !(element == EL_SPRING && level.use_spring_bug))
14839 return MP_NO_ACTION;
14841 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14842 ((move_direction & MV_VERTICAL &&
14843 ((element_info[element].move_pattern & MV_LEFT &&
14844 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14845 (element_info[element].move_pattern & MV_RIGHT &&
14846 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14847 (move_direction & MV_HORIZONTAL &&
14848 ((element_info[element].move_pattern & MV_UP &&
14849 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14850 (element_info[element].move_pattern & MV_DOWN &&
14851 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14852 return MP_NO_ACTION;
14854 // do not push elements already moving away faster than player
14855 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14856 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14857 return MP_NO_ACTION;
14859 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14861 if (player->push_delay_value == -1 || !player_was_pushing)
14862 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14864 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14866 if (player->push_delay_value == -1)
14867 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14869 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14871 if (!player->is_pushing)
14872 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14875 player->is_pushing = TRUE;
14876 player->is_active = TRUE;
14878 if (!(IN_LEV_FIELD(nextx, nexty) &&
14879 (IS_FREE(nextx, nexty) ||
14880 (IS_SB_ELEMENT(element) &&
14881 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14882 (IS_CUSTOM_ELEMENT(element) &&
14883 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14884 return MP_NO_ACTION;
14886 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14887 return MP_NO_ACTION;
14889 if (player->push_delay == -1) // new pushing; restart delay
14890 player->push_delay = 0;
14892 if (player->push_delay < player->push_delay_value &&
14893 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14894 element != EL_SPRING && element != EL_BALLOON)
14896 // make sure that there is no move delay before next try to push
14897 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14898 player->move_delay = 0;
14900 return MP_NO_ACTION;
14903 if (IS_CUSTOM_ELEMENT(element) &&
14904 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14906 if (!DigFieldByCE(nextx, nexty, element))
14907 return MP_NO_ACTION;
14910 if (IS_SB_ELEMENT(element))
14912 boolean sokoban_task_solved = FALSE;
14914 if (element == EL_SOKOBAN_FIELD_FULL)
14916 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14918 IncrementSokobanFieldsNeeded();
14919 IncrementSokobanObjectsNeeded();
14922 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14924 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14926 DecrementSokobanFieldsNeeded();
14927 DecrementSokobanObjectsNeeded();
14929 // sokoban object was pushed from empty field to sokoban field
14930 if (Back[x][y] == EL_EMPTY)
14931 sokoban_task_solved = TRUE;
14934 Tile[x][y] = EL_SOKOBAN_OBJECT;
14936 if (Back[x][y] == Back[nextx][nexty])
14937 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14938 else if (Back[x][y] != 0)
14939 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14942 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14945 if (sokoban_task_solved &&
14946 game.sokoban_fields_still_needed == 0 &&
14947 game.sokoban_objects_still_needed == 0 &&
14948 level.auto_exit_sokoban)
14950 game.players_still_needed = 0;
14954 PlaySound(SND_GAME_SOKOBAN_SOLVING);
14958 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14960 InitMovingField(x, y, move_direction);
14961 GfxAction[x][y] = ACTION_PUSHING;
14963 if (mode == DF_SNAP)
14964 ContinueMoving(x, y);
14966 MovPos[x][y] = (dx != 0 ? dx : dy);
14968 Pushed[x][y] = TRUE;
14969 Pushed[nextx][nexty] = TRUE;
14971 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14972 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14974 player->push_delay_value = -1; // get new value later
14976 // check for element change _after_ element has been pushed
14977 if (game.use_change_when_pushing_bug)
14979 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14980 player->index_bit, dig_side);
14981 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14982 player->index_bit, dig_side);
14985 else if (IS_SWITCHABLE(element))
14987 if (PLAYER_SWITCHING(player, x, y))
14989 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14990 player->index_bit, dig_side);
14995 player->is_switching = TRUE;
14996 player->switch_x = x;
14997 player->switch_y = y;
14999 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15001 if (element == EL_ROBOT_WHEEL)
15003 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
15005 game.robot_wheel_x = x;
15006 game.robot_wheel_y = y;
15007 game.robot_wheel_active = TRUE;
15009 TEST_DrawLevelField(x, y);
15011 else if (element == EL_SP_TERMINAL)
15015 SCAN_PLAYFIELD(xx, yy)
15017 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
15021 else if (Tile[xx][yy] == EL_SP_TERMINAL)
15023 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
15025 ResetGfxAnimation(xx, yy);
15026 TEST_DrawLevelField(xx, yy);
15030 else if (IS_BELT_SWITCH(element))
15032 ToggleBeltSwitch(x, y);
15034 else if (element == EL_SWITCHGATE_SWITCH_UP ||
15035 element == EL_SWITCHGATE_SWITCH_DOWN ||
15036 element == EL_DC_SWITCHGATE_SWITCH_UP ||
15037 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
15039 ToggleSwitchgateSwitch();
15041 else if (element == EL_LIGHT_SWITCH ||
15042 element == EL_LIGHT_SWITCH_ACTIVE)
15044 ToggleLightSwitch(x, y);
15046 else if (element == EL_TIMEGATE_SWITCH ||
15047 element == EL_DC_TIMEGATE_SWITCH)
15049 ActivateTimegateSwitch(x, y);
15051 else if (element == EL_BALLOON_SWITCH_LEFT ||
15052 element == EL_BALLOON_SWITCH_RIGHT ||
15053 element == EL_BALLOON_SWITCH_UP ||
15054 element == EL_BALLOON_SWITCH_DOWN ||
15055 element == EL_BALLOON_SWITCH_NONE ||
15056 element == EL_BALLOON_SWITCH_ANY)
15058 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
15059 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
15060 element == EL_BALLOON_SWITCH_UP ? MV_UP :
15061 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
15062 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
15065 else if (element == EL_LAMP)
15067 Tile[x][y] = EL_LAMP_ACTIVE;
15068 game.lights_still_needed--;
15070 ResetGfxAnimation(x, y);
15071 TEST_DrawLevelField(x, y);
15073 else if (element == EL_TIME_ORB_FULL)
15075 Tile[x][y] = EL_TIME_ORB_EMPTY;
15077 if (level.time > 0 || level.use_time_orb_bug)
15079 TimeLeft += level.time_orb_time;
15080 game.no_level_time_limit = FALSE;
15082 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
15084 DisplayGameControlValues();
15087 ResetGfxAnimation(x, y);
15088 TEST_DrawLevelField(x, y);
15090 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
15091 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15095 game.ball_active = !game.ball_active;
15097 SCAN_PLAYFIELD(xx, yy)
15099 int e = Tile[xx][yy];
15101 if (game.ball_active)
15103 if (e == EL_EMC_MAGIC_BALL)
15104 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
15105 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
15106 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
15110 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
15111 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
15112 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15113 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
15118 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15119 player->index_bit, dig_side);
15121 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15122 player->index_bit, dig_side);
15124 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15125 player->index_bit, dig_side);
15131 if (!PLAYER_SWITCHING(player, x, y))
15133 player->is_switching = TRUE;
15134 player->switch_x = x;
15135 player->switch_y = y;
15137 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
15138 player->index_bit, dig_side);
15139 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15140 player->index_bit, dig_side);
15142 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
15143 player->index_bit, dig_side);
15144 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15145 player->index_bit, dig_side);
15148 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
15149 player->index_bit, dig_side);
15150 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15151 player->index_bit, dig_side);
15153 return MP_NO_ACTION;
15156 player->push_delay = -1;
15158 if (is_player) // function can also be called by EL_PENGUIN
15160 if (Tile[x][y] != element) // really digged/collected something
15162 player->is_collecting = !player->is_digging;
15163 player->is_active = TRUE;
15165 player->last_removed_element = element;
15172 static boolean DigFieldByCE(int x, int y, int digging_element)
15174 int element = Tile[x][y];
15176 if (!IS_FREE(x, y))
15178 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
15179 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
15182 // no element can dig solid indestructible elements
15183 if (IS_INDESTRUCTIBLE(element) &&
15184 !IS_DIGGABLE(element) &&
15185 !IS_COLLECTIBLE(element))
15188 if (AmoebaNr[x][y] &&
15189 (element == EL_AMOEBA_FULL ||
15190 element == EL_BD_AMOEBA ||
15191 element == EL_AMOEBA_GROWING))
15193 AmoebaCnt[AmoebaNr[x][y]]--;
15194 AmoebaCnt2[AmoebaNr[x][y]]--;
15197 if (IS_MOVING(x, y))
15198 RemoveMovingField(x, y);
15202 TEST_DrawLevelField(x, y);
15205 // if digged element was about to explode, prevent the explosion
15206 ExplodeField[x][y] = EX_TYPE_NONE;
15208 PlayLevelSoundAction(x, y, action);
15211 Store[x][y] = EL_EMPTY;
15213 // this makes it possible to leave the removed element again
15214 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15215 Store[x][y] = element;
15220 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15222 int jx = player->jx, jy = player->jy;
15223 int x = jx + dx, y = jy + dy;
15224 int snap_direction = (dx == -1 ? MV_LEFT :
15225 dx == +1 ? MV_RIGHT :
15227 dy == +1 ? MV_DOWN : MV_NONE);
15228 boolean can_continue_snapping = (level.continuous_snapping &&
15229 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15231 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15234 if (!player->active || !IN_LEV_FIELD(x, y))
15242 if (player->MovPos == 0)
15243 player->is_pushing = FALSE;
15245 player->is_snapping = FALSE;
15247 if (player->MovPos == 0)
15249 player->is_moving = FALSE;
15250 player->is_digging = FALSE;
15251 player->is_collecting = FALSE;
15257 // prevent snapping with already pressed snap key when not allowed
15258 if (player->is_snapping && !can_continue_snapping)
15261 player->MovDir = snap_direction;
15263 if (player->MovPos == 0)
15265 player->is_moving = FALSE;
15266 player->is_digging = FALSE;
15267 player->is_collecting = FALSE;
15270 player->is_dropping = FALSE;
15271 player->is_dropping_pressed = FALSE;
15272 player->drop_pressed_delay = 0;
15274 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15277 player->is_snapping = TRUE;
15278 player->is_active = TRUE;
15280 if (player->MovPos == 0)
15282 player->is_moving = FALSE;
15283 player->is_digging = FALSE;
15284 player->is_collecting = FALSE;
15287 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15288 TEST_DrawLevelField(player->last_jx, player->last_jy);
15290 TEST_DrawLevelField(x, y);
15295 static boolean DropElement(struct PlayerInfo *player)
15297 int old_element, new_element;
15298 int dropx = player->jx, dropy = player->jy;
15299 int drop_direction = player->MovDir;
15300 int drop_side = drop_direction;
15301 int drop_element = get_next_dropped_element(player);
15303 /* do not drop an element on top of another element; when holding drop key
15304 pressed without moving, dropped element must move away before the next
15305 element can be dropped (this is especially important if the next element
15306 is dynamite, which can be placed on background for historical reasons) */
15307 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15310 if (IS_THROWABLE(drop_element))
15312 dropx += GET_DX_FROM_DIR(drop_direction);
15313 dropy += GET_DY_FROM_DIR(drop_direction);
15315 if (!IN_LEV_FIELD(dropx, dropy))
15319 old_element = Tile[dropx][dropy]; // old element at dropping position
15320 new_element = drop_element; // default: no change when dropping
15322 // check if player is active, not moving and ready to drop
15323 if (!player->active || player->MovPos || player->drop_delay > 0)
15326 // check if player has anything that can be dropped
15327 if (new_element == EL_UNDEFINED)
15330 // only set if player has anything that can be dropped
15331 player->is_dropping_pressed = TRUE;
15333 // check if drop key was pressed long enough for EM style dynamite
15334 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15337 // check if anything can be dropped at the current position
15338 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15341 // collected custom elements can only be dropped on empty fields
15342 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15345 if (old_element != EL_EMPTY)
15346 Back[dropx][dropy] = old_element; // store old element on this field
15348 ResetGfxAnimation(dropx, dropy);
15349 ResetRandomAnimationValue(dropx, dropy);
15351 if (player->inventory_size > 0 ||
15352 player->inventory_infinite_element != EL_UNDEFINED)
15354 if (player->inventory_size > 0)
15356 player->inventory_size--;
15358 DrawGameDoorValues();
15360 if (new_element == EL_DYNAMITE)
15361 new_element = EL_DYNAMITE_ACTIVE;
15362 else if (new_element == EL_EM_DYNAMITE)
15363 new_element = EL_EM_DYNAMITE_ACTIVE;
15364 else if (new_element == EL_SP_DISK_RED)
15365 new_element = EL_SP_DISK_RED_ACTIVE;
15368 Tile[dropx][dropy] = new_element;
15370 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15371 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15372 el2img(Tile[dropx][dropy]), 0);
15374 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15376 // needed if previous element just changed to "empty" in the last frame
15377 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15379 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15380 player->index_bit, drop_side);
15381 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15383 player->index_bit, drop_side);
15385 TestIfElementTouchesCustomElement(dropx, dropy);
15387 else // player is dropping a dyna bomb
15389 player->dynabombs_left--;
15391 Tile[dropx][dropy] = new_element;
15393 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15394 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15395 el2img(Tile[dropx][dropy]), 0);
15397 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15400 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15401 InitField_WithBug1(dropx, dropy, FALSE);
15403 new_element = Tile[dropx][dropy]; // element might have changed
15405 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15406 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15408 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15409 MovDir[dropx][dropy] = drop_direction;
15411 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15413 // do not cause impact style collision by dropping elements that can fall
15414 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15417 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15418 player->is_dropping = TRUE;
15420 player->drop_pressed_delay = 0;
15421 player->is_dropping_pressed = FALSE;
15423 player->drop_x = dropx;
15424 player->drop_y = dropy;
15429 // ----------------------------------------------------------------------------
15430 // game sound playing functions
15431 // ----------------------------------------------------------------------------
15433 static int *loop_sound_frame = NULL;
15434 static int *loop_sound_volume = NULL;
15436 void InitPlayLevelSound(void)
15438 int num_sounds = getSoundListSize();
15440 checked_free(loop_sound_frame);
15441 checked_free(loop_sound_volume);
15443 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15444 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15447 static void PlayLevelSoundExt(int x, int y, int nr, boolean is_loop_sound)
15449 int sx = SCREENX(x), sy = SCREENY(y);
15450 int volume, stereo_position;
15451 int max_distance = 8;
15452 int type = (is_loop_sound ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15454 if ((!setup.sound_simple && !is_loop_sound) ||
15455 (!setup.sound_loops && is_loop_sound))
15458 if (!IN_LEV_FIELD(x, y) ||
15459 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15460 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15463 volume = SOUND_MAX_VOLUME;
15465 if (!IN_SCR_FIELD(sx, sy))
15467 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15468 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15470 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15473 stereo_position = (SOUND_MAX_LEFT +
15474 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15475 (SCR_FIELDX + 2 * max_distance));
15479 /* This assures that quieter loop sounds do not overwrite louder ones,
15480 while restarting sound volume comparison with each new game frame. */
15482 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15485 loop_sound_volume[nr] = volume;
15486 loop_sound_frame[nr] = FrameCounter;
15489 PlaySoundExt(nr, volume, stereo_position, type);
15492 static void PlayLevelSound(int x, int y, int nr)
15494 PlayLevelSoundExt(x, y, nr, IS_LOOP_SOUND(nr));
15497 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15499 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15500 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15501 y < LEVELY(BY1) ? LEVELY(BY1) :
15502 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15506 static void PlayLevelSoundAction(int x, int y, int action)
15508 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15511 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15513 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15515 if (sound_effect != SND_UNDEFINED)
15516 PlayLevelSound(x, y, sound_effect);
15519 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15522 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15524 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15525 PlayLevelSound(x, y, sound_effect);
15528 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15530 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15532 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15533 PlayLevelSound(x, y, sound_effect);
15536 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15538 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15540 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15541 StopSound(sound_effect);
15544 static int getLevelMusicNr(void)
15546 int level_pos = level_nr - leveldir_current->first_level;
15548 if (levelset.music[level_nr] != MUS_UNDEFINED)
15549 return levelset.music[level_nr]; // from config file
15551 return MAP_NOCONF_MUSIC(level_pos); // from music dir
15554 static void FadeLevelSounds(void)
15559 static void FadeLevelMusic(void)
15561 int music_nr = getLevelMusicNr();
15562 char *curr_music = getCurrentlyPlayingMusicFilename();
15563 char *next_music = getMusicInfoEntryFilename(music_nr);
15565 if (!strEqual(curr_music, next_music))
15569 void FadeLevelSoundsAndMusic(void)
15575 static void PlayLevelMusic(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))
15582 PlayMusicLoop(music_nr);
15585 static int getSoundAction_BD(int sample)
15589 case GD_S_STONE_PUSHING:
15590 case GD_S_MEGA_STONE_PUSHING:
15591 case GD_S_FLYING_STONE_PUSHING:
15592 case GD_S_WAITING_STONE_PUSHING:
15593 case GD_S_CHASING_STONE_PUSHING:
15594 case GD_S_NUT_PUSHING:
15595 case GD_S_NITRO_PACK_PUSHING:
15596 case GD_S_BLADDER_PUSHING:
15597 case GD_S_BOX_PUSHING:
15598 return ACTION_PUSHING;
15600 case GD_S_STONE_FALLING:
15601 case GD_S_MEGA_STONE_FALLING:
15602 case GD_S_FLYING_STONE_FALLING:
15603 case GD_S_NUT_FALLING:
15604 case GD_S_DIRT_BALL_FALLING:
15605 case GD_S_DIRT_LOOSE_FALLING:
15606 case GD_S_NITRO_PACK_FALLING:
15607 case GD_S_FALLING_WALL_FALLING:
15608 return ACTION_FALLING;
15610 case GD_S_STONE_IMPACT:
15611 case GD_S_MEGA_STONE_IMPACT:
15612 case GD_S_FLYING_STONE_IMPACT:
15613 case GD_S_NUT_IMPACT:
15614 case GD_S_DIRT_BALL_IMPACT:
15615 case GD_S_DIRT_LOOSE_IMPACT:
15616 case GD_S_NITRO_PACK_IMPACT:
15617 case GD_S_FALLING_WALL_IMPACT:
15618 return ACTION_IMPACT;
15620 case GD_S_NUT_CRACKING:
15621 return ACTION_BREAKING;
15623 case GD_S_EXPANDING_WALL:
15624 case GD_S_WALL_REAPPEARING:
15627 case GD_S_ACID_SPREADING:
15628 return ACTION_GROWING;
15630 case GD_S_DIAMOND_COLLECTING:
15631 case GD_S_FLYING_DIAMOND_COLLECTING:
15632 case GD_S_SKELETON_COLLECTING:
15633 case GD_S_PNEUMATIC_COLLECTING:
15634 case GD_S_BOMB_COLLECTING:
15635 case GD_S_CLOCK_COLLECTING:
15636 case GD_S_SWEET_COLLECTING:
15637 case GD_S_KEY_COLLECTING:
15638 case GD_S_DIAMOND_KEY_COLLECTING:
15639 return ACTION_COLLECTING;
15641 case GD_S_BOMB_PLACING:
15642 case GD_S_REPLICATOR:
15643 return ACTION_DROPPING;
15645 case GD_S_BLADDER_MOVING:
15646 return ACTION_MOVING;
15648 case GD_S_BLADDER_SPENDER:
15649 case GD_S_BLADDER_CONVERTING:
15650 case GD_S_GRAVITY_CHANGING:
15651 return ACTION_CHANGING;
15653 case GD_S_BITER_EATING:
15654 return ACTION_EATING;
15656 case GD_S_DOOR_OPENING:
15657 case GD_S_CRACKING:
15658 return ACTION_OPENING;
15660 case GD_S_DIRT_WALKING:
15661 return ACTION_DIGGING;
15663 case GD_S_EMPTY_WALKING:
15664 return ACTION_WALKING;
15666 case GD_S_SWITCH_BITER:
15667 case GD_S_SWITCH_CREATURES:
15668 case GD_S_SWITCH_GRAVITY:
15669 case GD_S_SWITCH_EXPANDING:
15670 case GD_S_SWITCH_CONVEYOR:
15671 case GD_S_SWITCH_REPLICATOR:
15672 case GD_S_STIRRING:
15673 return ACTION_ACTIVATING;
15675 case GD_S_TELEPORTER:
15676 return ACTION_PASSING;
15678 case GD_S_EXPLODING:
15679 case GD_S_BOMB_EXPLODING:
15680 case GD_S_GHOST_EXPLODING:
15681 case GD_S_VOODOO_EXPLODING:
15682 case GD_S_NITRO_PACK_EXPLODING:
15683 return ACTION_EXPLODING;
15685 case GD_S_COVERING:
15687 case GD_S_MAGIC_WALL:
15688 case GD_S_PNEUMATIC_HAMMER:
15690 return ACTION_ACTIVE;
15692 case GD_S_DIAMOND_FALLING_RANDOM:
15693 case GD_S_DIAMOND_FALLING_1:
15694 case GD_S_DIAMOND_FALLING_2:
15695 case GD_S_DIAMOND_FALLING_3:
15696 case GD_S_DIAMOND_FALLING_4:
15697 case GD_S_DIAMOND_FALLING_5:
15698 case GD_S_DIAMOND_FALLING_6:
15699 case GD_S_DIAMOND_FALLING_7:
15700 case GD_S_DIAMOND_FALLING_8:
15701 case GD_S_DIAMOND_IMPACT_RANDOM:
15702 case GD_S_DIAMOND_IMPACT_1:
15703 case GD_S_DIAMOND_IMPACT_2:
15704 case GD_S_DIAMOND_IMPACT_3:
15705 case GD_S_DIAMOND_IMPACT_4:
15706 case GD_S_DIAMOND_IMPACT_5:
15707 case GD_S_DIAMOND_IMPACT_6:
15708 case GD_S_DIAMOND_IMPACT_7:
15709 case GD_S_DIAMOND_IMPACT_8:
15710 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15711 case GD_S_FLYING_DIAMOND_FALLING_1:
15712 case GD_S_FLYING_DIAMOND_FALLING_2:
15713 case GD_S_FLYING_DIAMOND_FALLING_3:
15714 case GD_S_FLYING_DIAMOND_FALLING_4:
15715 case GD_S_FLYING_DIAMOND_FALLING_5:
15716 case GD_S_FLYING_DIAMOND_FALLING_6:
15717 case GD_S_FLYING_DIAMOND_FALLING_7:
15718 case GD_S_FLYING_DIAMOND_FALLING_8:
15719 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15720 case GD_S_FLYING_DIAMOND_IMPACT_1:
15721 case GD_S_FLYING_DIAMOND_IMPACT_2:
15722 case GD_S_FLYING_DIAMOND_IMPACT_3:
15723 case GD_S_FLYING_DIAMOND_IMPACT_4:
15724 case GD_S_FLYING_DIAMOND_IMPACT_5:
15725 case GD_S_FLYING_DIAMOND_IMPACT_6:
15726 case GD_S_FLYING_DIAMOND_IMPACT_7:
15727 case GD_S_FLYING_DIAMOND_IMPACT_8:
15728 case GD_S_TIMEOUT_0:
15729 case GD_S_TIMEOUT_1:
15730 case GD_S_TIMEOUT_2:
15731 case GD_S_TIMEOUT_3:
15732 case GD_S_TIMEOUT_4:
15733 case GD_S_TIMEOUT_5:
15734 case GD_S_TIMEOUT_6:
15735 case GD_S_TIMEOUT_7:
15736 case GD_S_TIMEOUT_8:
15737 case GD_S_TIMEOUT_9:
15738 case GD_S_TIMEOUT_10:
15739 case GD_S_BONUS_LIFE:
15740 // trigger special post-processing (and force sound to be non-looping)
15741 return ACTION_OTHER;
15743 case GD_S_AMOEBA_MAGIC:
15744 case GD_S_FINISHED:
15745 // trigger special post-processing (and force sound to be looping)
15746 return ACTION_DEFAULT;
15749 return ACTION_DEFAULT;
15753 static int getSoundEffect_BD(int element_bd, int sample)
15755 int sound_action = getSoundAction_BD(sample);
15756 int sound_effect = element_info[SND_ELEMENT(element_bd)].sound[sound_action];
15760 if (sound_action != ACTION_OTHER &&
15761 sound_action != ACTION_DEFAULT)
15762 return sound_effect;
15764 // special post-processing for some sounds
15767 case GD_S_DIAMOND_FALLING_RANDOM:
15768 case GD_S_DIAMOND_FALLING_1:
15769 case GD_S_DIAMOND_FALLING_2:
15770 case GD_S_DIAMOND_FALLING_3:
15771 case GD_S_DIAMOND_FALLING_4:
15772 case GD_S_DIAMOND_FALLING_5:
15773 case GD_S_DIAMOND_FALLING_6:
15774 case GD_S_DIAMOND_FALLING_7:
15775 case GD_S_DIAMOND_FALLING_8:
15776 nr = (sample == GD_S_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15777 sample - GD_S_DIAMOND_FALLING_1);
15778 sound_effect = SND_BD_DIAMOND_FALLING_RANDOM_1 + nr;
15780 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15781 sound_effect = SND_BD_DIAMOND_FALLING;
15784 case GD_S_DIAMOND_IMPACT_RANDOM:
15785 case GD_S_DIAMOND_IMPACT_1:
15786 case GD_S_DIAMOND_IMPACT_2:
15787 case GD_S_DIAMOND_IMPACT_3:
15788 case GD_S_DIAMOND_IMPACT_4:
15789 case GD_S_DIAMOND_IMPACT_5:
15790 case GD_S_DIAMOND_IMPACT_6:
15791 case GD_S_DIAMOND_IMPACT_7:
15792 case GD_S_DIAMOND_IMPACT_8:
15793 nr = (sample == GD_S_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15794 sample - GD_S_DIAMOND_IMPACT_1);
15795 sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr;
15797 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15798 sound_effect = SND_BD_DIAMOND_IMPACT;
15801 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15802 case GD_S_FLYING_DIAMOND_FALLING_1:
15803 case GD_S_FLYING_DIAMOND_FALLING_2:
15804 case GD_S_FLYING_DIAMOND_FALLING_3:
15805 case GD_S_FLYING_DIAMOND_FALLING_4:
15806 case GD_S_FLYING_DIAMOND_FALLING_5:
15807 case GD_S_FLYING_DIAMOND_FALLING_6:
15808 case GD_S_FLYING_DIAMOND_FALLING_7:
15809 case GD_S_FLYING_DIAMOND_FALLING_8:
15810 nr = (sample == GD_S_FLYING_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15811 sample - GD_S_FLYING_DIAMOND_FALLING_1);
15812 sound_effect = SND_BD_FLYING_DIAMOND_FALLING_RANDOM_1 + nr;
15814 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15815 sound_effect = SND_BD_FLYING_DIAMOND_FALLING;
15818 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15819 case GD_S_FLYING_DIAMOND_IMPACT_1:
15820 case GD_S_FLYING_DIAMOND_IMPACT_2:
15821 case GD_S_FLYING_DIAMOND_IMPACT_3:
15822 case GD_S_FLYING_DIAMOND_IMPACT_4:
15823 case GD_S_FLYING_DIAMOND_IMPACT_5:
15824 case GD_S_FLYING_DIAMOND_IMPACT_6:
15825 case GD_S_FLYING_DIAMOND_IMPACT_7:
15826 case GD_S_FLYING_DIAMOND_IMPACT_8:
15827 nr = (sample == GD_S_FLYING_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15828 sample - GD_S_FLYING_DIAMOND_IMPACT_1);
15829 sound_effect = SND_BD_FLYING_DIAMOND_IMPACT_RANDOM_1 + nr;
15831 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15832 sound_effect = SND_BD_FLYING_DIAMOND_IMPACT;
15835 case GD_S_TIMEOUT_0:
15836 case GD_S_TIMEOUT_1:
15837 case GD_S_TIMEOUT_2:
15838 case GD_S_TIMEOUT_3:
15839 case GD_S_TIMEOUT_4:
15840 case GD_S_TIMEOUT_5:
15841 case GD_S_TIMEOUT_6:
15842 case GD_S_TIMEOUT_7:
15843 case GD_S_TIMEOUT_8:
15844 case GD_S_TIMEOUT_9:
15845 case GD_S_TIMEOUT_10:
15846 nr = sample - GD_S_TIMEOUT_0;
15847 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME_0 + nr;
15849 if (getSoundInfoEntryFilename(sound_effect) == NULL && sample != GD_S_TIMEOUT_0)
15850 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME;
15853 case GD_S_BONUS_LIFE:
15854 sound_effect = SND_GAME_HEALTH_BONUS;
15857 case GD_S_AMOEBA_MAGIC:
15858 sound_effect = SND_BD_AMOEBA_OTHER;
15861 case GD_S_FINISHED:
15862 sound_effect = SND_GAME_LEVELTIME_BONUS;
15866 sound_effect = SND_UNDEFINED;
15870 return sound_effect;
15873 void PlayLevelSound_BD(int xx, int yy, int element_bd, int sample)
15875 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15876 int sound_effect = getSoundEffect_BD(element, sample);
15877 int sound_action = getSoundAction_BD(sample);
15878 boolean is_loop_sound = IS_LOOP_SOUND(sound_effect);
15880 int x = xx - offset;
15881 int y = yy - offset;
15883 // some sound actions are always looping in native BD game engine
15884 if (sound_action == ACTION_DEFAULT)
15885 is_loop_sound = TRUE;
15887 // some sound actions are always non-looping in native BD game engine
15888 if (sound_action == ACTION_FALLING ||
15889 sound_action == ACTION_MOVING ||
15890 sound_action == ACTION_OTHER)
15891 is_loop_sound = FALSE;
15893 if (sound_effect != SND_UNDEFINED)
15894 PlayLevelSoundExt(x, y, sound_effect, is_loop_sound);
15897 void StopSound_BD(int element_bd, int sample)
15899 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15900 int sound_effect = getSoundEffect_BD(element, sample);
15902 if (sound_effect != SND_UNDEFINED)
15903 StopSound(sound_effect);
15906 boolean isSoundPlaying_BD(int element_bd, int sample)
15908 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15909 int sound_effect = getSoundEffect_BD(element, sample);
15911 if (sound_effect != SND_UNDEFINED)
15912 return isSoundPlaying(sound_effect);
15917 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15919 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15921 int x = xx - offset;
15922 int y = yy - offset;
15927 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15931 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15935 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15939 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15943 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15947 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15951 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15954 case SOUND_android_clone:
15955 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15958 case SOUND_android_move:
15959 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15963 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15967 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15971 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15974 case SOUND_eater_eat:
15975 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15979 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15982 case SOUND_collect:
15983 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15986 case SOUND_diamond:
15987 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15991 // !!! CHECK THIS !!!
15993 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15995 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15999 case SOUND_wonderfall:
16000 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
16004 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
16008 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
16012 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
16016 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
16020 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16024 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
16028 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16032 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16035 case SOUND_exit_open:
16036 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
16039 case SOUND_exit_leave:
16040 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16043 case SOUND_dynamite:
16044 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16048 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16052 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
16056 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16060 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
16064 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
16068 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
16072 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
16077 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
16079 int element = map_element_SP_to_RND(element_sp);
16080 int action = map_action_SP_to_RND(action_sp);
16081 int offset = (setup.sp_show_border_elements ? 0 : 1);
16082 int x = xx - offset;
16083 int y = yy - offset;
16085 PlayLevelSoundElementAction(x, y, element, action);
16088 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
16090 int element = map_element_MM_to_RND(element_mm);
16091 int action = map_action_MM_to_RND(action_mm);
16093 int x = xx - offset;
16094 int y = yy - offset;
16096 if (!IS_MM_ELEMENT(element))
16097 element = EL_MM_DEFAULT;
16099 PlayLevelSoundElementAction(x, y, element, action);
16102 void PlaySound_MM(int sound_mm)
16104 int sound = map_sound_MM_to_RND(sound_mm);
16106 if (sound == SND_UNDEFINED)
16112 void PlaySoundLoop_MM(int sound_mm)
16114 int sound = map_sound_MM_to_RND(sound_mm);
16116 if (sound == SND_UNDEFINED)
16119 PlaySoundLoop(sound);
16122 void StopSound_MM(int sound_mm)
16124 int sound = map_sound_MM_to_RND(sound_mm);
16126 if (sound == SND_UNDEFINED)
16132 void RaiseScore(int value)
16134 game.score += value;
16136 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
16138 DisplayGameControlValues();
16141 void RaiseScoreElement(int element)
16146 case EL_BD_DIAMOND:
16147 case EL_EMERALD_YELLOW:
16148 case EL_EMERALD_RED:
16149 case EL_EMERALD_PURPLE:
16150 case EL_SP_INFOTRON:
16151 RaiseScore(level.score[SC_EMERALD]);
16154 RaiseScore(level.score[SC_DIAMOND]);
16157 RaiseScore(level.score[SC_CRYSTAL]);
16160 RaiseScore(level.score[SC_PEARL]);
16163 case EL_BD_BUTTERFLY:
16164 case EL_SP_ELECTRON:
16165 RaiseScore(level.score[SC_BUG]);
16168 case EL_BD_FIREFLY:
16169 case EL_SP_SNIKSNAK:
16170 RaiseScore(level.score[SC_SPACESHIP]);
16173 case EL_DARK_YAMYAM:
16174 RaiseScore(level.score[SC_YAMYAM]);
16177 RaiseScore(level.score[SC_ROBOT]);
16180 RaiseScore(level.score[SC_PACMAN]);
16183 RaiseScore(level.score[SC_NUT]);
16186 case EL_EM_DYNAMITE:
16187 case EL_SP_DISK_RED:
16188 case EL_DYNABOMB_INCREASE_NUMBER:
16189 case EL_DYNABOMB_INCREASE_SIZE:
16190 case EL_DYNABOMB_INCREASE_POWER:
16191 RaiseScore(level.score[SC_DYNAMITE]);
16193 case EL_SHIELD_NORMAL:
16194 case EL_SHIELD_DEADLY:
16195 RaiseScore(level.score[SC_SHIELD]);
16197 case EL_EXTRA_TIME:
16198 RaiseScore(level.extra_time_score);
16212 case EL_DC_KEY_WHITE:
16213 RaiseScore(level.score[SC_KEY]);
16216 RaiseScore(element_info[element].collect_score);
16221 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
16223 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
16227 // prevent short reactivation of overlay buttons while closing door
16228 SetOverlayActive(FALSE);
16229 UnmapGameButtons();
16231 // door may still be open due to skipped or envelope style request
16232 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
16235 if (network.enabled)
16237 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
16241 // when using BD game engine, cover screen before fading out
16242 if (!quick_quit && level.game_engine_type == GAME_ENGINE_TYPE_BD)
16243 game_bd.cover_screen = TRUE;
16246 FadeSkipNextFadeIn();
16248 SetGameStatus(GAME_MODE_MAIN);
16253 else // continue playing the game
16255 if (tape.playing && tape.deactivate_display)
16256 TapeDeactivateDisplayOff(TRUE);
16258 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
16260 if (tape.playing && tape.deactivate_display)
16261 TapeDeactivateDisplayOn();
16265 void RequestQuitGame(boolean escape_key_pressed)
16267 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
16268 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
16269 level_editor_test_game);
16270 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
16271 quick_quit || score_info_tape_play);
16273 RequestQuitGameExt(skip_request, quick_quit,
16274 "Do you really want to quit the game?");
16277 static char *getRestartGameMessage(void)
16279 boolean play_again = hasStartedNetworkGame();
16280 static char message[MAX_OUTPUT_LINESIZE];
16281 char *game_over_text = "Game over!";
16282 char *play_again_text = " Play it again?";
16284 if (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
16285 game_mm.game_over_message != NULL)
16286 game_over_text = game_mm.game_over_message;
16288 snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text,
16289 (play_again ? play_again_text : ""));
16294 static void RequestRestartGame(void)
16296 char *message = getRestartGameMessage();
16297 boolean has_started_game = hasStartedNetworkGame();
16298 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
16299 int door_state = DOOR_CLOSE_1;
16301 boolean restart_wanted = (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game);
16303 // if no restart wanted, continue with next level for BD style intermission levels
16304 if (!restart_wanted && !level_editor_test_game && level.bd_intermission)
16306 boolean success = AdvanceToNextLevel();
16308 restart_wanted = (success && setup.auto_play_next_level);
16311 if (restart_wanted)
16313 CloseDoor(door_state);
16315 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16319 // if game was invoked from level editor, also close tape recorder door
16320 if (level_editor_test_game)
16321 door_state = DOOR_CLOSE_ALL;
16323 CloseDoor(door_state);
16325 SetGameStatus(GAME_MODE_MAIN);
16331 boolean CheckRestartGame(void)
16333 static int game_over_delay = 0;
16334 int game_over_delay_value = 50;
16335 boolean game_over = checkGameFailed();
16339 game_over_delay = game_over_delay_value;
16344 if (game_over_delay > 0)
16346 if (game_over_delay == game_over_delay_value / 2)
16347 PlaySound(SND_GAME_LOSING);
16354 // do not ask to play again if request dialog is already active
16355 if (checkRequestActive())
16358 // do not ask to play again if request dialog already handled
16359 if (game.RestartGameRequested)
16362 // do not ask to play again if game was never actually played
16363 if (!game.GamePlayed)
16366 // do not ask to play again if this was disabled in setup menu
16367 if (!setup.ask_on_game_over)
16370 game.RestartGameRequested = TRUE;
16372 RequestRestartGame();
16377 boolean checkGameRunning(void)
16379 if (game_status != GAME_MODE_PLAYING)
16382 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGameRunning_BD())
16388 boolean checkGamePlaying(void)
16390 if (game_status != GAME_MODE_PLAYING)
16393 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGamePlaying_BD())
16399 boolean checkGameSolved(void)
16401 // set for all game engines if level was solved
16402 return game.LevelSolved_GameEnd;
16405 boolean checkGameFailed(void)
16407 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
16408 return (game_bd.game_over && !game_bd.level_solved);
16409 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16410 return (game_em.game_over && !game_em.level_solved);
16411 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16412 return (game_sp.game_over && !game_sp.level_solved);
16413 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16414 return (game_mm.game_over && !game_mm.level_solved);
16415 else // GAME_ENGINE_TYPE_RND
16416 return (game.GameOver && !game.LevelSolved);
16419 boolean checkGameEnded(void)
16421 return (checkGameSolved() || checkGameFailed());
16424 boolean checkRequestActive(void)
16426 return (game.request_active || game.envelope_active || game.any_door_active);
16430 // ----------------------------------------------------------------------------
16431 // random generator functions
16432 // ----------------------------------------------------------------------------
16434 unsigned int InitEngineRandom_RND(int seed)
16436 game.num_random_calls = 0;
16438 return InitEngineRandom(seed);
16441 unsigned int RND(int max)
16445 game.num_random_calls++;
16447 return GetEngineRandom(max);
16454 // ----------------------------------------------------------------------------
16455 // game engine snapshot handling functions
16456 // ----------------------------------------------------------------------------
16458 struct EngineSnapshotInfo
16460 // runtime values for custom element collect score
16461 int collect_score[NUM_CUSTOM_ELEMENTS];
16463 // runtime values for group element choice position
16464 int choice_pos[NUM_GROUP_ELEMENTS];
16466 // runtime values for belt position animations
16467 int belt_graphic[4][NUM_BELT_PARTS];
16468 int belt_anim_mode[4][NUM_BELT_PARTS];
16471 static struct EngineSnapshotInfo engine_snapshot_rnd;
16472 static char *snapshot_level_identifier = NULL;
16473 static int snapshot_level_nr = -1;
16475 static void SaveEngineSnapshotValues_RND(void)
16477 static int belt_base_active_element[4] =
16479 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
16480 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
16481 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
16482 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
16486 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16488 int element = EL_CUSTOM_START + i;
16490 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
16493 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16495 int element = EL_GROUP_START + i;
16497 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
16500 for (i = 0; i < 4; i++)
16502 for (j = 0; j < NUM_BELT_PARTS; j++)
16504 int element = belt_base_active_element[i] + j;
16505 int graphic = el2img(element);
16506 int anim_mode = graphic_info[graphic].anim_mode;
16508 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
16509 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
16514 static void LoadEngineSnapshotValues_RND(void)
16516 unsigned int num_random_calls = game.num_random_calls;
16519 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16521 int element = EL_CUSTOM_START + i;
16523 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
16526 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16528 int element = EL_GROUP_START + i;
16530 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
16533 for (i = 0; i < 4; i++)
16535 for (j = 0; j < NUM_BELT_PARTS; j++)
16537 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
16538 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
16540 graphic_info[graphic].anim_mode = anim_mode;
16544 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16546 InitRND(tape.random_seed);
16547 for (i = 0; i < num_random_calls; i++)
16551 if (game.num_random_calls != num_random_calls)
16553 Error("number of random calls out of sync");
16554 Error("number of random calls should be %d", num_random_calls);
16555 Error("number of random calls is %d", game.num_random_calls);
16557 Fail("this should not happen -- please debug");
16561 void FreeEngineSnapshotSingle(void)
16563 FreeSnapshotSingle();
16565 setString(&snapshot_level_identifier, NULL);
16566 snapshot_level_nr = -1;
16569 void FreeEngineSnapshotList(void)
16571 FreeSnapshotList();
16574 static ListNode *SaveEngineSnapshotBuffers(void)
16576 ListNode *buffers = NULL;
16578 // copy some special values to a structure better suited for the snapshot
16580 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16581 SaveEngineSnapshotValues_RND();
16582 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16583 SaveEngineSnapshotValues_EM();
16584 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16585 SaveEngineSnapshotValues_SP(&buffers);
16586 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16587 SaveEngineSnapshotValues_MM();
16589 // save values stored in special snapshot structure
16591 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16592 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
16593 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16594 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
16595 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16596 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
16597 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16598 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
16600 // save further RND engine values
16602 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
16603 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
16604 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
16606 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
16607 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
16608 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
16609 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16610 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTimeFrames));
16611 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16613 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16614 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16615 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16617 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16619 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16620 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16622 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16623 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16624 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16625 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16626 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16627 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16628 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16629 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16630 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16631 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16632 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16633 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16634 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16635 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16636 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16637 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16638 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16639 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16641 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16642 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16644 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16645 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16646 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16648 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16649 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16651 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16652 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16653 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16654 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16655 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16656 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16658 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16659 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16662 ListNode *node = engine_snapshot_list_rnd;
16665 while (node != NULL)
16667 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16672 Debug("game:playing:SaveEngineSnapshotBuffers",
16673 "size of engine snapshot: %d bytes", num_bytes);
16679 void SaveEngineSnapshotSingle(void)
16681 ListNode *buffers = SaveEngineSnapshotBuffers();
16683 // finally save all snapshot buffers to single snapshot
16684 SaveSnapshotSingle(buffers);
16686 // save level identification information
16687 setString(&snapshot_level_identifier, leveldir_current->identifier);
16688 snapshot_level_nr = level_nr;
16691 boolean CheckSaveEngineSnapshotToList(void)
16693 boolean save_snapshot =
16694 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16695 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16696 game.snapshot.changed_action) ||
16697 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16698 game.snapshot.collected_item));
16700 game.snapshot.changed_action = FALSE;
16701 game.snapshot.collected_item = FALSE;
16702 game.snapshot.save_snapshot = save_snapshot;
16704 return save_snapshot;
16707 void SaveEngineSnapshotToList(void)
16709 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16713 ListNode *buffers = SaveEngineSnapshotBuffers();
16715 // finally save all snapshot buffers to snapshot list
16716 SaveSnapshotToList(buffers);
16719 void SaveEngineSnapshotToListInitial(void)
16721 FreeEngineSnapshotList();
16723 SaveEngineSnapshotToList();
16726 static void LoadEngineSnapshotValues(void)
16728 // restore special values from snapshot structure
16730 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16731 LoadEngineSnapshotValues_RND();
16732 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16733 LoadEngineSnapshotValues_EM();
16734 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16735 LoadEngineSnapshotValues_SP();
16736 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16737 LoadEngineSnapshotValues_MM();
16740 void LoadEngineSnapshotSingle(void)
16742 LoadSnapshotSingle();
16744 LoadEngineSnapshotValues();
16747 static void LoadEngineSnapshot_Undo(int steps)
16749 LoadSnapshotFromList_Older(steps);
16751 LoadEngineSnapshotValues();
16754 static void LoadEngineSnapshot_Redo(int steps)
16756 LoadSnapshotFromList_Newer(steps);
16758 LoadEngineSnapshotValues();
16761 boolean CheckEngineSnapshotSingle(void)
16763 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16764 snapshot_level_nr == level_nr);
16767 boolean CheckEngineSnapshotList(void)
16769 return CheckSnapshotList();
16773 // ---------- new game button stuff -------------------------------------------
16780 boolean *setup_value;
16781 boolean allowed_on_tape;
16782 boolean is_touch_button;
16784 } gamebutton_info[NUM_GAME_BUTTONS] =
16787 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16788 GAME_CTRL_ID_STOP, NULL,
16789 TRUE, FALSE, "stop game"
16792 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16793 GAME_CTRL_ID_PAUSE, NULL,
16794 TRUE, FALSE, "pause game"
16797 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16798 GAME_CTRL_ID_PLAY, NULL,
16799 TRUE, FALSE, "play game"
16802 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16803 GAME_CTRL_ID_UNDO, NULL,
16804 TRUE, FALSE, "undo step"
16807 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16808 GAME_CTRL_ID_REDO, NULL,
16809 TRUE, FALSE, "redo step"
16812 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16813 GAME_CTRL_ID_SAVE, NULL,
16814 TRUE, FALSE, "save game"
16817 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16818 GAME_CTRL_ID_PAUSE2, NULL,
16819 TRUE, FALSE, "pause game"
16822 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16823 GAME_CTRL_ID_LOAD, NULL,
16824 TRUE, FALSE, "load game"
16827 IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart,
16828 GAME_CTRL_ID_RESTART, NULL,
16829 TRUE, FALSE, "restart game"
16832 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16833 GAME_CTRL_ID_PANEL_STOP, NULL,
16834 FALSE, FALSE, "stop game"
16837 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16838 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16839 FALSE, FALSE, "pause game"
16842 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16843 GAME_CTRL_ID_PANEL_PLAY, NULL,
16844 FALSE, FALSE, "play game"
16847 IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart,
16848 GAME_CTRL_ID_PANEL_RESTART, NULL,
16849 FALSE, FALSE, "restart game"
16852 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16853 GAME_CTRL_ID_TOUCH_STOP, NULL,
16854 FALSE, TRUE, "stop game"
16857 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16858 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16859 FALSE, TRUE, "pause game"
16862 IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart,
16863 GAME_CTRL_ID_TOUCH_RESTART, NULL,
16864 FALSE, TRUE, "restart game"
16867 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16868 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16869 TRUE, FALSE, "background music on/off"
16872 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16873 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16874 TRUE, FALSE, "sound loops on/off"
16877 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16878 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16879 TRUE, FALSE, "normal sounds on/off"
16882 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16883 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16884 FALSE, FALSE, "background music on/off"
16887 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16888 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16889 FALSE, FALSE, "sound loops on/off"
16892 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16893 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16894 FALSE, FALSE, "normal sounds on/off"
16898 void CreateGameButtons(void)
16902 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16904 int graphic = gamebutton_info[i].graphic;
16905 struct GraphicInfo *gfx = &graphic_info[graphic];
16906 struct XY *pos = gamebutton_info[i].pos;
16907 struct GadgetInfo *gi;
16910 unsigned int event_mask;
16911 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16912 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16913 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16914 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16915 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16916 int gd_x = gfx->src_x;
16917 int gd_y = gfx->src_y;
16918 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16919 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16920 int gd_xa = gfx->src_x + gfx->active_xoffset;
16921 int gd_ya = gfx->src_y + gfx->active_yoffset;
16922 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16923 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16924 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16925 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16928 // do not use touch buttons if overlay touch buttons are disabled
16929 if (is_touch_button && !setup.touch.overlay_buttons)
16932 if (gfx->bitmap == NULL)
16934 game_gadget[id] = NULL;
16939 if (id == GAME_CTRL_ID_STOP ||
16940 id == GAME_CTRL_ID_PANEL_STOP ||
16941 id == GAME_CTRL_ID_TOUCH_STOP ||
16942 id == GAME_CTRL_ID_PLAY ||
16943 id == GAME_CTRL_ID_PANEL_PLAY ||
16944 id == GAME_CTRL_ID_SAVE ||
16945 id == GAME_CTRL_ID_LOAD ||
16946 id == GAME_CTRL_ID_RESTART ||
16947 id == GAME_CTRL_ID_PANEL_RESTART ||
16948 id == GAME_CTRL_ID_TOUCH_RESTART)
16950 button_type = GD_TYPE_NORMAL_BUTTON;
16952 event_mask = GD_EVENT_RELEASED;
16954 else if (id == GAME_CTRL_ID_UNDO ||
16955 id == GAME_CTRL_ID_REDO)
16957 button_type = GD_TYPE_NORMAL_BUTTON;
16959 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16963 button_type = GD_TYPE_CHECK_BUTTON;
16964 checked = (gamebutton_info[i].setup_value != NULL ?
16965 *gamebutton_info[i].setup_value : FALSE);
16966 event_mask = GD_EVENT_PRESSED;
16969 gi = CreateGadget(GDI_CUSTOM_ID, id,
16970 GDI_IMAGE_ID, graphic,
16971 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16974 GDI_WIDTH, gfx->width,
16975 GDI_HEIGHT, gfx->height,
16976 GDI_TYPE, button_type,
16977 GDI_STATE, GD_BUTTON_UNPRESSED,
16978 GDI_CHECKED, checked,
16979 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16980 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16981 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16982 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16983 GDI_DIRECT_DRAW, FALSE,
16984 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16985 GDI_EVENT_MASK, event_mask,
16986 GDI_CALLBACK_ACTION, HandleGameButtons,
16990 Fail("cannot create gadget");
16992 game_gadget[id] = gi;
16996 void FreeGameButtons(void)
17000 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17001 FreeGadget(game_gadget[i]);
17004 static void UnmapGameButtonsAtSamePosition(int id)
17008 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17010 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
17011 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
17012 UnmapGadget(game_gadget[i]);
17015 static void UnmapGameButtonsAtSamePosition_All(void)
17017 if (setup.show_load_save_buttons)
17019 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
17020 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
17021 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
17023 else if (setup.show_undo_redo_buttons)
17025 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
17026 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
17027 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
17031 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
17032 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
17033 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
17035 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
17036 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
17037 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
17041 void MapLoadSaveButtons(void)
17043 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
17044 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
17046 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
17047 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
17050 void MapUndoRedoButtons(void)
17052 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
17053 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
17055 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
17056 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
17059 void ModifyPauseButtons(void)
17063 GAME_CTRL_ID_PAUSE,
17064 GAME_CTRL_ID_PAUSE2,
17065 GAME_CTRL_ID_PANEL_PAUSE,
17066 GAME_CTRL_ID_TOUCH_PAUSE,
17071 // do not redraw pause button on closed door (may happen when restarting game)
17072 if (!(GetDoorState() & DOOR_OPEN_1))
17075 for (i = 0; ids[i] > -1; i++)
17076 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
17079 static void MapGameButtonsExt(boolean on_tape)
17083 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17085 if ((i == GAME_CTRL_ID_UNDO ||
17086 i == GAME_CTRL_ID_REDO) &&
17087 game_status != GAME_MODE_PLAYING)
17090 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17091 MapGadget(game_gadget[i]);
17094 UnmapGameButtonsAtSamePosition_All();
17096 RedrawGameButtons();
17099 static void UnmapGameButtonsExt(boolean on_tape)
17103 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17104 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17105 UnmapGadget(game_gadget[i]);
17108 static void RedrawGameButtonsExt(boolean on_tape)
17112 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17113 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17114 RedrawGadget(game_gadget[i]);
17117 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
17122 gi->checked = state;
17125 static void RedrawSoundButtonGadget(int id)
17127 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
17128 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
17129 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
17130 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
17131 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
17132 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
17135 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
17136 RedrawGadget(game_gadget[id2]);
17139 void MapGameButtons(void)
17141 MapGameButtonsExt(FALSE);
17144 void UnmapGameButtons(void)
17146 UnmapGameButtonsExt(FALSE);
17149 void RedrawGameButtons(void)
17151 RedrawGameButtonsExt(FALSE);
17154 void MapGameButtonsOnTape(void)
17156 MapGameButtonsExt(TRUE);
17159 void UnmapGameButtonsOnTape(void)
17161 UnmapGameButtonsExt(TRUE);
17164 void RedrawGameButtonsOnTape(void)
17166 RedrawGameButtonsExt(TRUE);
17169 static void GameUndoRedoExt(void)
17171 ClearPlayerAction();
17173 tape.pausing = TRUE;
17176 UpdateAndDisplayGameControlValues();
17178 DrawCompleteVideoDisplay();
17179 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
17180 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
17181 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
17183 ModifyPauseButtons();
17188 static void GameUndo(int steps)
17190 if (!CheckEngineSnapshotList())
17193 int tape_property_bits = tape.property_bits;
17195 LoadEngineSnapshot_Undo(steps);
17197 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17202 static void GameRedo(int steps)
17204 if (!CheckEngineSnapshotList())
17207 int tape_property_bits = tape.property_bits;
17209 LoadEngineSnapshot_Redo(steps);
17211 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17216 static void HandleGameButtonsExt(int id, int button)
17218 static boolean game_undo_executed = FALSE;
17219 int steps = BUTTON_STEPSIZE(button);
17220 boolean handle_game_buttons =
17221 (game_status == GAME_MODE_PLAYING ||
17222 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
17224 if (!handle_game_buttons)
17229 case GAME_CTRL_ID_STOP:
17230 case GAME_CTRL_ID_PANEL_STOP:
17231 case GAME_CTRL_ID_TOUCH_STOP:
17236 case GAME_CTRL_ID_PAUSE:
17237 case GAME_CTRL_ID_PAUSE2:
17238 case GAME_CTRL_ID_PANEL_PAUSE:
17239 case GAME_CTRL_ID_TOUCH_PAUSE:
17240 if (network.enabled && game_status == GAME_MODE_PLAYING)
17243 SendToServer_ContinuePlaying();
17245 SendToServer_PausePlaying();
17248 TapeTogglePause(TAPE_TOGGLE_MANUAL);
17250 game_undo_executed = FALSE;
17254 case GAME_CTRL_ID_PLAY:
17255 case GAME_CTRL_ID_PANEL_PLAY:
17256 if (game_status == GAME_MODE_MAIN)
17258 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
17260 else if (tape.pausing)
17262 if (network.enabled)
17263 SendToServer_ContinuePlaying();
17265 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
17269 case GAME_CTRL_ID_UNDO:
17270 // Important: When using "save snapshot when collecting an item" mode,
17271 // load last (current) snapshot for first "undo" after pressing "pause"
17272 // (else the last-but-one snapshot would be loaded, because the snapshot
17273 // pointer already points to the last snapshot when pressing "pause",
17274 // which is fine for "every step/move" mode, but not for "every collect")
17275 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
17276 !game_undo_executed)
17279 game_undo_executed = TRUE;
17284 case GAME_CTRL_ID_REDO:
17288 case GAME_CTRL_ID_SAVE:
17292 case GAME_CTRL_ID_LOAD:
17296 case GAME_CTRL_ID_RESTART:
17297 case GAME_CTRL_ID_PANEL_RESTART:
17298 case GAME_CTRL_ID_TOUCH_RESTART:
17303 case SOUND_CTRL_ID_MUSIC:
17304 case SOUND_CTRL_ID_PANEL_MUSIC:
17305 if (setup.sound_music)
17307 setup.sound_music = FALSE;
17311 else if (audio.music_available)
17313 setup.sound = setup.sound_music = TRUE;
17315 SetAudioMode(setup.sound);
17317 if (game_status == GAME_MODE_PLAYING)
17321 RedrawSoundButtonGadget(id);
17325 case SOUND_CTRL_ID_LOOPS:
17326 case SOUND_CTRL_ID_PANEL_LOOPS:
17327 if (setup.sound_loops)
17328 setup.sound_loops = FALSE;
17329 else if (audio.loops_available)
17331 setup.sound = setup.sound_loops = TRUE;
17333 SetAudioMode(setup.sound);
17336 RedrawSoundButtonGadget(id);
17340 case SOUND_CTRL_ID_SIMPLE:
17341 case SOUND_CTRL_ID_PANEL_SIMPLE:
17342 if (setup.sound_simple)
17343 setup.sound_simple = FALSE;
17344 else if (audio.sound_available)
17346 setup.sound = setup.sound_simple = TRUE;
17348 SetAudioMode(setup.sound);
17351 RedrawSoundButtonGadget(id);
17360 static void HandleGameButtons(struct GadgetInfo *gi)
17362 HandleGameButtonsExt(gi->custom_id, gi->event.button);
17365 void HandleSoundButtonKeys(Key key)
17367 if (key == setup.shortcut.sound_simple)
17368 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
17369 else if (key == setup.shortcut.sound_loops)
17370 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
17371 else if (key == setup.shortcut.sound_music)
17372 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);