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;
5165 // Important note: This function is not only called after "GameWon()", but also after
5166 // "game over" (if automatically asking for restarting the game is disabled in setup)
5168 // do not handle game end if request dialog is already active
5169 if (checkRequestActive())
5172 if (game.LevelSolved)
5173 game.LevelSolved_GameEnd = TRUE;
5175 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5177 // make sure that request dialog to save tape does not open door again
5178 if (!global.use_envelope_request)
5179 CloseDoor(DOOR_CLOSE_1);
5182 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5184 // set unique basename for score tape (also saved in high score table)
5185 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5188 // if no tape is to be saved, close both doors simultaneously
5189 CloseDoor(DOOR_CLOSE_ALL);
5191 if (level_editor_test_game || score_info_tape_play)
5193 SetGameStatus(GAME_MODE_MAIN);
5200 if (!game.GamePlayed || (!game.LevelSolved_SaveScore && !level.bd_intermission))
5202 SetGameStatus(GAME_MODE_MAIN);
5209 if (level_nr == leveldir_current->handicap_level)
5211 leveldir_current->handicap_level++;
5213 SaveLevelSetup_SeriesInfo();
5216 // save score and score tape before potentially erasing tape below
5217 if (game.LevelSolved_SaveScore)
5218 NewHighScore(last_level_nr, tape_saved);
5220 // increment and load next level (if possible and not configured otherwise)
5221 AdvanceToNextLevel();
5223 if (game.LevelSolved_SaveScore && scores.last_added >= 0 && setup.show_scores_after_game)
5225 SetGameStatus(GAME_MODE_SCORES);
5227 DrawHallOfFame(last_level_nr);
5229 else if (scores.continue_playing)
5231 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5235 SetGameStatus(GAME_MODE_MAIN);
5241 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5242 boolean one_score_entry_per_name)
5246 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5249 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5251 struct ScoreEntry *entry = &list->entry[i];
5252 boolean score_is_better = (new_entry->score > entry->score);
5253 boolean score_is_equal = (new_entry->score == entry->score);
5254 boolean time_is_better = (new_entry->time < entry->time);
5255 boolean time_is_equal = (new_entry->time == entry->time);
5256 boolean better_by_score = (score_is_better ||
5257 (score_is_equal && time_is_better));
5258 boolean better_by_time = (time_is_better ||
5259 (time_is_equal && score_is_better));
5260 boolean is_better = (level.rate_time_over_score ? better_by_time :
5262 boolean entry_is_empty = (entry->score == 0 &&
5265 // prevent adding server score entries if also existing in local score file
5266 // (special case: historic score entries have an empty tape basename entry)
5267 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5268 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5270 // add fields from server score entry not stored in local score entry
5271 // (currently, this means setting platform, version and country fields;
5272 // in rare cases, this may also correct an invalid score value, as
5273 // historic scores might have been truncated to 16-bit values locally)
5274 *entry = *new_entry;
5279 if (is_better || entry_is_empty)
5281 // player has made it to the hall of fame
5283 if (i < MAX_SCORE_ENTRIES - 1)
5285 int m = MAX_SCORE_ENTRIES - 1;
5288 if (one_score_entry_per_name)
5290 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5291 if (strEqual(list->entry[l].name, new_entry->name))
5294 if (m == i) // player's new highscore overwrites his old one
5298 for (l = m; l > i; l--)
5299 list->entry[l] = list->entry[l - 1];
5304 *entry = *new_entry;
5308 else if (one_score_entry_per_name &&
5309 strEqual(entry->name, new_entry->name))
5311 // player already in high score list with better score or time
5317 // special case: new score is beyond the last high score list position
5318 return MAX_SCORE_ENTRIES;
5321 void NewHighScore(int level_nr, boolean tape_saved)
5323 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5324 boolean one_per_name = FALSE;
5326 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5327 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5329 new_entry.score = game.score_final;
5330 new_entry.time = game.score_time_final;
5332 LoadScore(level_nr);
5334 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5336 if (scores.last_added >= MAX_SCORE_ENTRIES)
5338 scores.last_added = MAX_SCORE_ENTRIES - 1;
5339 scores.force_last_added = TRUE;
5341 scores.entry[scores.last_added] = new_entry;
5343 // store last added local score entry (before merging server scores)
5344 scores.last_added_local = scores.last_added;
5349 if (scores.last_added < 0)
5352 SaveScore(level_nr);
5354 // store last added local score entry (before merging server scores)
5355 scores.last_added_local = scores.last_added;
5357 if (!game.LevelSolved_SaveTape)
5360 SaveScoreTape(level_nr);
5362 if (setup.ask_for_using_api_server)
5364 setup.use_api_server =
5365 Request("Upload your score and tape to the high score server?", REQ_ASK);
5367 if (!setup.use_api_server)
5368 Request("Not using high score server! Use setup menu to enable again!",
5371 runtime.use_api_server = setup.use_api_server;
5373 // after asking for using API server once, do not ask again
5374 setup.ask_for_using_api_server = FALSE;
5376 SaveSetup_ServerSetup();
5379 SaveServerScore(level_nr, tape_saved);
5382 void MergeServerScore(void)
5384 struct ScoreEntry last_added_entry;
5385 boolean one_per_name = FALSE;
5388 if (scores.last_added >= 0)
5389 last_added_entry = scores.entry[scores.last_added];
5391 for (i = 0; i < server_scores.num_entries; i++)
5393 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5395 if (pos >= 0 && pos <= scores.last_added)
5396 scores.last_added++;
5399 if (scores.last_added >= MAX_SCORE_ENTRIES)
5401 scores.last_added = MAX_SCORE_ENTRIES - 1;
5402 scores.force_last_added = TRUE;
5404 scores.entry[scores.last_added] = last_added_entry;
5408 static int getElementMoveStepsizeExt(int x, int y, int direction)
5410 int element = Tile[x][y];
5411 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5412 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5413 int horiz_move = (dx != 0);
5414 int sign = (horiz_move ? dx : dy);
5415 int step = sign * element_info[element].move_stepsize;
5417 // special values for move stepsize for spring and things on conveyor belt
5420 if (CAN_FALL(element) &&
5421 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5422 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5423 else if (element == EL_SPRING)
5424 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5430 static int getElementMoveStepsize(int x, int y)
5432 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5435 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5437 if (player->GfxAction != action || player->GfxDir != dir)
5439 player->GfxAction = action;
5440 player->GfxDir = dir;
5442 player->StepFrame = 0;
5446 static void ResetGfxFrame(int x, int y)
5448 // profiling showed that "autotest" spends 10~20% of its time in this function
5449 if (DrawingDeactivatedField())
5452 int element = Tile[x][y];
5453 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5455 if (graphic_info[graphic].anim_global_sync)
5456 GfxFrame[x][y] = FrameCounter;
5457 else if (graphic_info[graphic].anim_global_anim_sync)
5458 GfxFrame[x][y] = getGlobalAnimSyncFrame();
5459 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5460 GfxFrame[x][y] = CustomValue[x][y];
5461 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5462 GfxFrame[x][y] = element_info[element].collect_score;
5463 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5464 GfxFrame[x][y] = ChangeDelay[x][y];
5467 static void ResetGfxAnimation(int x, int y)
5469 GfxAction[x][y] = ACTION_DEFAULT;
5470 GfxDir[x][y] = MovDir[x][y];
5473 ResetGfxFrame(x, y);
5476 static void ResetRandomAnimationValue(int x, int y)
5478 GfxRandom[x][y] = INIT_GFX_RANDOM();
5481 static void InitMovingField(int x, int y, int direction)
5483 int element = Tile[x][y];
5484 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5485 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5488 boolean is_moving_before, is_moving_after;
5490 // check if element was/is moving or being moved before/after mode change
5491 is_moving_before = (WasJustMoving[x][y] != 0);
5492 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5494 // reset animation only for moving elements which change direction of moving
5495 // or which just started or stopped moving
5496 // (else CEs with property "can move" / "not moving" are reset each frame)
5497 if (is_moving_before != is_moving_after ||
5498 direction != MovDir[x][y])
5499 ResetGfxAnimation(x, y);
5501 MovDir[x][y] = direction;
5502 GfxDir[x][y] = direction;
5504 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5505 direction == MV_DOWN && CAN_FALL(element) ?
5506 ACTION_FALLING : ACTION_MOVING);
5508 // this is needed for CEs with property "can move" / "not moving"
5510 if (is_moving_after)
5512 if (Tile[newx][newy] == EL_EMPTY)
5513 Tile[newx][newy] = EL_BLOCKED;
5515 MovDir[newx][newy] = MovDir[x][y];
5517 CustomValue[newx][newy] = CustomValue[x][y];
5519 GfxFrame[newx][newy] = GfxFrame[x][y];
5520 GfxRandom[newx][newy] = GfxRandom[x][y];
5521 GfxAction[newx][newy] = GfxAction[x][y];
5522 GfxDir[newx][newy] = GfxDir[x][y];
5526 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5528 int direction = MovDir[x][y];
5529 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5530 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5536 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5538 int direction = MovDir[x][y];
5539 int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0);
5540 int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0);
5542 *comes_from_x = oldx;
5543 *comes_from_y = oldy;
5546 static int MovingOrBlocked2Element(int x, int y)
5548 int element = Tile[x][y];
5550 if (element == EL_BLOCKED)
5554 Blocked2Moving(x, y, &oldx, &oldy);
5556 return Tile[oldx][oldy];
5562 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5564 // like MovingOrBlocked2Element(), but if element is moving
5565 // and (x, y) is the field the moving element is just leaving,
5566 // return EL_BLOCKED instead of the element value
5567 int element = Tile[x][y];
5569 if (IS_MOVING(x, y))
5571 if (element == EL_BLOCKED)
5575 Blocked2Moving(x, y, &oldx, &oldy);
5576 return Tile[oldx][oldy];
5585 static void RemoveField(int x, int y)
5587 Tile[x][y] = EL_EMPTY;
5593 CustomValue[x][y] = 0;
5596 ChangeDelay[x][y] = 0;
5597 ChangePage[x][y] = -1;
5598 Pushed[x][y] = FALSE;
5600 GfxElement[x][y] = EL_UNDEFINED;
5601 GfxAction[x][y] = ACTION_DEFAULT;
5602 GfxDir[x][y] = MV_NONE;
5605 static void RemoveMovingField(int x, int y)
5607 int oldx = x, oldy = y, newx = x, newy = y;
5608 int element = Tile[x][y];
5609 int next_element = EL_UNDEFINED;
5611 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5614 if (IS_MOVING(x, y))
5616 Moving2Blocked(x, y, &newx, &newy);
5618 if (Tile[newx][newy] != EL_BLOCKED)
5620 // element is moving, but target field is not free (blocked), but
5621 // already occupied by something different (example: acid pool);
5622 // in this case, only remove the moving field, but not the target
5624 RemoveField(oldx, oldy);
5626 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5628 TEST_DrawLevelField(oldx, oldy);
5633 else if (element == EL_BLOCKED)
5635 Blocked2Moving(x, y, &oldx, &oldy);
5636 if (!IS_MOVING(oldx, oldy))
5640 if (element == EL_BLOCKED &&
5641 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5642 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5643 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5644 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5645 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5646 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5647 next_element = get_next_element(Tile[oldx][oldy]);
5649 RemoveField(oldx, oldy);
5650 RemoveField(newx, newy);
5652 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5654 if (next_element != EL_UNDEFINED)
5655 Tile[oldx][oldy] = next_element;
5657 TEST_DrawLevelField(oldx, oldy);
5658 TEST_DrawLevelField(newx, newy);
5661 void DrawDynamite(int x, int y)
5663 int sx = SCREENX(x), sy = SCREENY(y);
5664 int graphic = el2img(Tile[x][y]);
5667 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5670 if (IS_WALKABLE_INSIDE(Back[x][y]))
5674 DrawLevelElement(x, y, Back[x][y]);
5675 else if (Store[x][y])
5676 DrawLevelElement(x, y, Store[x][y]);
5677 else if (game.use_masked_elements)
5678 DrawLevelElement(x, y, EL_EMPTY);
5680 frame = getGraphicAnimationFrameXY(graphic, x, y);
5682 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5683 DrawGraphicThruMask(sx, sy, graphic, frame);
5685 DrawGraphic(sx, sy, graphic, frame);
5688 static void CheckDynamite(int x, int y)
5690 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5694 if (MovDelay[x][y] != 0)
5697 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5703 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5708 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5710 boolean num_checked_players = 0;
5713 for (i = 0; i < MAX_PLAYERS; i++)
5715 if (stored_player[i].active)
5717 int sx = stored_player[i].jx;
5718 int sy = stored_player[i].jy;
5720 if (num_checked_players == 0)
5727 *sx1 = MIN(*sx1, sx);
5728 *sy1 = MIN(*sy1, sy);
5729 *sx2 = MAX(*sx2, sx);
5730 *sy2 = MAX(*sy2, sy);
5733 num_checked_players++;
5738 static boolean checkIfAllPlayersFitToScreen_RND(void)
5740 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5742 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5744 return (sx2 - sx1 < SCR_FIELDX &&
5745 sy2 - sy1 < SCR_FIELDY);
5748 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5750 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5752 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5754 *sx = (sx1 + sx2) / 2;
5755 *sy = (sy1 + sy2) / 2;
5758 static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
5759 boolean center_screen, boolean quick_relocation)
5761 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5762 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5763 boolean no_delay = (tape.warp_forward);
5764 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5765 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5766 int new_scroll_x, new_scroll_y;
5768 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5770 // case 1: quick relocation inside visible screen (without scrolling)
5777 if (!level.shifted_relocation || center_screen)
5779 // relocation _with_ centering of screen
5781 new_scroll_x = SCROLL_POSITION_X(x);
5782 new_scroll_y = SCROLL_POSITION_Y(y);
5786 // relocation _without_ centering of screen
5788 // apply distance between old and new player position to scroll position
5789 int shifted_scroll_x = scroll_x + (x - old_x);
5790 int shifted_scroll_y = scroll_y + (y - old_y);
5792 // make sure that shifted scroll position does not scroll beyond screen
5793 new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
5794 new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
5796 // special case for teleporting from one end of the playfield to the other
5797 // (this kludge prevents the destination area to be shifted by half a tile
5798 // against the source destination for even screen width or screen height;
5799 // probably most useful when used with high "game.forced_scroll_delay_value"
5800 // in combination with "game.forced_scroll_x" and "game.forced_scroll_y")
5801 if (quick_relocation)
5803 if (EVEN(SCR_FIELDX))
5805 // relocate (teleport) between left and right border (half or full)
5806 if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1)
5807 new_scroll_x = SBX_Right;
5808 else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right)
5809 new_scroll_x = SBX_Right - 1;
5810 else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1)
5811 new_scroll_x = SBX_Left;
5812 else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left)
5813 new_scroll_x = SBX_Left + 1;
5816 if (EVEN(SCR_FIELDY))
5818 // relocate (teleport) between top and bottom border (half or full)
5819 if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1)
5820 new_scroll_y = SBY_Lower;
5821 else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower)
5822 new_scroll_y = SBY_Lower - 1;
5823 else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1)
5824 new_scroll_y = SBY_Upper;
5825 else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper)
5826 new_scroll_y = SBY_Upper + 1;
5831 if (quick_relocation)
5833 // case 2: quick relocation (redraw without visible scrolling)
5835 scroll_x = new_scroll_x;
5836 scroll_y = new_scroll_y;
5843 // case 3: visible relocation (with scrolling to new position)
5845 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5847 SetVideoFrameDelay(wait_delay_value);
5849 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5851 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5852 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5854 if (dx == 0 && dy == 0) // no scrolling needed at all
5860 // set values for horizontal/vertical screen scrolling (half tile size)
5861 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5862 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5863 int pos_x = dx * TILEX / 2;
5864 int pos_y = dy * TILEY / 2;
5865 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5866 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5868 ScrollLevel(dx, dy);
5871 // scroll in two steps of half tile size to make things smoother
5872 BlitScreenToBitmapExt_RND(window, fx, fy);
5874 // scroll second step to align at full tile size
5875 BlitScreenToBitmap(window);
5881 SetVideoFrameDelay(frame_delay_value_old);
5884 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5886 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5887 int player_nr = GET_PLAYER_NR(el_player);
5888 struct PlayerInfo *player = &stored_player[player_nr];
5889 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5890 boolean no_delay = (tape.warp_forward);
5891 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5892 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5893 int old_jx = player->jx;
5894 int old_jy = player->jy;
5895 int old_element = Tile[old_jx][old_jy];
5896 int element = Tile[jx][jy];
5897 boolean player_relocated = (old_jx != jx || old_jy != jy);
5899 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5900 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5901 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5902 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5903 int leave_side_horiz = move_dir_horiz;
5904 int leave_side_vert = move_dir_vert;
5905 int enter_side = enter_side_horiz | enter_side_vert;
5906 int leave_side = leave_side_horiz | leave_side_vert;
5908 if (player->buried) // do not reanimate dead player
5911 if (!player_relocated) // no need to relocate the player
5914 if (IS_PLAYER(jx, jy)) // player already placed at new position
5916 RemoveField(jx, jy); // temporarily remove newly placed player
5917 DrawLevelField(jx, jy);
5920 if (player->present)
5922 while (player->MovPos)
5924 ScrollPlayer(player, SCROLL_GO_ON);
5925 ScrollScreen(NULL, SCROLL_GO_ON);
5927 AdvanceFrameAndPlayerCounters(player->index_nr);
5931 BackToFront_WithFrameDelay(wait_delay_value);
5934 DrawPlayer(player); // needed here only to cleanup last field
5935 DrawLevelField(player->jx, player->jy); // remove player graphic
5937 player->is_moving = FALSE;
5940 if (IS_CUSTOM_ELEMENT(old_element))
5941 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5943 player->index_bit, leave_side);
5945 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5947 player->index_bit, leave_side);
5949 Tile[jx][jy] = el_player;
5950 InitPlayerField(jx, jy, el_player, TRUE);
5952 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5953 possible that the relocation target field did not contain a player element,
5954 but a walkable element, to which the new player was relocated -- in this
5955 case, restore that (already initialized!) element on the player field */
5956 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5958 Tile[jx][jy] = element; // restore previously existing element
5961 // only visually relocate centered player
5962 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy,
5963 FALSE, level.instant_relocation);
5965 TestIfPlayerTouchesBadThing(jx, jy);
5966 TestIfPlayerTouchesCustomElement(jx, jy);
5968 if (IS_CUSTOM_ELEMENT(element))
5969 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5970 player->index_bit, enter_side);
5972 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5973 player->index_bit, enter_side);
5975 if (player->is_switching)
5977 /* ensure that relocation while still switching an element does not cause
5978 a new element to be treated as also switched directly after relocation
5979 (this is important for teleporter switches that teleport the player to
5980 a place where another teleporter switch is in the same direction, which
5981 would then incorrectly be treated as immediately switched before the
5982 direction key that caused the switch was released) */
5984 player->switch_x += jx - old_jx;
5985 player->switch_y += jy - old_jy;
5989 static void Explode(int ex, int ey, int phase, int mode)
5995 if (game.explosions_delayed)
5997 ExplodeField[ex][ey] = mode;
6001 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
6003 int center_element = Tile[ex][ey];
6004 int ce_value = CustomValue[ex][ey];
6005 int ce_score = element_info[center_element].collect_score;
6006 int artwork_element, explosion_element; // set these values later
6008 // remove things displayed in background while burning dynamite
6009 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
6012 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
6014 // put moving element to center field (and let it explode there)
6015 center_element = MovingOrBlocked2Element(ex, ey);
6016 RemoveMovingField(ex, ey);
6017 Tile[ex][ey] = center_element;
6020 // now "center_element" is finally determined -- set related values now
6021 artwork_element = center_element; // for custom player artwork
6022 explosion_element = center_element; // for custom player artwork
6024 if (IS_PLAYER(ex, ey))
6026 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
6028 artwork_element = stored_player[player_nr].artwork_element;
6030 if (level.use_explosion_element[player_nr])
6032 explosion_element = level.explosion_element[player_nr];
6033 artwork_element = explosion_element;
6037 if (mode == EX_TYPE_NORMAL ||
6038 mode == EX_TYPE_CENTER ||
6039 mode == EX_TYPE_CROSS)
6040 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
6042 last_phase = element_info[explosion_element].explosion_delay + 1;
6044 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
6046 int xx = x - ex + 1;
6047 int yy = y - ey + 1;
6050 if (!IN_LEV_FIELD(x, y) ||
6051 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
6052 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
6055 element = Tile[x][y];
6057 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
6059 element = MovingOrBlocked2Element(x, y);
6061 if (!IS_EXPLOSION_PROOF(element))
6062 RemoveMovingField(x, y);
6065 // indestructible elements can only explode in center (but not flames)
6066 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
6067 mode == EX_TYPE_BORDER)) ||
6068 element == EL_FLAMES)
6071 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
6072 behaviour, for example when touching a yamyam that explodes to rocks
6073 with active deadly shield, a rock is created under the player !!! */
6074 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
6076 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
6077 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
6078 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
6080 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
6083 if (IS_ACTIVE_BOMB(element))
6085 // re-activate things under the bomb like gate or penguin
6086 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
6093 // save walkable background elements while explosion on same tile
6094 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
6095 (x != ex || y != ey || mode == EX_TYPE_BORDER))
6096 Back[x][y] = element;
6098 // ignite explodable elements reached by other explosion
6099 if (element == EL_EXPLOSION)
6100 element = Store2[x][y];
6102 if (AmoebaNr[x][y] &&
6103 (element == EL_AMOEBA_FULL ||
6104 element == EL_BD_AMOEBA ||
6105 element == EL_AMOEBA_GROWING))
6107 AmoebaCnt[AmoebaNr[x][y]]--;
6108 AmoebaCnt2[AmoebaNr[x][y]]--;
6113 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
6115 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
6117 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
6119 if (PLAYERINFO(ex, ey)->use_murphy)
6120 Store[x][y] = EL_EMPTY;
6123 // !!! check this case -- currently needed for rnd_rado_negundo_v,
6124 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
6125 else if (IS_PLAYER_ELEMENT(center_element))
6126 Store[x][y] = EL_EMPTY;
6127 else if (center_element == EL_YAMYAM)
6128 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
6129 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
6130 Store[x][y] = element_info[center_element].content.e[xx][yy];
6132 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
6133 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
6134 // otherwise) -- FIX THIS !!!
6135 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
6136 Store[x][y] = element_info[element].content.e[1][1];
6138 else if (!CAN_EXPLODE(element))
6139 Store[x][y] = element_info[element].content.e[1][1];
6142 Store[x][y] = EL_EMPTY;
6144 if (IS_CUSTOM_ELEMENT(center_element))
6145 Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value :
6146 Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score :
6147 Store[x][y] >= EL_PREV_CE_8 &&
6148 Store[x][y] <= EL_NEXT_CE_8 ?
6149 RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) :
6152 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
6153 center_element == EL_AMOEBA_TO_DIAMOND)
6154 Store2[x][y] = element;
6156 Tile[x][y] = EL_EXPLOSION;
6157 GfxElement[x][y] = artwork_element;
6159 ExplodePhase[x][y] = 1;
6160 ExplodeDelay[x][y] = last_phase;
6165 if (center_element == EL_YAMYAM)
6166 game.yamyam_content_nr =
6167 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
6179 GfxFrame[x][y] = 0; // restart explosion animation
6181 last_phase = ExplodeDelay[x][y];
6183 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
6185 // this can happen if the player leaves an explosion just in time
6186 if (GfxElement[x][y] == EL_UNDEFINED)
6187 GfxElement[x][y] = EL_EMPTY;
6189 border_element = Store2[x][y];
6190 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6191 border_element = StorePlayer[x][y];
6193 if (phase == element_info[border_element].ignition_delay ||
6194 phase == last_phase)
6196 boolean border_explosion = FALSE;
6198 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6199 !PLAYER_EXPLOSION_PROTECTED(x, y))
6201 KillPlayerUnlessExplosionProtected(x, y);
6202 border_explosion = TRUE;
6204 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6206 Tile[x][y] = Store2[x][y];
6209 border_explosion = TRUE;
6211 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6213 AmoebaToDiamond(x, y);
6215 border_explosion = TRUE;
6218 // if an element just explodes due to another explosion (chain-reaction),
6219 // do not immediately end the new explosion when it was the last frame of
6220 // the explosion (as it would be done in the following "if"-statement!)
6221 if (border_explosion && phase == last_phase)
6225 // this can happen if the player was just killed by an explosion
6226 if (GfxElement[x][y] == EL_UNDEFINED)
6227 GfxElement[x][y] = EL_EMPTY;
6229 if (phase == last_phase)
6233 element = Tile[x][y] = Store[x][y];
6234 Store[x][y] = Store2[x][y] = 0;
6235 GfxElement[x][y] = EL_UNDEFINED;
6237 // player can escape from explosions and might therefore be still alive
6238 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6239 element <= EL_PLAYER_IS_EXPLODING_4)
6241 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6242 int explosion_element = EL_PLAYER_1 + player_nr;
6243 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6244 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6246 if (level.use_explosion_element[player_nr])
6247 explosion_element = level.explosion_element[player_nr];
6249 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6250 element_info[explosion_element].content.e[xx][yy]);
6253 // restore probably existing indestructible background element
6254 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6255 element = Tile[x][y] = Back[x][y];
6258 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6259 GfxDir[x][y] = MV_NONE;
6260 ChangeDelay[x][y] = 0;
6261 ChangePage[x][y] = -1;
6263 CustomValue[x][y] = 0;
6265 InitField_WithBug2(x, y, FALSE);
6267 TEST_DrawLevelField(x, y);
6269 TestIfElementTouchesCustomElement(x, y);
6271 if (GFX_CRUMBLED(element))
6272 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6274 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6275 StorePlayer[x][y] = 0;
6277 if (IS_PLAYER_ELEMENT(element))
6278 RelocatePlayer(x, y, element);
6280 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6282 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6283 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6286 TEST_DrawLevelFieldCrumbled(x, y);
6288 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6290 DrawLevelElement(x, y, Back[x][y]);
6291 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6293 else if (IS_WALKABLE_UNDER(Back[x][y]))
6295 DrawLevelGraphic(x, y, graphic, frame);
6296 DrawLevelElementThruMask(x, y, Back[x][y]);
6298 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6299 DrawLevelGraphic(x, y, graphic, frame);
6303 static void DynaExplode(int ex, int ey)
6306 int dynabomb_element = Tile[ex][ey];
6307 int dynabomb_size = 1;
6308 boolean dynabomb_xl = FALSE;
6309 struct PlayerInfo *player;
6310 struct XY *xy = xy_topdown;
6312 if (IS_ACTIVE_BOMB(dynabomb_element))
6314 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6315 dynabomb_size = player->dynabomb_size;
6316 dynabomb_xl = player->dynabomb_xl;
6317 player->dynabombs_left++;
6320 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6322 for (i = 0; i < NUM_DIRECTIONS; i++)
6324 for (j = 1; j <= dynabomb_size; j++)
6326 int x = ex + j * xy[i].x;
6327 int y = ey + j * xy[i].y;
6330 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6333 element = Tile[x][y];
6335 // do not restart explosions of fields with active bombs
6336 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6339 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6341 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6342 !IS_DIGGABLE(element) && !dynabomb_xl)
6348 void Bang(int x, int y)
6350 int element = MovingOrBlocked2Element(x, y);
6351 int explosion_type = EX_TYPE_NORMAL;
6353 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6355 struct PlayerInfo *player = PLAYERINFO(x, y);
6357 element = Tile[x][y] = player->initial_element;
6359 if (level.use_explosion_element[player->index_nr])
6361 int explosion_element = level.explosion_element[player->index_nr];
6363 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6364 explosion_type = EX_TYPE_CROSS;
6365 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6366 explosion_type = EX_TYPE_CENTER;
6374 case EL_BD_BUTTERFLY:
6377 case EL_DARK_YAMYAM:
6381 RaiseScoreElement(element);
6384 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6385 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6386 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6387 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6388 case EL_DYNABOMB_INCREASE_NUMBER:
6389 case EL_DYNABOMB_INCREASE_SIZE:
6390 case EL_DYNABOMB_INCREASE_POWER:
6391 explosion_type = EX_TYPE_DYNA;
6394 case EL_DC_LANDMINE:
6395 explosion_type = EX_TYPE_CENTER;
6400 case EL_LAMP_ACTIVE:
6401 case EL_AMOEBA_TO_DIAMOND:
6402 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6403 explosion_type = EX_TYPE_CENTER;
6407 if (element_info[element].explosion_type == EXPLODES_CROSS)
6408 explosion_type = EX_TYPE_CROSS;
6409 else if (element_info[element].explosion_type == EXPLODES_1X1)
6410 explosion_type = EX_TYPE_CENTER;
6414 if (explosion_type == EX_TYPE_DYNA)
6417 Explode(x, y, EX_PHASE_START, explosion_type);
6419 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6422 static void SplashAcid(int x, int y)
6424 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6425 (!IN_LEV_FIELD(x - 1, y - 2) ||
6426 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6427 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
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_RIGHT;
6434 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6437 static void InitBeltMovement(void)
6439 static int belt_base_element[4] =
6441 EL_CONVEYOR_BELT_1_LEFT,
6442 EL_CONVEYOR_BELT_2_LEFT,
6443 EL_CONVEYOR_BELT_3_LEFT,
6444 EL_CONVEYOR_BELT_4_LEFT
6446 static int belt_base_active_element[4] =
6448 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6449 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6450 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6451 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6456 // set frame order for belt animation graphic according to belt direction
6457 for (i = 0; i < NUM_BELTS; i++)
6461 for (j = 0; j < NUM_BELT_PARTS; j++)
6463 int element = belt_base_active_element[belt_nr] + j;
6464 int graphic_1 = el2img(element);
6465 int graphic_2 = el2panelimg(element);
6467 if (game.belt_dir[i] == MV_LEFT)
6469 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6470 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6474 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6475 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6480 SCAN_PLAYFIELD(x, y)
6482 int element = Tile[x][y];
6484 for (i = 0; i < NUM_BELTS; i++)
6486 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6488 int e_belt_nr = getBeltNrFromBeltElement(element);
6491 if (e_belt_nr == belt_nr)
6493 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6495 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6502 static void ToggleBeltSwitch(int x, int y)
6504 static int belt_base_element[4] =
6506 EL_CONVEYOR_BELT_1_LEFT,
6507 EL_CONVEYOR_BELT_2_LEFT,
6508 EL_CONVEYOR_BELT_3_LEFT,
6509 EL_CONVEYOR_BELT_4_LEFT
6511 static int belt_base_active_element[4] =
6513 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6514 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6515 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6516 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6518 static int belt_base_switch_element[4] =
6520 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6521 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6522 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6523 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6525 static int belt_move_dir[4] =
6533 int element = Tile[x][y];
6534 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6535 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6536 int belt_dir = belt_move_dir[belt_dir_nr];
6539 if (!IS_BELT_SWITCH(element))
6542 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6543 game.belt_dir[belt_nr] = belt_dir;
6545 if (belt_dir_nr == 3)
6548 // set frame order for belt animation graphic according to belt direction
6549 for (i = 0; i < NUM_BELT_PARTS; i++)
6551 int element = belt_base_active_element[belt_nr] + i;
6552 int graphic_1 = el2img(element);
6553 int graphic_2 = el2panelimg(element);
6555 if (belt_dir == MV_LEFT)
6557 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6558 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6562 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6563 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6567 SCAN_PLAYFIELD(xx, yy)
6569 int element = Tile[xx][yy];
6571 if (IS_BELT_SWITCH(element))
6573 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6575 if (e_belt_nr == belt_nr)
6577 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6578 TEST_DrawLevelField(xx, yy);
6581 else if (IS_BELT(element) && belt_dir != MV_NONE)
6583 int e_belt_nr = getBeltNrFromBeltElement(element);
6585 if (e_belt_nr == belt_nr)
6587 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6589 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6590 TEST_DrawLevelField(xx, yy);
6593 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6595 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6597 if (e_belt_nr == belt_nr)
6599 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6601 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6602 TEST_DrawLevelField(xx, yy);
6608 static void ToggleSwitchgateSwitch(void)
6612 game.switchgate_pos = !game.switchgate_pos;
6614 SCAN_PLAYFIELD(xx, yy)
6616 int element = Tile[xx][yy];
6618 if (element == EL_SWITCHGATE_SWITCH_UP)
6620 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6621 TEST_DrawLevelField(xx, yy);
6623 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6625 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6626 TEST_DrawLevelField(xx, yy);
6628 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6630 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6631 TEST_DrawLevelField(xx, yy);
6633 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6635 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6636 TEST_DrawLevelField(xx, yy);
6638 else if (element == EL_SWITCHGATE_OPEN ||
6639 element == EL_SWITCHGATE_OPENING)
6641 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6643 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6645 else if (element == EL_SWITCHGATE_CLOSED ||
6646 element == EL_SWITCHGATE_CLOSING)
6648 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6650 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6655 static int getInvisibleActiveFromInvisibleElement(int element)
6657 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6658 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6659 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6663 static int getInvisibleFromInvisibleActiveElement(int element)
6665 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6666 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6667 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6671 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6675 SCAN_PLAYFIELD(x, y)
6677 int element = Tile[x][y];
6679 if (element == EL_LIGHT_SWITCH &&
6680 game.light_time_left > 0)
6682 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6683 TEST_DrawLevelField(x, y);
6685 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6686 game.light_time_left == 0)
6688 Tile[x][y] = EL_LIGHT_SWITCH;
6689 TEST_DrawLevelField(x, y);
6691 else if (element == EL_EMC_DRIPPER &&
6692 game.light_time_left > 0)
6694 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6695 TEST_DrawLevelField(x, y);
6697 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6698 game.light_time_left == 0)
6700 Tile[x][y] = EL_EMC_DRIPPER;
6701 TEST_DrawLevelField(x, y);
6703 else if (element == EL_INVISIBLE_STEELWALL ||
6704 element == EL_INVISIBLE_WALL ||
6705 element == EL_INVISIBLE_SAND)
6707 if (game.light_time_left > 0)
6708 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6710 TEST_DrawLevelField(x, y);
6712 // uncrumble neighbour fields, if needed
6713 if (element == EL_INVISIBLE_SAND)
6714 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6716 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6717 element == EL_INVISIBLE_WALL_ACTIVE ||
6718 element == EL_INVISIBLE_SAND_ACTIVE)
6720 if (game.light_time_left == 0)
6721 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6723 TEST_DrawLevelField(x, y);
6725 // re-crumble neighbour fields, if needed
6726 if (element == EL_INVISIBLE_SAND)
6727 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6732 static void RedrawAllInvisibleElementsForLenses(void)
6736 SCAN_PLAYFIELD(x, y)
6738 int element = Tile[x][y];
6740 if (element == EL_EMC_DRIPPER &&
6741 game.lenses_time_left > 0)
6743 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6744 TEST_DrawLevelField(x, y);
6746 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6747 game.lenses_time_left == 0)
6749 Tile[x][y] = EL_EMC_DRIPPER;
6750 TEST_DrawLevelField(x, y);
6752 else if (element == EL_INVISIBLE_STEELWALL ||
6753 element == EL_INVISIBLE_WALL ||
6754 element == EL_INVISIBLE_SAND)
6756 if (game.lenses_time_left > 0)
6757 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6759 TEST_DrawLevelField(x, y);
6761 // uncrumble neighbour fields, if needed
6762 if (element == EL_INVISIBLE_SAND)
6763 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6765 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6766 element == EL_INVISIBLE_WALL_ACTIVE ||
6767 element == EL_INVISIBLE_SAND_ACTIVE)
6769 if (game.lenses_time_left == 0)
6770 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6772 TEST_DrawLevelField(x, y);
6774 // re-crumble neighbour fields, if needed
6775 if (element == EL_INVISIBLE_SAND)
6776 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6781 static void RedrawAllInvisibleElementsForMagnifier(void)
6785 SCAN_PLAYFIELD(x, y)
6787 int element = Tile[x][y];
6789 if (element == EL_EMC_FAKE_GRASS &&
6790 game.magnify_time_left > 0)
6792 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6793 TEST_DrawLevelField(x, y);
6795 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6796 game.magnify_time_left == 0)
6798 Tile[x][y] = EL_EMC_FAKE_GRASS;
6799 TEST_DrawLevelField(x, y);
6801 else if (IS_GATE_GRAY(element) &&
6802 game.magnify_time_left > 0)
6804 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6805 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6806 IS_EM_GATE_GRAY(element) ?
6807 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6808 IS_EMC_GATE_GRAY(element) ?
6809 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6810 IS_DC_GATE_GRAY(element) ?
6811 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6813 TEST_DrawLevelField(x, y);
6815 else if (IS_GATE_GRAY_ACTIVE(element) &&
6816 game.magnify_time_left == 0)
6818 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6819 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6820 IS_EM_GATE_GRAY_ACTIVE(element) ?
6821 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6822 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6823 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6824 IS_DC_GATE_GRAY_ACTIVE(element) ?
6825 EL_DC_GATE_WHITE_GRAY :
6827 TEST_DrawLevelField(x, y);
6832 static void ToggleLightSwitch(int x, int y)
6834 int element = Tile[x][y];
6836 game.light_time_left =
6837 (element == EL_LIGHT_SWITCH ?
6838 level.time_light * FRAMES_PER_SECOND : 0);
6840 RedrawAllLightSwitchesAndInvisibleElements();
6843 static void ActivateTimegateSwitch(int x, int y)
6847 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6849 SCAN_PLAYFIELD(xx, yy)
6851 int element = Tile[xx][yy];
6853 if (element == EL_TIMEGATE_CLOSED ||
6854 element == EL_TIMEGATE_CLOSING)
6856 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6857 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6861 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6863 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6864 TEST_DrawLevelField(xx, yy);
6870 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6871 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6874 static void Impact(int x, int y)
6876 boolean last_line = (y == lev_fieldy - 1);
6877 boolean object_hit = FALSE;
6878 boolean impact = (last_line || object_hit);
6879 int element = Tile[x][y];
6880 int smashed = EL_STEELWALL;
6882 if (!last_line) // check if element below was hit
6884 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6887 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6888 MovDir[x][y + 1] != MV_DOWN ||
6889 MovPos[x][y + 1] <= TILEY / 2));
6891 // do not smash moving elements that left the smashed field in time
6892 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6893 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6896 #if USE_QUICKSAND_IMPACT_BUGFIX
6897 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6899 RemoveMovingField(x, y + 1);
6900 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6901 Tile[x][y + 2] = EL_ROCK;
6902 TEST_DrawLevelField(x, y + 2);
6907 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6909 RemoveMovingField(x, y + 1);
6910 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6911 Tile[x][y + 2] = EL_ROCK;
6912 TEST_DrawLevelField(x, y + 2);
6919 smashed = MovingOrBlocked2Element(x, y + 1);
6921 impact = (last_line || object_hit);
6924 if (!last_line && smashed == EL_ACID) // element falls into acid
6926 SplashAcid(x, y + 1);
6930 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6931 // only reset graphic animation if graphic really changes after impact
6933 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6935 ResetGfxAnimation(x, y);
6936 TEST_DrawLevelField(x, y);
6939 if (impact && CAN_EXPLODE_IMPACT(element))
6944 else if (impact && element == EL_PEARL &&
6945 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6947 ResetGfxAnimation(x, y);
6949 Tile[x][y] = EL_PEARL_BREAKING;
6950 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6953 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6955 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6960 if (impact && element == EL_AMOEBA_DROP)
6962 if (object_hit && IS_PLAYER(x, y + 1))
6963 KillPlayerUnlessEnemyProtected(x, y + 1);
6964 else if (object_hit && smashed == EL_PENGUIN)
6968 Tile[x][y] = EL_AMOEBA_GROWING;
6969 Store[x][y] = EL_AMOEBA_WET;
6971 ResetRandomAnimationValue(x, y);
6976 if (object_hit) // check which object was hit
6978 if ((CAN_PASS_MAGIC_WALL(element) &&
6979 (smashed == EL_MAGIC_WALL ||
6980 smashed == EL_BD_MAGIC_WALL)) ||
6981 (CAN_PASS_DC_MAGIC_WALL(element) &&
6982 smashed == EL_DC_MAGIC_WALL))
6985 int activated_magic_wall =
6986 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6987 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6988 EL_DC_MAGIC_WALL_ACTIVE);
6990 // activate magic wall / mill
6991 SCAN_PLAYFIELD(xx, yy)
6993 if (Tile[xx][yy] == smashed)
6994 Tile[xx][yy] = activated_magic_wall;
6997 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6998 game.magic_wall_active = TRUE;
7000 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
7001 SND_MAGIC_WALL_ACTIVATING :
7002 smashed == EL_BD_MAGIC_WALL ?
7003 SND_BD_MAGIC_WALL_ACTIVATING :
7004 SND_DC_MAGIC_WALL_ACTIVATING));
7007 if (IS_PLAYER(x, y + 1))
7009 if (CAN_SMASH_PLAYER(element))
7011 KillPlayerUnlessEnemyProtected(x, y + 1);
7015 else if (smashed == EL_PENGUIN)
7017 if (CAN_SMASH_PLAYER(element))
7023 else if (element == EL_BD_DIAMOND)
7025 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
7031 else if (((element == EL_SP_INFOTRON ||
7032 element == EL_SP_ZONK) &&
7033 (smashed == EL_SP_SNIKSNAK ||
7034 smashed == EL_SP_ELECTRON ||
7035 smashed == EL_SP_DISK_ORANGE)) ||
7036 (element == EL_SP_INFOTRON &&
7037 smashed == EL_SP_DISK_YELLOW))
7042 else if (CAN_SMASH_EVERYTHING(element))
7044 if (IS_CLASSIC_ENEMY(smashed) ||
7045 CAN_EXPLODE_SMASHED(smashed))
7050 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
7052 if (smashed == EL_LAMP ||
7053 smashed == EL_LAMP_ACTIVE)
7058 else if (smashed == EL_NUT)
7060 Tile[x][y + 1] = EL_NUT_BREAKING;
7061 PlayLevelSound(x, y, SND_NUT_BREAKING);
7062 RaiseScoreElement(EL_NUT);
7065 else if (smashed == EL_PEARL)
7067 ResetGfxAnimation(x, y);
7069 Tile[x][y + 1] = EL_PEARL_BREAKING;
7070 PlayLevelSound(x, y, SND_PEARL_BREAKING);
7073 else if (smashed == EL_DIAMOND)
7075 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
7076 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
7079 else if (IS_BELT_SWITCH(smashed))
7081 ToggleBeltSwitch(x, y + 1);
7083 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
7084 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
7085 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
7086 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
7088 ToggleSwitchgateSwitch();
7090 else if (smashed == EL_LIGHT_SWITCH ||
7091 smashed == EL_LIGHT_SWITCH_ACTIVE)
7093 ToggleLightSwitch(x, y + 1);
7097 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7099 CheckElementChangeBySide(x, y + 1, smashed, element,
7100 CE_SWITCHED, CH_SIDE_TOP);
7101 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
7107 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7112 // play sound of magic wall / mill
7114 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7115 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
7116 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
7118 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7119 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
7120 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7121 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
7122 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7123 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
7128 // play sound of object that hits the ground
7129 if (last_line || object_hit)
7130 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
7133 static void TurnRoundExt(int x, int y)
7145 { 0, 0 }, { 0, 0 }, { 0, 0 },
7150 int left, right, back;
7154 { MV_DOWN, MV_UP, MV_RIGHT },
7155 { MV_UP, MV_DOWN, MV_LEFT },
7157 { MV_LEFT, MV_RIGHT, MV_DOWN },
7161 { MV_RIGHT, MV_LEFT, MV_UP }
7164 int element = Tile[x][y];
7165 int move_pattern = element_info[element].move_pattern;
7167 int old_move_dir = MovDir[x][y];
7168 int left_dir = turn[old_move_dir].left;
7169 int right_dir = turn[old_move_dir].right;
7170 int back_dir = turn[old_move_dir].back;
7172 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
7173 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
7174 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
7175 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
7177 int left_x = x + left_dx, left_y = y + left_dy;
7178 int right_x = x + right_dx, right_y = y + right_dy;
7179 int move_x = x + move_dx, move_y = y + move_dy;
7183 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
7185 TestIfBadThingTouchesOtherBadThing(x, y);
7187 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
7188 MovDir[x][y] = right_dir;
7189 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7190 MovDir[x][y] = left_dir;
7192 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7194 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7197 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7199 TestIfBadThingTouchesOtherBadThing(x, y);
7201 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7202 MovDir[x][y] = left_dir;
7203 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7204 MovDir[x][y] = right_dir;
7206 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7208 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7211 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7213 TestIfBadThingTouchesOtherBadThing(x, y);
7215 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7216 MovDir[x][y] = left_dir;
7217 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7218 MovDir[x][y] = right_dir;
7220 if (MovDir[x][y] != old_move_dir)
7223 else if (element == EL_YAMYAM)
7225 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7226 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7228 if (can_turn_left && can_turn_right)
7229 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7230 else if (can_turn_left)
7231 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7232 else if (can_turn_right)
7233 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7235 MovDir[x][y] = back_dir;
7237 MovDelay[x][y] = 16 + 16 * RND(3);
7239 else if (element == EL_DARK_YAMYAM)
7241 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7243 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7246 if (can_turn_left && can_turn_right)
7247 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7248 else if (can_turn_left)
7249 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7250 else if (can_turn_right)
7251 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7253 MovDir[x][y] = back_dir;
7255 MovDelay[x][y] = 16 + 16 * RND(3);
7257 else if (element == EL_PACMAN)
7259 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7260 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7262 if (can_turn_left && can_turn_right)
7263 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7264 else if (can_turn_left)
7265 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7266 else if (can_turn_right)
7267 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7269 MovDir[x][y] = back_dir;
7271 MovDelay[x][y] = 6 + RND(40);
7273 else if (element == EL_PIG)
7275 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7276 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7277 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7278 boolean should_turn_left, should_turn_right, should_move_on;
7280 int rnd = RND(rnd_value);
7282 should_turn_left = (can_turn_left &&
7284 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7285 y + back_dy + left_dy)));
7286 should_turn_right = (can_turn_right &&
7288 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7289 y + back_dy + right_dy)));
7290 should_move_on = (can_move_on &&
7293 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7294 y + move_dy + left_dy) ||
7295 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7296 y + move_dy + right_dy)));
7298 if (should_turn_left || should_turn_right || should_move_on)
7300 if (should_turn_left && should_turn_right && should_move_on)
7301 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7302 rnd < 2 * rnd_value / 3 ? right_dir :
7304 else if (should_turn_left && should_turn_right)
7305 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7306 else if (should_turn_left && should_move_on)
7307 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7308 else if (should_turn_right && should_move_on)
7309 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7310 else if (should_turn_left)
7311 MovDir[x][y] = left_dir;
7312 else if (should_turn_right)
7313 MovDir[x][y] = right_dir;
7314 else if (should_move_on)
7315 MovDir[x][y] = old_move_dir;
7317 else if (can_move_on && rnd > rnd_value / 8)
7318 MovDir[x][y] = old_move_dir;
7319 else if (can_turn_left && can_turn_right)
7320 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7321 else if (can_turn_left && rnd > rnd_value / 8)
7322 MovDir[x][y] = left_dir;
7323 else if (can_turn_right && rnd > rnd_value/8)
7324 MovDir[x][y] = right_dir;
7326 MovDir[x][y] = back_dir;
7328 xx = x + move_xy[MovDir[x][y]].dx;
7329 yy = y + move_xy[MovDir[x][y]].dy;
7331 if (!IN_LEV_FIELD(xx, yy) ||
7332 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7333 MovDir[x][y] = old_move_dir;
7337 else if (element == EL_DRAGON)
7339 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7340 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7341 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7343 int rnd = RND(rnd_value);
7345 if (can_move_on && rnd > rnd_value / 8)
7346 MovDir[x][y] = old_move_dir;
7347 else if (can_turn_left && can_turn_right)
7348 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7349 else if (can_turn_left && rnd > rnd_value / 8)
7350 MovDir[x][y] = left_dir;
7351 else if (can_turn_right && rnd > rnd_value / 8)
7352 MovDir[x][y] = right_dir;
7354 MovDir[x][y] = back_dir;
7356 xx = x + move_xy[MovDir[x][y]].dx;
7357 yy = y + move_xy[MovDir[x][y]].dy;
7359 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7360 MovDir[x][y] = old_move_dir;
7364 else if (element == EL_MOLE)
7366 boolean can_move_on =
7367 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7368 IS_AMOEBOID(Tile[move_x][move_y]) ||
7369 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7372 boolean can_turn_left =
7373 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7374 IS_AMOEBOID(Tile[left_x][left_y])));
7376 boolean can_turn_right =
7377 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7378 IS_AMOEBOID(Tile[right_x][right_y])));
7380 if (can_turn_left && can_turn_right)
7381 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7382 else if (can_turn_left)
7383 MovDir[x][y] = left_dir;
7385 MovDir[x][y] = right_dir;
7388 if (MovDir[x][y] != old_move_dir)
7391 else if (element == EL_BALLOON)
7393 MovDir[x][y] = game.wind_direction;
7396 else if (element == EL_SPRING)
7398 if (MovDir[x][y] & MV_HORIZONTAL)
7400 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7401 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7403 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7404 ResetGfxAnimation(move_x, move_y);
7405 TEST_DrawLevelField(move_x, move_y);
7407 MovDir[x][y] = back_dir;
7409 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7410 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7411 MovDir[x][y] = MV_NONE;
7416 else if (element == EL_ROBOT ||
7417 element == EL_SATELLITE ||
7418 element == EL_PENGUIN ||
7419 element == EL_EMC_ANDROID)
7421 int attr_x = -1, attr_y = -1;
7423 if (game.all_players_gone)
7425 attr_x = game.exit_x;
7426 attr_y = game.exit_y;
7432 for (i = 0; i < MAX_PLAYERS; i++)
7434 struct PlayerInfo *player = &stored_player[i];
7435 int jx = player->jx, jy = player->jy;
7437 if (!player->active)
7441 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7449 if (element == EL_ROBOT &&
7450 game.robot_wheel_x >= 0 &&
7451 game.robot_wheel_y >= 0 &&
7452 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7453 game.engine_version < VERSION_IDENT(3,1,0,0)))
7455 attr_x = game.robot_wheel_x;
7456 attr_y = game.robot_wheel_y;
7459 if (element == EL_PENGUIN)
7462 struct XY *xy = xy_topdown;
7464 for (i = 0; i < NUM_DIRECTIONS; i++)
7466 int ex = x + xy[i].x;
7467 int ey = y + xy[i].y;
7469 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7470 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7471 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7472 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7481 MovDir[x][y] = MV_NONE;
7483 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7484 else if (attr_x > x)
7485 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7487 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7488 else if (attr_y > y)
7489 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7491 if (element == EL_ROBOT)
7495 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7496 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7497 Moving2Blocked(x, y, &newx, &newy);
7499 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7500 MovDelay[x][y] = 8 + 8 * !RND(3);
7502 MovDelay[x][y] = 16;
7504 else if (element == EL_PENGUIN)
7510 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7512 boolean first_horiz = RND(2);
7513 int new_move_dir = MovDir[x][y];
7516 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7517 Moving2Blocked(x, y, &newx, &newy);
7519 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7523 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7524 Moving2Blocked(x, y, &newx, &newy);
7526 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7529 MovDir[x][y] = old_move_dir;
7533 else if (element == EL_SATELLITE)
7539 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7541 boolean first_horiz = RND(2);
7542 int new_move_dir = MovDir[x][y];
7545 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7546 Moving2Blocked(x, y, &newx, &newy);
7548 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7552 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7553 Moving2Blocked(x, y, &newx, &newy);
7555 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7558 MovDir[x][y] = old_move_dir;
7562 else if (element == EL_EMC_ANDROID)
7564 static int check_pos[16] =
7566 -1, // 0 => (invalid)
7569 -1, // 3 => (invalid)
7571 0, // 5 => MV_LEFT | MV_UP
7572 2, // 6 => MV_RIGHT | MV_UP
7573 -1, // 7 => (invalid)
7575 6, // 9 => MV_LEFT | MV_DOWN
7576 4, // 10 => MV_RIGHT | MV_DOWN
7577 -1, // 11 => (invalid)
7578 -1, // 12 => (invalid)
7579 -1, // 13 => (invalid)
7580 -1, // 14 => (invalid)
7581 -1, // 15 => (invalid)
7589 { -1, -1, MV_LEFT | MV_UP },
7591 { +1, -1, MV_RIGHT | MV_UP },
7592 { +1, 0, MV_RIGHT },
7593 { +1, +1, MV_RIGHT | MV_DOWN },
7595 { -1, +1, MV_LEFT | MV_DOWN },
7598 int start_pos, check_order;
7599 boolean can_clone = FALSE;
7602 // check if there is any free field around current position
7603 for (i = 0; i < 8; i++)
7605 int newx = x + check_xy[i].dx;
7606 int newy = y + check_xy[i].dy;
7608 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7616 if (can_clone) // randomly find an element to clone
7620 start_pos = check_pos[RND(8)];
7621 check_order = (RND(2) ? -1 : +1);
7623 for (i = 0; i < 8; i++)
7625 int pos_raw = start_pos + i * check_order;
7626 int pos = (pos_raw + 8) % 8;
7627 int newx = x + check_xy[pos].dx;
7628 int newy = y + check_xy[pos].dy;
7630 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7632 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7633 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7635 Store[x][y] = Tile[newx][newy];
7644 if (can_clone) // randomly find a direction to move
7648 start_pos = check_pos[RND(8)];
7649 check_order = (RND(2) ? -1 : +1);
7651 for (i = 0; i < 8; i++)
7653 int pos_raw = start_pos + i * check_order;
7654 int pos = (pos_raw + 8) % 8;
7655 int newx = x + check_xy[pos].dx;
7656 int newy = y + check_xy[pos].dy;
7657 int new_move_dir = check_xy[pos].dir;
7659 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7661 MovDir[x][y] = new_move_dir;
7662 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7671 if (can_clone) // cloning and moving successful
7674 // cannot clone -- try to move towards player
7676 start_pos = check_pos[MovDir[x][y] & 0x0f];
7677 check_order = (RND(2) ? -1 : +1);
7679 for (i = 0; i < 3; i++)
7681 // first check start_pos, then previous/next or (next/previous) pos
7682 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7683 int pos = (pos_raw + 8) % 8;
7684 int newx = x + check_xy[pos].dx;
7685 int newy = y + check_xy[pos].dy;
7686 int new_move_dir = check_xy[pos].dir;
7688 if (IS_PLAYER(newx, newy))
7691 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7693 MovDir[x][y] = new_move_dir;
7694 MovDelay[x][y] = level.android_move_time * 8 + 1;
7701 else if (move_pattern == MV_TURNING_LEFT ||
7702 move_pattern == MV_TURNING_RIGHT ||
7703 move_pattern == MV_TURNING_LEFT_RIGHT ||
7704 move_pattern == MV_TURNING_RIGHT_LEFT ||
7705 move_pattern == MV_TURNING_RANDOM ||
7706 move_pattern == MV_ALL_DIRECTIONS)
7708 boolean can_turn_left =
7709 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7710 boolean can_turn_right =
7711 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y);
7713 if (element_info[element].move_stepsize == 0) // "not moving"
7716 if (move_pattern == MV_TURNING_LEFT)
7717 MovDir[x][y] = left_dir;
7718 else if (move_pattern == MV_TURNING_RIGHT)
7719 MovDir[x][y] = right_dir;
7720 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7721 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7722 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7723 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7724 else if (move_pattern == MV_TURNING_RANDOM)
7725 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7726 can_turn_right && !can_turn_left ? right_dir :
7727 RND(2) ? left_dir : right_dir);
7728 else if (can_turn_left && can_turn_right)
7729 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7730 else if (can_turn_left)
7731 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7732 else if (can_turn_right)
7733 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7735 MovDir[x][y] = back_dir;
7737 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7739 else if (move_pattern == MV_HORIZONTAL ||
7740 move_pattern == MV_VERTICAL)
7742 if (move_pattern & old_move_dir)
7743 MovDir[x][y] = back_dir;
7744 else if (move_pattern == MV_HORIZONTAL)
7745 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7746 else if (move_pattern == MV_VERTICAL)
7747 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7749 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7751 else if (move_pattern & MV_ANY_DIRECTION)
7753 MovDir[x][y] = move_pattern;
7754 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7756 else if (move_pattern & MV_WIND_DIRECTION)
7758 MovDir[x][y] = game.wind_direction;
7759 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7761 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7763 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7764 MovDir[x][y] = left_dir;
7765 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7766 MovDir[x][y] = right_dir;
7768 if (MovDir[x][y] != old_move_dir)
7769 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7771 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7773 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7774 MovDir[x][y] = right_dir;
7775 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7776 MovDir[x][y] = left_dir;
7778 if (MovDir[x][y] != old_move_dir)
7779 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7781 else if (move_pattern == MV_TOWARDS_PLAYER ||
7782 move_pattern == MV_AWAY_FROM_PLAYER)
7784 int attr_x = -1, attr_y = -1;
7786 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7788 if (game.all_players_gone)
7790 attr_x = game.exit_x;
7791 attr_y = game.exit_y;
7797 for (i = 0; i < MAX_PLAYERS; i++)
7799 struct PlayerInfo *player = &stored_player[i];
7800 int jx = player->jx, jy = player->jy;
7802 if (!player->active)
7806 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7814 MovDir[x][y] = MV_NONE;
7816 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7817 else if (attr_x > x)
7818 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7820 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7821 else if (attr_y > y)
7822 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7824 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7826 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7828 boolean first_horiz = RND(2);
7829 int new_move_dir = MovDir[x][y];
7831 if (element_info[element].move_stepsize == 0) // "not moving"
7833 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7834 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7840 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7841 Moving2Blocked(x, y, &newx, &newy);
7843 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7847 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7848 Moving2Blocked(x, y, &newx, &newy);
7850 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7853 MovDir[x][y] = old_move_dir;
7856 else if (move_pattern == MV_WHEN_PUSHED ||
7857 move_pattern == MV_WHEN_DROPPED)
7859 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7860 MovDir[x][y] = MV_NONE;
7864 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7866 struct XY *test_xy = xy_topdown;
7867 static int test_dir[4] =
7874 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7875 int move_preference = -1000000; // start with very low preference
7876 int new_move_dir = MV_NONE;
7877 int start_test = RND(4);
7880 for (i = 0; i < NUM_DIRECTIONS; i++)
7882 int j = (start_test + i) % 4;
7883 int move_dir = test_dir[j];
7884 int move_dir_preference;
7886 xx = x + test_xy[j].x;
7887 yy = y + test_xy[j].y;
7889 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7890 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7892 new_move_dir = move_dir;
7897 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7900 move_dir_preference = -1 * RunnerVisit[xx][yy];
7901 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7902 move_dir_preference = PlayerVisit[xx][yy];
7904 if (move_dir_preference > move_preference)
7906 // prefer field that has not been visited for the longest time
7907 move_preference = move_dir_preference;
7908 new_move_dir = move_dir;
7910 else if (move_dir_preference == move_preference &&
7911 move_dir == old_move_dir)
7913 // prefer last direction when all directions are preferred equally
7914 move_preference = move_dir_preference;
7915 new_move_dir = move_dir;
7919 MovDir[x][y] = new_move_dir;
7920 if (old_move_dir != new_move_dir)
7921 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7925 static void TurnRound(int x, int y)
7927 int direction = MovDir[x][y];
7931 GfxDir[x][y] = MovDir[x][y];
7933 if (direction != MovDir[x][y])
7937 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7939 ResetGfxFrame(x, y);
7942 static boolean JustBeingPushed(int x, int y)
7946 for (i = 0; i < MAX_PLAYERS; i++)
7948 struct PlayerInfo *player = &stored_player[i];
7950 if (player->active && player->is_pushing && player->MovPos)
7952 int next_jx = player->jx + (player->jx - player->last_jx);
7953 int next_jy = player->jy + (player->jy - player->last_jy);
7955 if (x == next_jx && y == next_jy)
7963 static void StartMoving(int x, int y)
7965 boolean started_moving = FALSE; // some elements can fall _and_ move
7966 int element = Tile[x][y];
7971 if (MovDelay[x][y] == 0)
7972 GfxAction[x][y] = ACTION_DEFAULT;
7974 if (CAN_FALL(element) && y < lev_fieldy - 1)
7976 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7977 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7978 if (JustBeingPushed(x, y))
7981 if (element == EL_QUICKSAND_FULL)
7983 if (IS_FREE(x, y + 1))
7985 InitMovingField(x, y, MV_DOWN);
7986 started_moving = TRUE;
7988 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7989 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7990 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7991 Store[x][y] = EL_ROCK;
7993 Store[x][y] = EL_ROCK;
7996 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7998 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8000 if (!MovDelay[x][y])
8002 MovDelay[x][y] = TILEY + 1;
8004 ResetGfxAnimation(x, y);
8005 ResetGfxAnimation(x, y + 1);
8010 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
8011 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8018 Tile[x][y] = EL_QUICKSAND_EMPTY;
8019 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8020 Store[x][y + 1] = Store[x][y];
8023 PlayLevelSoundAction(x, y, ACTION_FILLING);
8025 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8027 if (!MovDelay[x][y])
8029 MovDelay[x][y] = TILEY + 1;
8031 ResetGfxAnimation(x, y);
8032 ResetGfxAnimation(x, y + 1);
8037 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
8038 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8045 Tile[x][y] = EL_QUICKSAND_EMPTY;
8046 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8047 Store[x][y + 1] = Store[x][y];
8050 PlayLevelSoundAction(x, y, ACTION_FILLING);
8053 else if (element == EL_QUICKSAND_FAST_FULL)
8055 if (IS_FREE(x, y + 1))
8057 InitMovingField(x, y, MV_DOWN);
8058 started_moving = TRUE;
8060 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
8061 #if USE_QUICKSAND_BD_ROCK_BUGFIX
8062 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
8063 Store[x][y] = EL_ROCK;
8065 Store[x][y] = EL_ROCK;
8068 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
8070 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8072 if (!MovDelay[x][y])
8074 MovDelay[x][y] = TILEY + 1;
8076 ResetGfxAnimation(x, y);
8077 ResetGfxAnimation(x, y + 1);
8082 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8083 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8090 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8091 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8092 Store[x][y + 1] = Store[x][y];
8095 PlayLevelSoundAction(x, y, ACTION_FILLING);
8097 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8099 if (!MovDelay[x][y])
8101 MovDelay[x][y] = TILEY + 1;
8103 ResetGfxAnimation(x, y);
8104 ResetGfxAnimation(x, y + 1);
8109 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8110 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8117 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8118 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8119 Store[x][y + 1] = Store[x][y];
8122 PlayLevelSoundAction(x, y, ACTION_FILLING);
8125 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8126 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8128 InitMovingField(x, y, MV_DOWN);
8129 started_moving = TRUE;
8131 Tile[x][y] = EL_QUICKSAND_FILLING;
8132 Store[x][y] = element;
8134 PlayLevelSoundAction(x, y, ACTION_FILLING);
8136 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8137 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8139 InitMovingField(x, y, MV_DOWN);
8140 started_moving = TRUE;
8142 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
8143 Store[x][y] = element;
8145 PlayLevelSoundAction(x, y, ACTION_FILLING);
8147 else if (element == EL_MAGIC_WALL_FULL)
8149 if (IS_FREE(x, y + 1))
8151 InitMovingField(x, y, MV_DOWN);
8152 started_moving = TRUE;
8154 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
8155 Store[x][y] = EL_CHANGED(Store[x][y]);
8157 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
8159 if (!MovDelay[x][y])
8160 MovDelay[x][y] = TILEY / 4 + 1;
8169 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
8170 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
8171 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8175 else if (element == EL_BD_MAGIC_WALL_FULL)
8177 if (IS_FREE(x, y + 1))
8179 InitMovingField(x, y, MV_DOWN);
8180 started_moving = TRUE;
8182 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8183 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8185 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8187 if (!MovDelay[x][y])
8188 MovDelay[x][y] = TILEY / 4 + 1;
8197 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8198 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8199 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8203 else if (element == EL_DC_MAGIC_WALL_FULL)
8205 if (IS_FREE(x, y + 1))
8207 InitMovingField(x, y, MV_DOWN);
8208 started_moving = TRUE;
8210 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8211 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8213 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8215 if (!MovDelay[x][y])
8216 MovDelay[x][y] = TILEY / 4 + 1;
8225 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8226 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8227 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8231 else if ((CAN_PASS_MAGIC_WALL(element) &&
8232 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8233 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8234 (CAN_PASS_DC_MAGIC_WALL(element) &&
8235 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8238 InitMovingField(x, y, MV_DOWN);
8239 started_moving = TRUE;
8242 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8243 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8244 EL_DC_MAGIC_WALL_FILLING);
8245 Store[x][y] = element;
8247 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8249 SplashAcid(x, y + 1);
8251 InitMovingField(x, y, MV_DOWN);
8252 started_moving = TRUE;
8254 Store[x][y] = EL_ACID;
8257 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8258 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8259 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8260 CAN_FALL(element) && WasJustFalling[x][y] &&
8261 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8263 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8264 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8265 (Tile[x][y + 1] == EL_BLOCKED)))
8267 /* this is needed for a special case not covered by calling "Impact()"
8268 from "ContinueMoving()": if an element moves to a tile directly below
8269 another element which was just falling on that tile (which was empty
8270 in the previous frame), the falling element above would just stop
8271 instead of smashing the element below (in previous version, the above
8272 element was just checked for "moving" instead of "falling", resulting
8273 in incorrect smashes caused by horizontal movement of the above
8274 element; also, the case of the player being the element to smash was
8275 simply not covered here... :-/ ) */
8277 CheckCollision[x][y] = 0;
8278 CheckImpact[x][y] = 0;
8282 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8284 if (MovDir[x][y] == MV_NONE)
8286 InitMovingField(x, y, MV_DOWN);
8287 started_moving = TRUE;
8290 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8292 if (WasJustFalling[x][y]) // prevent animation from being restarted
8293 MovDir[x][y] = MV_DOWN;
8295 InitMovingField(x, y, MV_DOWN);
8296 started_moving = TRUE;
8298 else if (element == EL_AMOEBA_DROP)
8300 Tile[x][y] = EL_AMOEBA_GROWING;
8301 Store[x][y] = EL_AMOEBA_WET;
8303 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8304 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8305 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8306 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8308 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8309 (IS_FREE(x - 1, y + 1) ||
8310 Tile[x - 1][y + 1] == EL_ACID));
8311 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8312 (IS_FREE(x + 1, y + 1) ||
8313 Tile[x + 1][y + 1] == EL_ACID));
8314 boolean can_fall_any = (can_fall_left || can_fall_right);
8315 boolean can_fall_both = (can_fall_left && can_fall_right);
8316 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8318 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8320 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8321 can_fall_right = FALSE;
8322 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8323 can_fall_left = FALSE;
8324 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8325 can_fall_right = FALSE;
8326 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8327 can_fall_left = FALSE;
8329 can_fall_any = (can_fall_left || can_fall_right);
8330 can_fall_both = FALSE;
8335 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8336 can_fall_right = FALSE; // slip down on left side
8338 can_fall_left = !(can_fall_right = RND(2));
8340 can_fall_both = FALSE;
8345 // if not determined otherwise, prefer left side for slipping down
8346 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8347 started_moving = TRUE;
8350 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8352 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8353 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8354 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8355 int belt_dir = game.belt_dir[belt_nr];
8357 if ((belt_dir == MV_LEFT && left_is_free) ||
8358 (belt_dir == MV_RIGHT && right_is_free))
8360 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8362 InitMovingField(x, y, belt_dir);
8363 started_moving = TRUE;
8365 Pushed[x][y] = TRUE;
8366 Pushed[nextx][y] = TRUE;
8368 GfxAction[x][y] = ACTION_DEFAULT;
8372 MovDir[x][y] = 0; // if element was moving, stop it
8377 // not "else if" because of elements that can fall and move (EL_SPRING)
8378 if (CAN_MOVE(element) && !started_moving)
8380 int move_pattern = element_info[element].move_pattern;
8383 Moving2Blocked(x, y, &newx, &newy);
8385 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8388 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8389 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8391 WasJustMoving[x][y] = 0;
8392 CheckCollision[x][y] = 0;
8394 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8396 if (Tile[x][y] != element) // element has changed
8400 if (!MovDelay[x][y]) // start new movement phase
8402 // all objects that can change their move direction after each step
8403 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8405 if (element != EL_YAMYAM &&
8406 element != EL_DARK_YAMYAM &&
8407 element != EL_PACMAN &&
8408 !(move_pattern & MV_ANY_DIRECTION) &&
8409 move_pattern != MV_TURNING_LEFT &&
8410 move_pattern != MV_TURNING_RIGHT &&
8411 move_pattern != MV_TURNING_LEFT_RIGHT &&
8412 move_pattern != MV_TURNING_RIGHT_LEFT &&
8413 move_pattern != MV_TURNING_RANDOM)
8417 if (MovDelay[x][y] && (element == EL_BUG ||
8418 element == EL_SPACESHIP ||
8419 element == EL_SP_SNIKSNAK ||
8420 element == EL_SP_ELECTRON ||
8421 element == EL_MOLE))
8422 TEST_DrawLevelField(x, y);
8426 if (MovDelay[x][y]) // wait some time before next movement
8430 if (element == EL_ROBOT ||
8431 element == EL_YAMYAM ||
8432 element == EL_DARK_YAMYAM)
8434 DrawLevelElementAnimationIfNeeded(x, y, element);
8435 PlayLevelSoundAction(x, y, ACTION_WAITING);
8437 else if (element == EL_SP_ELECTRON)
8438 DrawLevelElementAnimationIfNeeded(x, y, element);
8439 else if (element == EL_DRAGON)
8442 int dir = MovDir[x][y];
8443 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8444 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8445 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8446 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8447 dir == MV_UP ? IMG_FLAMES_1_UP :
8448 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8449 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8451 GfxAction[x][y] = ACTION_ATTACKING;
8453 if (IS_PLAYER(x, y))
8454 DrawPlayerField(x, y);
8456 TEST_DrawLevelField(x, y);
8458 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8460 for (i = 1; i <= 3; i++)
8462 int xx = x + i * dx;
8463 int yy = y + i * dy;
8464 int sx = SCREENX(xx);
8465 int sy = SCREENY(yy);
8466 int flame_graphic = graphic + (i - 1);
8468 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8473 int flamed = MovingOrBlocked2Element(xx, yy);
8475 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8478 RemoveMovingField(xx, yy);
8480 ChangeDelay[xx][yy] = 0;
8482 Tile[xx][yy] = EL_FLAMES;
8484 if (IN_SCR_FIELD(sx, sy))
8486 TEST_DrawLevelFieldCrumbled(xx, yy);
8487 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8492 if (Tile[xx][yy] == EL_FLAMES)
8493 Tile[xx][yy] = EL_EMPTY;
8494 TEST_DrawLevelField(xx, yy);
8499 if (MovDelay[x][y]) // element still has to wait some time
8501 PlayLevelSoundAction(x, y, ACTION_WAITING);
8507 // now make next step
8509 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8511 if (DONT_COLLIDE_WITH(element) &&
8512 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8513 !PLAYER_ENEMY_PROTECTED(newx, newy))
8515 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8520 else if (CAN_MOVE_INTO_ACID(element) &&
8521 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8522 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8523 (MovDir[x][y] == MV_DOWN ||
8524 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8526 SplashAcid(newx, newy);
8527 Store[x][y] = EL_ACID;
8529 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8531 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8532 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8533 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8534 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8537 TEST_DrawLevelField(x, y);
8539 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8540 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8541 DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0);
8543 game.friends_still_needed--;
8544 if (!game.friends_still_needed &&
8546 game.all_players_gone)
8551 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8553 if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING)
8554 TEST_DrawLevelField(newx, newy);
8556 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8558 else if (!IS_FREE(newx, newy))
8560 GfxAction[x][y] = ACTION_WAITING;
8562 if (IS_PLAYER(x, y))
8563 DrawPlayerField(x, y);
8565 TEST_DrawLevelField(x, y);
8570 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8572 if (IS_FOOD_PIG(Tile[newx][newy]))
8574 if (IS_MOVING(newx, newy))
8575 RemoveMovingField(newx, newy);
8578 Tile[newx][newy] = EL_EMPTY;
8579 TEST_DrawLevelField(newx, newy);
8582 PlayLevelSound(x, y, SND_PIG_DIGGING);
8584 else if (!IS_FREE(newx, newy))
8586 if (IS_PLAYER(x, y))
8587 DrawPlayerField(x, y);
8589 TEST_DrawLevelField(x, y);
8594 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8596 if (Store[x][y] != EL_EMPTY)
8598 boolean can_clone = FALSE;
8601 // check if element to clone is still there
8602 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8604 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8612 // cannot clone or target field not free anymore -- do not clone
8613 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8614 Store[x][y] = EL_EMPTY;
8617 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8619 if (IS_MV_DIAGONAL(MovDir[x][y]))
8621 int diagonal_move_dir = MovDir[x][y];
8622 int stored = Store[x][y];
8623 int change_delay = 8;
8626 // android is moving diagonally
8628 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8630 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8631 GfxElement[x][y] = EL_EMC_ANDROID;
8632 GfxAction[x][y] = ACTION_SHRINKING;
8633 GfxDir[x][y] = diagonal_move_dir;
8634 ChangeDelay[x][y] = change_delay;
8636 if (Store[x][y] == EL_EMPTY)
8637 Store[x][y] = GfxElementEmpty[x][y];
8639 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8642 DrawLevelGraphicAnimation(x, y, graphic);
8643 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8645 if (Tile[newx][newy] == EL_ACID)
8647 SplashAcid(newx, newy);
8652 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8654 Store[newx][newy] = EL_EMC_ANDROID;
8655 GfxElement[newx][newy] = EL_EMC_ANDROID;
8656 GfxAction[newx][newy] = ACTION_GROWING;
8657 GfxDir[newx][newy] = diagonal_move_dir;
8658 ChangeDelay[newx][newy] = change_delay;
8660 graphic = el_act_dir2img(GfxElement[newx][newy],
8661 GfxAction[newx][newy], GfxDir[newx][newy]);
8663 DrawLevelGraphicAnimation(newx, newy, graphic);
8664 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8670 Tile[newx][newy] = EL_EMPTY;
8671 TEST_DrawLevelField(newx, newy);
8673 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8676 else if (!IS_FREE(newx, newy))
8681 else if (IS_CUSTOM_ELEMENT(element) &&
8682 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8684 if (!DigFieldByCE(newx, newy, element))
8687 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8689 RunnerVisit[x][y] = FrameCounter;
8690 PlayerVisit[x][y] /= 8; // expire player visit path
8693 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8695 if (!IS_FREE(newx, newy))
8697 if (IS_PLAYER(x, y))
8698 DrawPlayerField(x, y);
8700 TEST_DrawLevelField(x, y);
8706 boolean wanna_flame = !RND(10);
8707 int dx = newx - x, dy = newy - y;
8708 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8709 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8710 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8711 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8712 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8713 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8716 IS_CLASSIC_ENEMY(element1) ||
8717 IS_CLASSIC_ENEMY(element2)) &&
8718 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8719 element1 != EL_FLAMES && element2 != EL_FLAMES)
8721 ResetGfxAnimation(x, y);
8722 GfxAction[x][y] = ACTION_ATTACKING;
8724 if (IS_PLAYER(x, y))
8725 DrawPlayerField(x, y);
8727 TEST_DrawLevelField(x, y);
8729 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8731 MovDelay[x][y] = 50;
8733 Tile[newx][newy] = EL_FLAMES;
8734 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8735 Tile[newx1][newy1] = EL_FLAMES;
8736 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8737 Tile[newx2][newy2] = EL_FLAMES;
8743 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8744 Tile[newx][newy] == EL_DIAMOND)
8746 if (IS_MOVING(newx, newy))
8747 RemoveMovingField(newx, newy);
8750 Tile[newx][newy] = EL_EMPTY;
8751 TEST_DrawLevelField(newx, newy);
8754 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8756 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8757 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8759 if (AmoebaNr[newx][newy])
8761 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8762 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8763 Tile[newx][newy] == EL_BD_AMOEBA)
8764 AmoebaCnt[AmoebaNr[newx][newy]]--;
8767 if (IS_MOVING(newx, newy))
8769 RemoveMovingField(newx, newy);
8773 Tile[newx][newy] = EL_EMPTY;
8774 TEST_DrawLevelField(newx, newy);
8777 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8779 else if ((element == EL_PACMAN || element == EL_MOLE)
8780 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8782 if (AmoebaNr[newx][newy])
8784 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8785 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8786 Tile[newx][newy] == EL_BD_AMOEBA)
8787 AmoebaCnt[AmoebaNr[newx][newy]]--;
8790 if (element == EL_MOLE)
8792 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8793 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8795 ResetGfxAnimation(x, y);
8796 GfxAction[x][y] = ACTION_DIGGING;
8797 TEST_DrawLevelField(x, y);
8799 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8801 return; // wait for shrinking amoeba
8803 else // element == EL_PACMAN
8805 Tile[newx][newy] = EL_EMPTY;
8806 TEST_DrawLevelField(newx, newy);
8807 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8810 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8811 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8812 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8814 // wait for shrinking amoeba to completely disappear
8817 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8819 // object was running against a wall
8823 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8824 DrawLevelElementAnimation(x, y, element);
8826 if (DONT_TOUCH(element))
8827 TestIfBadThingTouchesPlayer(x, y);
8832 InitMovingField(x, y, MovDir[x][y]);
8834 PlayLevelSoundAction(x, y, ACTION_MOVING);
8838 ContinueMoving(x, y);
8841 void ContinueMoving(int x, int y)
8843 int element = Tile[x][y];
8844 struct ElementInfo *ei = &element_info[element];
8845 int direction = MovDir[x][y];
8846 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8847 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8848 int newx = x + dx, newy = y + dy;
8849 int stored = Store[x][y];
8850 int stored_new = Store[newx][newy];
8851 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8852 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8853 boolean last_line = (newy == lev_fieldy - 1);
8854 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8856 if (pushed_by_player) // special case: moving object pushed by player
8858 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos));
8860 else if (use_step_delay) // special case: moving object has step delay
8862 if (!MovDelay[x][y])
8863 MovPos[x][y] += getElementMoveStepsize(x, y);
8868 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8872 TEST_DrawLevelField(x, y);
8874 return; // element is still waiting
8877 else // normal case: generically moving object
8879 MovPos[x][y] += getElementMoveStepsize(x, y);
8882 if (ABS(MovPos[x][y]) < TILEX)
8884 TEST_DrawLevelField(x, y);
8886 return; // element is still moving
8889 // element reached destination field
8891 Tile[x][y] = EL_EMPTY;
8892 Tile[newx][newy] = element;
8893 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8895 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8897 element = Tile[newx][newy] = EL_ACID;
8899 else if (element == EL_MOLE)
8901 Tile[x][y] = EL_SAND;
8903 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8905 else if (element == EL_QUICKSAND_FILLING)
8907 element = Tile[newx][newy] = get_next_element(element);
8908 Store[newx][newy] = Store[x][y];
8910 else if (element == EL_QUICKSAND_EMPTYING)
8912 Tile[x][y] = get_next_element(element);
8913 element = Tile[newx][newy] = Store[x][y];
8915 else if (element == EL_QUICKSAND_FAST_FILLING)
8917 element = Tile[newx][newy] = get_next_element(element);
8918 Store[newx][newy] = Store[x][y];
8920 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8922 Tile[x][y] = get_next_element(element);
8923 element = Tile[newx][newy] = Store[x][y];
8925 else if (element == EL_MAGIC_WALL_FILLING)
8927 element = Tile[newx][newy] = get_next_element(element);
8928 if (!game.magic_wall_active)
8929 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8930 Store[newx][newy] = Store[x][y];
8932 else if (element == EL_MAGIC_WALL_EMPTYING)
8934 Tile[x][y] = get_next_element(element);
8935 if (!game.magic_wall_active)
8936 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8937 element = Tile[newx][newy] = Store[x][y];
8939 InitField(newx, newy, FALSE);
8941 else if (element == EL_BD_MAGIC_WALL_FILLING)
8943 element = Tile[newx][newy] = get_next_element(element);
8944 if (!game.magic_wall_active)
8945 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8946 Store[newx][newy] = Store[x][y];
8948 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8950 Tile[x][y] = get_next_element(element);
8951 if (!game.magic_wall_active)
8952 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8953 element = Tile[newx][newy] = Store[x][y];
8955 InitField(newx, newy, FALSE);
8957 else if (element == EL_DC_MAGIC_WALL_FILLING)
8959 element = Tile[newx][newy] = get_next_element(element);
8960 if (!game.magic_wall_active)
8961 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8962 Store[newx][newy] = Store[x][y];
8964 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8966 Tile[x][y] = get_next_element(element);
8967 if (!game.magic_wall_active)
8968 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8969 element = Tile[newx][newy] = Store[x][y];
8971 InitField(newx, newy, FALSE);
8973 else if (element == EL_AMOEBA_DROPPING)
8975 Tile[x][y] = get_next_element(element);
8976 element = Tile[newx][newy] = Store[x][y];
8978 else if (element == EL_SOKOBAN_OBJECT)
8981 Tile[x][y] = Back[x][y];
8983 if (Back[newx][newy])
8984 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8986 Back[x][y] = Back[newx][newy] = 0;
8989 Store[x][y] = EL_EMPTY;
8994 MovDelay[newx][newy] = 0;
8996 if (CAN_CHANGE_OR_HAS_ACTION(element))
8998 // copy element change control values to new field
8999 ChangeDelay[newx][newy] = ChangeDelay[x][y];
9000 ChangePage[newx][newy] = ChangePage[x][y];
9001 ChangeCount[newx][newy] = ChangeCount[x][y];
9002 ChangeEvent[newx][newy] = ChangeEvent[x][y];
9005 CustomValue[newx][newy] = CustomValue[x][y];
9007 ChangeDelay[x][y] = 0;
9008 ChangePage[x][y] = -1;
9009 ChangeCount[x][y] = 0;
9010 ChangeEvent[x][y] = -1;
9012 CustomValue[x][y] = 0;
9014 // copy animation control values to new field
9015 GfxFrame[newx][newy] = GfxFrame[x][y];
9016 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
9017 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
9018 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
9020 Pushed[x][y] = Pushed[newx][newy] = FALSE;
9022 // some elements can leave other elements behind after moving
9023 if (ei->move_leave_element != EL_EMPTY &&
9024 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
9025 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
9027 int move_leave_element = ei->move_leave_element;
9029 // this makes it possible to leave the removed element again
9030 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
9031 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
9033 Tile[x][y] = move_leave_element;
9035 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
9036 MovDir[x][y] = direction;
9038 InitField(x, y, FALSE);
9040 if (GFX_CRUMBLED(Tile[x][y]))
9041 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
9043 if (IS_PLAYER_ELEMENT(move_leave_element))
9044 RelocatePlayer(x, y, move_leave_element);
9047 // do this after checking for left-behind element
9048 ResetGfxAnimation(x, y); // reset animation values for old field
9050 if (!CAN_MOVE(element) ||
9051 (CAN_FALL(element) && direction == MV_DOWN &&
9052 (element == EL_SPRING ||
9053 element_info[element].move_pattern == MV_WHEN_PUSHED ||
9054 element_info[element].move_pattern == MV_WHEN_DROPPED)))
9055 GfxDir[x][y] = MovDir[newx][newy] = 0;
9057 TEST_DrawLevelField(x, y);
9058 TEST_DrawLevelField(newx, newy);
9060 Stop[newx][newy] = TRUE; // ignore this element until the next frame
9062 // prevent pushed element from moving on in pushed direction
9063 if (pushed_by_player && CAN_MOVE(element) &&
9064 element_info[element].move_pattern & MV_ANY_DIRECTION &&
9065 !(element_info[element].move_pattern & direction))
9066 TurnRound(newx, newy);
9068 // prevent elements on conveyor belt from moving on in last direction
9069 if (pushed_by_conveyor && CAN_FALL(element) &&
9070 direction & MV_HORIZONTAL)
9071 MovDir[newx][newy] = 0;
9073 if (!pushed_by_player)
9075 int nextx = newx + dx, nexty = newy + dy;
9076 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
9078 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
9080 if (CAN_FALL(element) && direction == MV_DOWN)
9081 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
9083 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
9084 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
9086 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
9087 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
9090 if (DONT_TOUCH(element)) // object may be nasty to player or others
9092 TestIfBadThingTouchesPlayer(newx, newy);
9093 TestIfBadThingTouchesFriend(newx, newy);
9095 if (!IS_CUSTOM_ELEMENT(element))
9096 TestIfBadThingTouchesOtherBadThing(newx, newy);
9098 else if (element == EL_PENGUIN)
9099 TestIfFriendTouchesBadThing(newx, newy);
9101 if (DONT_GET_HIT_BY(element))
9103 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
9106 // give the player one last chance (one more frame) to move away
9107 if (CAN_FALL(element) && direction == MV_DOWN &&
9108 (last_line || (!IS_FREE(x, newy + 1) &&
9109 (!IS_PLAYER(x, newy + 1) ||
9110 game.engine_version < VERSION_IDENT(3,1,1,0)))))
9113 if (pushed_by_player && !game.use_change_when_pushing_bug)
9115 int push_side = MV_DIR_OPPOSITE(direction);
9116 struct PlayerInfo *player = PLAYERINFO(x, y);
9118 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
9119 player->index_bit, push_side);
9120 CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X,
9121 player->index_bit, push_side);
9124 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
9125 MovDelay[newx][newy] = 1;
9127 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
9129 TestIfElementTouchesCustomElement(x, y); // empty or new element
9130 TestIfElementHitsCustomElement(newx, newy, direction);
9131 TestIfPlayerTouchesCustomElement(newx, newy);
9132 TestIfElementTouchesCustomElement(newx, newy);
9134 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
9135 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
9136 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
9137 MV_DIR_OPPOSITE(direction));
9140 int AmoebaNeighbourNr(int ax, int ay)
9143 int element = Tile[ax][ay];
9145 struct XY *xy = xy_topdown;
9147 for (i = 0; i < NUM_DIRECTIONS; i++)
9149 int x = ax + xy[i].x;
9150 int y = ay + xy[i].y;
9152 if (!IN_LEV_FIELD(x, y))
9155 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
9156 group_nr = AmoebaNr[x][y];
9162 static void AmoebaMerge(int ax, int ay)
9164 int i, x, y, xx, yy;
9165 int new_group_nr = AmoebaNr[ax][ay];
9166 struct XY *xy = xy_topdown;
9168 if (new_group_nr == 0)
9171 for (i = 0; i < NUM_DIRECTIONS; i++)
9176 if (!IN_LEV_FIELD(x, y))
9179 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9180 Tile[x][y] == EL_BD_AMOEBA ||
9181 Tile[x][y] == EL_AMOEBA_DEAD) &&
9182 AmoebaNr[x][y] != new_group_nr)
9184 int old_group_nr = AmoebaNr[x][y];
9186 if (old_group_nr == 0)
9189 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9190 AmoebaCnt[old_group_nr] = 0;
9191 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9192 AmoebaCnt2[old_group_nr] = 0;
9194 SCAN_PLAYFIELD(xx, yy)
9196 if (AmoebaNr[xx][yy] == old_group_nr)
9197 AmoebaNr[xx][yy] = new_group_nr;
9203 void AmoebaToDiamond(int ax, int ay)
9207 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9209 int group_nr = AmoebaNr[ax][ay];
9214 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9215 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9221 SCAN_PLAYFIELD(x, y)
9223 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9226 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9230 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9231 SND_AMOEBA_TURNING_TO_GEM :
9232 SND_AMOEBA_TURNING_TO_ROCK));
9237 struct XY *xy = xy_topdown;
9239 for (i = 0; i < NUM_DIRECTIONS; i++)
9244 if (!IN_LEV_FIELD(x, y))
9247 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9249 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9250 SND_AMOEBA_TURNING_TO_GEM :
9251 SND_AMOEBA_TURNING_TO_ROCK));
9258 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9261 int group_nr = AmoebaNr[ax][ay];
9262 boolean done = FALSE;
9267 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9268 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9274 SCAN_PLAYFIELD(x, y)
9276 if (AmoebaNr[x][y] == group_nr &&
9277 (Tile[x][y] == EL_AMOEBA_DEAD ||
9278 Tile[x][y] == EL_BD_AMOEBA ||
9279 Tile[x][y] == EL_AMOEBA_GROWING))
9282 Tile[x][y] = new_element;
9283 InitField(x, y, FALSE);
9284 TEST_DrawLevelField(x, y);
9290 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9291 SND_BD_AMOEBA_TURNING_TO_ROCK :
9292 SND_BD_AMOEBA_TURNING_TO_GEM));
9295 static void AmoebaGrowing(int x, int y)
9297 static DelayCounter sound_delay = { 0 };
9299 if (!MovDelay[x][y]) // start new growing cycle
9303 if (DelayReached(&sound_delay))
9305 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9306 sound_delay.value = 30;
9310 if (MovDelay[x][y]) // wait some time before growing bigger
9313 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9315 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9316 6 - MovDelay[x][y]);
9318 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9321 if (!MovDelay[x][y])
9323 Tile[x][y] = Store[x][y];
9325 TEST_DrawLevelField(x, y);
9330 static void AmoebaShrinking(int x, int y)
9332 static DelayCounter sound_delay = { 0 };
9334 if (!MovDelay[x][y]) // start new shrinking cycle
9338 if (DelayReached(&sound_delay))
9339 sound_delay.value = 30;
9342 if (MovDelay[x][y]) // wait some time before shrinking
9345 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9347 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9348 6 - MovDelay[x][y]);
9350 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9353 if (!MovDelay[x][y])
9355 Tile[x][y] = EL_EMPTY;
9356 TEST_DrawLevelField(x, y);
9358 // don't let mole enter this field in this cycle;
9359 // (give priority to objects falling to this field from above)
9365 static void AmoebaReproduce(int ax, int ay)
9368 int element = Tile[ax][ay];
9369 int graphic = el2img(element);
9370 int newax = ax, neway = ay;
9371 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9372 struct XY *xy = xy_topdown;
9374 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9376 Tile[ax][ay] = EL_AMOEBA_DEAD;
9377 TEST_DrawLevelField(ax, ay);
9381 if (IS_ANIMATED(graphic))
9382 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9384 if (!MovDelay[ax][ay]) // start making new amoeba field
9385 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9387 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9390 if (MovDelay[ax][ay])
9394 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9397 int x = ax + xy[start].x;
9398 int y = ay + xy[start].y;
9400 if (!IN_LEV_FIELD(x, y))
9403 if (IS_FREE(x, y) ||
9404 CAN_GROW_INTO(Tile[x][y]) ||
9405 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9406 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9412 if (newax == ax && neway == ay)
9415 else // normal or "filled" (BD style) amoeba
9418 boolean waiting_for_player = FALSE;
9420 for (i = 0; i < NUM_DIRECTIONS; i++)
9422 int j = (start + i) % 4;
9423 int x = ax + xy[j].x;
9424 int y = ay + xy[j].y;
9426 if (!IN_LEV_FIELD(x, y))
9429 if (IS_FREE(x, y) ||
9430 CAN_GROW_INTO(Tile[x][y]) ||
9431 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9432 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9438 else if (IS_PLAYER(x, y))
9439 waiting_for_player = TRUE;
9442 if (newax == ax && neway == ay) // amoeba cannot grow
9444 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9446 Tile[ax][ay] = EL_AMOEBA_DEAD;
9447 TEST_DrawLevelField(ax, ay);
9448 AmoebaCnt[AmoebaNr[ax][ay]]--;
9450 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9452 if (element == EL_AMOEBA_FULL)
9453 AmoebaToDiamond(ax, ay);
9454 else if (element == EL_BD_AMOEBA)
9455 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9460 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9462 // amoeba gets larger by growing in some direction
9464 int new_group_nr = AmoebaNr[ax][ay];
9467 if (new_group_nr == 0)
9469 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9471 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9477 AmoebaNr[newax][neway] = new_group_nr;
9478 AmoebaCnt[new_group_nr]++;
9479 AmoebaCnt2[new_group_nr]++;
9481 // if amoeba touches other amoeba(s) after growing, unify them
9482 AmoebaMerge(newax, neway);
9484 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9486 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9492 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9493 (neway == lev_fieldy - 1 && newax != ax))
9495 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9496 Store[newax][neway] = element;
9498 else if (neway == ay || element == EL_EMC_DRIPPER)
9500 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9502 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9506 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9507 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9508 Store[ax][ay] = EL_AMOEBA_DROP;
9509 ContinueMoving(ax, ay);
9513 TEST_DrawLevelField(newax, neway);
9516 static void Life(int ax, int ay)
9520 int element = Tile[ax][ay];
9521 int graphic = el2img(element);
9522 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9524 boolean changed = FALSE;
9526 if (IS_ANIMATED(graphic))
9527 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9532 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9533 MovDelay[ax][ay] = life_time;
9535 if (MovDelay[ax][ay]) // wait some time before next cycle
9538 if (MovDelay[ax][ay])
9542 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9544 int xx = ax + x1, yy = ay + y1;
9545 int old_element = Tile[xx][yy];
9546 int num_neighbours = 0;
9548 if (!IN_LEV_FIELD(xx, yy))
9551 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9553 int x = xx + x2, y = yy + y2;
9555 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9558 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9559 boolean is_neighbour = FALSE;
9561 if (level.use_life_bugs)
9563 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9564 (IS_FREE(x, y) && Stop[x][y]));
9567 (Last[x][y] == element || is_player_cell);
9573 boolean is_free = FALSE;
9575 if (level.use_life_bugs)
9576 is_free = (IS_FREE(xx, yy));
9578 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9580 if (xx == ax && yy == ay) // field in the middle
9582 if (num_neighbours < life_parameter[0] ||
9583 num_neighbours > life_parameter[1])
9585 Tile[xx][yy] = EL_EMPTY;
9586 if (Tile[xx][yy] != old_element)
9587 TEST_DrawLevelField(xx, yy);
9588 Stop[xx][yy] = TRUE;
9592 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9593 { // free border field
9594 if (num_neighbours >= life_parameter[2] &&
9595 num_neighbours <= life_parameter[3])
9597 Tile[xx][yy] = element;
9598 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9599 if (Tile[xx][yy] != old_element)
9600 TEST_DrawLevelField(xx, yy);
9601 Stop[xx][yy] = TRUE;
9608 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9609 SND_GAME_OF_LIFE_GROWING);
9612 static void InitRobotWheel(int x, int y)
9614 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9617 static void RunRobotWheel(int x, int y)
9619 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9622 static void StopRobotWheel(int x, int y)
9624 if (game.robot_wheel_x == x &&
9625 game.robot_wheel_y == y)
9627 game.robot_wheel_x = -1;
9628 game.robot_wheel_y = -1;
9629 game.robot_wheel_active = FALSE;
9633 static void InitTimegateWheel(int x, int y)
9635 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9638 static void RunTimegateWheel(int x, int y)
9640 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9643 static void InitMagicBallDelay(int x, int y)
9645 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9648 static void ActivateMagicBall(int bx, int by)
9652 if (level.ball_random)
9654 int pos_border = RND(8); // select one of the eight border elements
9655 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9656 int xx = pos_content % 3;
9657 int yy = pos_content / 3;
9662 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9663 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9667 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9669 int xx = x - bx + 1;
9670 int yy = y - by + 1;
9672 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9673 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9677 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9680 static void CheckExit(int x, int y)
9682 if (game.gems_still_needed > 0 ||
9683 game.sokoban_fields_still_needed > 0 ||
9684 game.sokoban_objects_still_needed > 0 ||
9685 game.lights_still_needed > 0)
9687 int element = Tile[x][y];
9688 int graphic = el2img(element);
9690 if (IS_ANIMATED(graphic))
9691 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9696 // do not re-open exit door closed after last player
9697 if (game.all_players_gone)
9700 Tile[x][y] = EL_EXIT_OPENING;
9702 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9705 static void CheckExitEM(int x, int y)
9707 if (game.gems_still_needed > 0 ||
9708 game.sokoban_fields_still_needed > 0 ||
9709 game.sokoban_objects_still_needed > 0 ||
9710 game.lights_still_needed > 0)
9712 int element = Tile[x][y];
9713 int graphic = el2img(element);
9715 if (IS_ANIMATED(graphic))
9716 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9721 // do not re-open exit door closed after last player
9722 if (game.all_players_gone)
9725 Tile[x][y] = EL_EM_EXIT_OPENING;
9727 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9730 static void CheckExitSteel(int x, int y)
9732 if (game.gems_still_needed > 0 ||
9733 game.sokoban_fields_still_needed > 0 ||
9734 game.sokoban_objects_still_needed > 0 ||
9735 game.lights_still_needed > 0)
9737 int element = Tile[x][y];
9738 int graphic = el2img(element);
9740 if (IS_ANIMATED(graphic))
9741 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9746 // do not re-open exit door closed after last player
9747 if (game.all_players_gone)
9750 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9752 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9755 static void CheckExitSteelEM(int x, int y)
9757 if (game.gems_still_needed > 0 ||
9758 game.sokoban_fields_still_needed > 0 ||
9759 game.sokoban_objects_still_needed > 0 ||
9760 game.lights_still_needed > 0)
9762 int element = Tile[x][y];
9763 int graphic = el2img(element);
9765 if (IS_ANIMATED(graphic))
9766 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9771 // do not re-open exit door closed after last player
9772 if (game.all_players_gone)
9775 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9777 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9780 static void CheckExitSP(int x, int y)
9782 if (game.gems_still_needed > 0)
9784 int element = Tile[x][y];
9785 int graphic = el2img(element);
9787 if (IS_ANIMATED(graphic))
9788 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9793 // do not re-open exit door closed after last player
9794 if (game.all_players_gone)
9797 Tile[x][y] = EL_SP_EXIT_OPENING;
9799 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9802 static void CloseAllOpenTimegates(void)
9806 SCAN_PLAYFIELD(x, y)
9808 int element = Tile[x][y];
9810 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9812 Tile[x][y] = EL_TIMEGATE_CLOSING;
9814 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9819 static void DrawTwinkleOnField(int x, int y)
9821 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9824 if (Tile[x][y] == EL_BD_DIAMOND)
9827 if (MovDelay[x][y] == 0) // next animation frame
9828 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9830 if (MovDelay[x][y] != 0) // wait some time before next frame
9834 DrawLevelElementAnimation(x, y, Tile[x][y]);
9836 if (MovDelay[x][y] != 0)
9838 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9839 10 - MovDelay[x][y]);
9841 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9846 static void WallGrowing(int x, int y)
9850 if (!MovDelay[x][y]) // next animation frame
9851 MovDelay[x][y] = 3 * delay;
9853 if (MovDelay[x][y]) // wait some time before next frame
9857 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9859 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9860 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9862 DrawLevelGraphic(x, y, graphic, frame);
9865 if (!MovDelay[x][y])
9867 if (MovDir[x][y] == MV_LEFT)
9869 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9870 TEST_DrawLevelField(x - 1, y);
9872 else if (MovDir[x][y] == MV_RIGHT)
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_UP)
9879 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9880 TEST_DrawLevelField(x, y - 1);
9884 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9885 TEST_DrawLevelField(x, y + 1);
9888 Tile[x][y] = Store[x][y];
9890 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9891 TEST_DrawLevelField(x, y);
9896 static void CheckWallGrowing(int ax, int ay)
9898 int element = Tile[ax][ay];
9899 int graphic = el2img(element);
9900 boolean free_top = FALSE;
9901 boolean free_bottom = FALSE;
9902 boolean free_left = FALSE;
9903 boolean free_right = FALSE;
9904 boolean stop_top = FALSE;
9905 boolean stop_bottom = FALSE;
9906 boolean stop_left = FALSE;
9907 boolean stop_right = FALSE;
9908 boolean new_wall = FALSE;
9910 boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9911 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9912 element == EL_EXPANDABLE_STEELWALL_ANY);
9914 boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9915 element == EL_EXPANDABLE_WALL_ANY ||
9916 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9917 element == EL_EXPANDABLE_STEELWALL_ANY);
9919 boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9920 element == EL_EXPANDABLE_WALL_ANY ||
9921 element == EL_EXPANDABLE_WALL ||
9922 element == EL_BD_EXPANDABLE_WALL ||
9923 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9924 element == EL_EXPANDABLE_STEELWALL_ANY);
9926 boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9927 element == EL_EXPANDABLE_STEELWALL_VERTICAL);
9929 boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9930 element == EL_EXPANDABLE_WALL ||
9931 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL);
9933 int wall_growing = (is_steelwall ?
9934 EL_EXPANDABLE_STEELWALL_GROWING :
9935 EL_EXPANDABLE_WALL_GROWING);
9937 int gfx_wall_growing_up = (is_steelwall ?
9938 IMG_EXPANDABLE_STEELWALL_GROWING_UP :
9939 IMG_EXPANDABLE_WALL_GROWING_UP);
9940 int gfx_wall_growing_down = (is_steelwall ?
9941 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN :
9942 IMG_EXPANDABLE_WALL_GROWING_DOWN);
9943 int gfx_wall_growing_left = (is_steelwall ?
9944 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT :
9945 IMG_EXPANDABLE_WALL_GROWING_LEFT);
9946 int gfx_wall_growing_right = (is_steelwall ?
9947 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT :
9948 IMG_EXPANDABLE_WALL_GROWING_RIGHT);
9950 if (IS_ANIMATED(graphic))
9951 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9953 if (!MovDelay[ax][ay]) // start building new wall
9954 MovDelay[ax][ay] = 6;
9956 if (MovDelay[ax][ay]) // wait some time before building new wall
9959 if (MovDelay[ax][ay])
9963 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9965 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9967 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9969 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9976 Tile[ax][ay - 1] = wall_growing;
9977 Store[ax][ay - 1] = element;
9978 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9980 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9981 DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0);
9988 Tile[ax][ay + 1] = wall_growing;
9989 Store[ax][ay + 1] = element;
9990 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9992 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9993 DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0);
9999 if (grow_horizontal)
10003 Tile[ax - 1][ay] = wall_growing;
10004 Store[ax - 1][ay] = element;
10005 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
10007 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
10008 DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0);
10015 Tile[ax + 1][ay] = wall_growing;
10016 Store[ax + 1][ay] = element;
10017 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
10019 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
10020 DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0);
10026 if (element == EL_EXPANDABLE_WALL && (free_left || free_right))
10027 TEST_DrawLevelField(ax, ay);
10029 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
10031 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
10032 stop_bottom = TRUE;
10033 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
10035 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
10038 if (((stop_top && stop_bottom) || stop_horizontal) &&
10039 ((stop_left && stop_right) || stop_vertical))
10040 Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL);
10043 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
10046 static void CheckForDragon(int x, int y)
10049 boolean dragon_found = FALSE;
10050 struct XY *xy = xy_topdown;
10052 for (i = 0; i < NUM_DIRECTIONS; i++)
10054 for (j = 0; j < 4; j++)
10056 int xx = x + j * xy[i].x;
10057 int yy = y + j * xy[i].y;
10059 if (IN_LEV_FIELD(xx, yy) &&
10060 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
10062 if (Tile[xx][yy] == EL_DRAGON)
10063 dragon_found = TRUE;
10072 for (i = 0; i < NUM_DIRECTIONS; i++)
10074 for (j = 0; j < 3; j++)
10076 int xx = x + j * xy[i].x;
10077 int yy = y + j * xy[i].y;
10079 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
10081 Tile[xx][yy] = EL_EMPTY;
10082 TEST_DrawLevelField(xx, yy);
10091 static void InitBuggyBase(int x, int y)
10093 int element = Tile[x][y];
10094 int activating_delay = FRAMES_PER_SECOND / 4;
10096 ChangeDelay[x][y] =
10097 (element == EL_SP_BUGGY_BASE ?
10098 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10099 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10101 element == EL_SP_BUGGY_BASE_ACTIVE ?
10102 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10105 static void WarnBuggyBase(int x, int y)
10108 struct XY *xy = xy_topdown;
10110 for (i = 0; i < NUM_DIRECTIONS; i++)
10112 int xx = x + xy[i].x;
10113 int yy = y + xy[i].y;
10115 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10117 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10124 static void InitTrap(int x, int y)
10126 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10129 static void ActivateTrap(int x, int y)
10131 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10134 static void ChangeActiveTrap(int x, int y)
10136 int graphic = IMG_TRAP_ACTIVE;
10138 // if new animation frame was drawn, correct crumbled sand border
10139 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10140 TEST_DrawLevelFieldCrumbled(x, y);
10143 static int getSpecialActionElement(int element, int number, int base_element)
10145 return (element != EL_EMPTY ? element :
10146 number != -1 ? base_element + number - 1 :
10150 static int getModifiedActionNumber(int value_old, int operator, int operand,
10151 int value_min, int value_max)
10153 int value_new = (operator == CA_MODE_SET ? operand :
10154 operator == CA_MODE_ADD ? value_old + operand :
10155 operator == CA_MODE_SUBTRACT ? value_old - operand :
10156 operator == CA_MODE_MULTIPLY ? value_old * operand :
10157 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10158 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10161 return (value_new < value_min ? value_min :
10162 value_new > value_max ? value_max :
10166 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10168 struct ElementInfo *ei = &element_info[element];
10169 struct ElementChangeInfo *change = &ei->change_page[page];
10170 int target_element = change->target_element;
10171 int action_type = change->action_type;
10172 int action_mode = change->action_mode;
10173 int action_arg = change->action_arg;
10174 int action_element = change->action_element;
10177 if (!change->has_action)
10180 // ---------- determine action paramater values -----------------------------
10182 int level_time_value =
10183 (level.time > 0 ? TimeLeft :
10186 int action_arg_element_raw =
10187 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10188 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10189 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10190 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10191 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10192 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10193 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10195 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10197 int action_arg_direction =
10198 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10199 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10200 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10201 change->actual_trigger_side :
10202 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10203 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10206 int action_arg_number_min =
10207 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10210 int action_arg_number_max =
10211 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10212 action_type == CA_SET_LEVEL_GEMS ? 999 :
10213 action_type == CA_SET_LEVEL_TIME ? 9999 :
10214 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10215 action_type == CA_SET_CE_VALUE ? 9999 :
10216 action_type == CA_SET_CE_SCORE ? 9999 :
10219 int action_arg_number_reset =
10220 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10221 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10222 action_type == CA_SET_LEVEL_TIME ? level.time :
10223 action_type == CA_SET_LEVEL_SCORE ? 0 :
10224 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10225 action_type == CA_SET_CE_SCORE ? 0 :
10228 int action_arg_number =
10229 (action_arg <= CA_ARG_MAX ? action_arg :
10230 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10231 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10232 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10233 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10234 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10235 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10236 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10237 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10238 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10239 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10240 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10241 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10242 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10243 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10244 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10245 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10246 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10247 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10248 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10249 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10250 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10253 int action_arg_number_old =
10254 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10255 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10256 action_type == CA_SET_LEVEL_SCORE ? game.score :
10257 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10258 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10261 int action_arg_number_new =
10262 getModifiedActionNumber(action_arg_number_old,
10263 action_mode, action_arg_number,
10264 action_arg_number_min, action_arg_number_max);
10266 int trigger_player_bits =
10267 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10268 change->actual_trigger_player_bits : change->trigger_player);
10270 int action_arg_player_bits =
10271 (action_arg >= CA_ARG_PLAYER_1 &&
10272 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10273 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10274 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10277 // ---------- execute action -----------------------------------------------
10279 switch (action_type)
10286 // ---------- level actions ----------------------------------------------
10288 case CA_RESTART_LEVEL:
10290 game.restart_level = TRUE;
10295 case CA_SHOW_ENVELOPE:
10297 int element = getSpecialActionElement(action_arg_element,
10298 action_arg_number, EL_ENVELOPE_1);
10300 if (IS_ENVELOPE(element))
10301 local_player->show_envelope = element;
10306 case CA_SET_LEVEL_TIME:
10308 if (level.time > 0) // only modify limited time value
10310 TimeLeft = action_arg_number_new;
10312 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10314 DisplayGameControlValues();
10316 if (!TimeLeft && game.time_limit)
10317 for (i = 0; i < MAX_PLAYERS; i++)
10318 KillPlayer(&stored_player[i]);
10324 case CA_SET_LEVEL_SCORE:
10326 game.score = action_arg_number_new;
10328 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10330 DisplayGameControlValues();
10335 case CA_SET_LEVEL_GEMS:
10337 game.gems_still_needed = action_arg_number_new;
10339 game.snapshot.collected_item = TRUE;
10341 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10343 DisplayGameControlValues();
10348 case CA_SET_LEVEL_WIND:
10350 game.wind_direction = action_arg_direction;
10355 case CA_SET_LEVEL_RANDOM_SEED:
10357 // ensure that setting a new random seed while playing is predictable
10358 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10363 // ---------- player actions ---------------------------------------------
10365 case CA_MOVE_PLAYER:
10366 case CA_MOVE_PLAYER_NEW:
10368 // automatically move to the next field in specified direction
10369 for (i = 0; i < MAX_PLAYERS; i++)
10370 if (trigger_player_bits & (1 << i))
10371 if (action_type == CA_MOVE_PLAYER ||
10372 stored_player[i].MovPos == 0)
10373 stored_player[i].programmed_action = action_arg_direction;
10378 case CA_EXIT_PLAYER:
10380 for (i = 0; i < MAX_PLAYERS; i++)
10381 if (action_arg_player_bits & (1 << i))
10382 ExitPlayer(&stored_player[i]);
10384 if (game.players_still_needed == 0)
10390 case CA_KILL_PLAYER:
10392 for (i = 0; i < MAX_PLAYERS; i++)
10393 if (action_arg_player_bits & (1 << i))
10394 KillPlayer(&stored_player[i]);
10399 case CA_SET_PLAYER_KEYS:
10401 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10402 int element = getSpecialActionElement(action_arg_element,
10403 action_arg_number, EL_KEY_1);
10405 if (IS_KEY(element))
10407 for (i = 0; i < MAX_PLAYERS; i++)
10409 if (trigger_player_bits & (1 << i))
10411 stored_player[i].key[KEY_NR(element)] = key_state;
10413 DrawGameDoorValues();
10421 case CA_SET_PLAYER_SPEED:
10423 for (i = 0; i < MAX_PLAYERS; i++)
10425 if (trigger_player_bits & (1 << i))
10427 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10429 if (action_arg == CA_ARG_SPEED_FASTER &&
10430 stored_player[i].cannot_move)
10432 action_arg_number = STEPSIZE_VERY_SLOW;
10434 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10435 action_arg == CA_ARG_SPEED_FASTER)
10437 action_arg_number = 2;
10438 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10441 else if (action_arg == CA_ARG_NUMBER_RESET)
10443 action_arg_number = level.initial_player_stepsize[i];
10447 getModifiedActionNumber(move_stepsize,
10450 action_arg_number_min,
10451 action_arg_number_max);
10453 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10460 case CA_SET_PLAYER_SHIELD:
10462 for (i = 0; i < MAX_PLAYERS; i++)
10464 if (trigger_player_bits & (1 << i))
10466 if (action_arg == CA_ARG_SHIELD_OFF)
10468 stored_player[i].shield_normal_time_left = 0;
10469 stored_player[i].shield_deadly_time_left = 0;
10471 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10473 stored_player[i].shield_normal_time_left = 999999;
10475 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10477 stored_player[i].shield_normal_time_left = 999999;
10478 stored_player[i].shield_deadly_time_left = 999999;
10486 case CA_SET_PLAYER_GRAVITY:
10488 for (i = 0; i < MAX_PLAYERS; i++)
10490 if (trigger_player_bits & (1 << i))
10492 stored_player[i].gravity =
10493 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10494 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10495 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10496 stored_player[i].gravity);
10503 case CA_SET_PLAYER_ARTWORK:
10505 for (i = 0; i < MAX_PLAYERS; i++)
10507 if (trigger_player_bits & (1 << i))
10509 int artwork_element = action_arg_element;
10511 if (action_arg == CA_ARG_ELEMENT_RESET)
10513 (level.use_artwork_element[i] ? level.artwork_element[i] :
10514 stored_player[i].element_nr);
10516 if (stored_player[i].artwork_element != artwork_element)
10517 stored_player[i].Frame = 0;
10519 stored_player[i].artwork_element = artwork_element;
10521 SetPlayerWaiting(&stored_player[i], FALSE);
10523 // set number of special actions for bored and sleeping animation
10524 stored_player[i].num_special_action_bored =
10525 get_num_special_action(artwork_element,
10526 ACTION_BORING_1, ACTION_BORING_LAST);
10527 stored_player[i].num_special_action_sleeping =
10528 get_num_special_action(artwork_element,
10529 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10536 case CA_SET_PLAYER_INVENTORY:
10538 for (i = 0; i < MAX_PLAYERS; i++)
10540 struct PlayerInfo *player = &stored_player[i];
10543 if (trigger_player_bits & (1 << i))
10545 int inventory_element = action_arg_element;
10547 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10548 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10549 action_arg == CA_ARG_ELEMENT_ACTION)
10551 int element = inventory_element;
10552 int collect_count = element_info[element].collect_count_initial;
10554 if (!IS_CUSTOM_ELEMENT(element))
10557 if (collect_count == 0)
10558 player->inventory_infinite_element = element;
10560 for (k = 0; k < collect_count; k++)
10561 if (player->inventory_size < MAX_INVENTORY_SIZE)
10562 player->inventory_element[player->inventory_size++] =
10565 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10566 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10567 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10569 if (player->inventory_infinite_element != EL_UNDEFINED &&
10570 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10571 action_arg_element_raw))
10572 player->inventory_infinite_element = EL_UNDEFINED;
10574 for (k = 0, j = 0; j < player->inventory_size; j++)
10576 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10577 action_arg_element_raw))
10578 player->inventory_element[k++] = player->inventory_element[j];
10581 player->inventory_size = k;
10583 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10585 if (player->inventory_size > 0)
10587 for (j = 0; j < player->inventory_size - 1; j++)
10588 player->inventory_element[j] = player->inventory_element[j + 1];
10590 player->inventory_size--;
10593 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10595 if (player->inventory_size > 0)
10596 player->inventory_size--;
10598 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10600 player->inventory_infinite_element = EL_UNDEFINED;
10601 player->inventory_size = 0;
10603 else if (action_arg == CA_ARG_INVENTORY_RESET)
10605 player->inventory_infinite_element = EL_UNDEFINED;
10606 player->inventory_size = 0;
10608 if (level.use_initial_inventory[i])
10610 for (j = 0; j < level.initial_inventory_size[i]; j++)
10612 int element = level.initial_inventory_content[i][j];
10613 int collect_count = element_info[element].collect_count_initial;
10615 if (!IS_CUSTOM_ELEMENT(element))
10618 if (collect_count == 0)
10619 player->inventory_infinite_element = element;
10621 for (k = 0; k < collect_count; k++)
10622 if (player->inventory_size < MAX_INVENTORY_SIZE)
10623 player->inventory_element[player->inventory_size++] =
10634 // ---------- CE actions -------------------------------------------------
10636 case CA_SET_CE_VALUE:
10638 int last_ce_value = CustomValue[x][y];
10640 CustomValue[x][y] = action_arg_number_new;
10642 if (CustomValue[x][y] != last_ce_value)
10644 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10645 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10647 if (CustomValue[x][y] == 0)
10649 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10650 ChangeCount[x][y] = 0; // allow at least one more change
10652 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10653 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10660 case CA_SET_CE_SCORE:
10662 int last_ce_score = ei->collect_score;
10664 ei->collect_score = action_arg_number_new;
10666 if (ei->collect_score != last_ce_score)
10668 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10669 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10671 if (ei->collect_score == 0)
10675 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10676 ChangeCount[x][y] = 0; // allow at least one more change
10678 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10679 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10682 This is a very special case that seems to be a mixture between
10683 CheckElementChange() and CheckTriggeredElementChange(): while
10684 the first one only affects single elements that are triggered
10685 directly, the second one affects multiple elements in the playfield
10686 that are triggered indirectly by another element. This is a third
10687 case: Changing the CE score always affects multiple identical CEs,
10688 so every affected CE must be checked, not only the single CE for
10689 which the CE score was changed in the first place (as every instance
10690 of that CE shares the same CE score, and therefore also can change)!
10692 SCAN_PLAYFIELD(xx, yy)
10694 if (Tile[xx][yy] == element)
10695 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10696 CE_SCORE_GETS_ZERO);
10704 case CA_SET_CE_ARTWORK:
10706 int artwork_element = action_arg_element;
10707 boolean reset_frame = FALSE;
10710 if (action_arg == CA_ARG_ELEMENT_RESET)
10711 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10714 if (ei->gfx_element != artwork_element)
10715 reset_frame = TRUE;
10717 ei->gfx_element = artwork_element;
10719 SCAN_PLAYFIELD(xx, yy)
10721 if (Tile[xx][yy] == element)
10725 ResetGfxAnimation(xx, yy);
10726 ResetRandomAnimationValue(xx, yy);
10729 TEST_DrawLevelField(xx, yy);
10736 // ---------- engine actions ---------------------------------------------
10738 case CA_SET_ENGINE_SCAN_MODE:
10740 InitPlayfieldScanMode(action_arg);
10750 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10752 int old_element = Tile[x][y];
10753 int new_element = GetElementFromGroupElement(element);
10754 int previous_move_direction = MovDir[x][y];
10755 int last_ce_value = CustomValue[x][y];
10756 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10757 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10758 boolean add_player_onto_element = (new_element_is_player &&
10759 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10760 IS_WALKABLE(old_element));
10762 if (!add_player_onto_element)
10764 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10765 RemoveMovingField(x, y);
10769 Tile[x][y] = new_element;
10771 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10772 MovDir[x][y] = previous_move_direction;
10774 if (element_info[new_element].use_last_ce_value)
10775 CustomValue[x][y] = last_ce_value;
10777 InitField_WithBug1(x, y, FALSE);
10779 new_element = Tile[x][y]; // element may have changed
10781 ResetGfxAnimation(x, y);
10782 ResetRandomAnimationValue(x, y);
10784 TEST_DrawLevelField(x, y);
10786 if (GFX_CRUMBLED(new_element))
10787 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10789 if (old_element == EL_EXPLOSION)
10791 Store[x][y] = Store2[x][y] = 0;
10793 // check if new element replaces an exploding player, requiring cleanup
10794 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
10795 StorePlayer[x][y] = 0;
10798 // check if element under the player changes from accessible to unaccessible
10799 // (needed for special case of dropping element which then changes)
10800 // (must be checked after creating new element for walkable group elements)
10801 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10802 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10804 KillPlayer(PLAYERINFO(x, y));
10810 // "ChangeCount" not set yet to allow "entered by player" change one time
10811 if (new_element_is_player)
10812 RelocatePlayer(x, y, new_element);
10815 ChangeCount[x][y]++; // count number of changes in the same frame
10817 TestIfBadThingTouchesPlayer(x, y);
10818 TestIfPlayerTouchesCustomElement(x, y);
10819 TestIfElementTouchesCustomElement(x, y);
10822 static void CreateField(int x, int y, int element)
10824 CreateFieldExt(x, y, element, FALSE);
10827 static void CreateElementFromChange(int x, int y, int element)
10829 element = GET_VALID_RUNTIME_ELEMENT(element);
10831 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10833 int old_element = Tile[x][y];
10835 // prevent changed element from moving in same engine frame
10836 // unless both old and new element can either fall or move
10837 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10838 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10842 CreateFieldExt(x, y, element, TRUE);
10845 static boolean ChangeElement(int x, int y, int element, int page)
10847 struct ElementInfo *ei = &element_info[element];
10848 struct ElementChangeInfo *change = &ei->change_page[page];
10849 int ce_value = CustomValue[x][y];
10850 int ce_score = ei->collect_score;
10851 int target_element;
10852 int old_element = Tile[x][y];
10854 // always use default change event to prevent running into a loop
10855 if (ChangeEvent[x][y] == -1)
10856 ChangeEvent[x][y] = CE_DELAY;
10858 if (ChangeEvent[x][y] == CE_DELAY)
10860 // reset actual trigger element, trigger player and action element
10861 change->actual_trigger_element = EL_EMPTY;
10862 change->actual_trigger_player = EL_EMPTY;
10863 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10864 change->actual_trigger_side = CH_SIDE_NONE;
10865 change->actual_trigger_ce_value = 0;
10866 change->actual_trigger_ce_score = 0;
10867 change->actual_trigger_x = -1;
10868 change->actual_trigger_y = -1;
10871 // do not change elements more than a specified maximum number of changes
10872 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10875 ChangeCount[x][y]++; // count number of changes in the same frame
10877 if (ei->has_anim_event)
10878 HandleGlobalAnimEventByElementChange(element, page, x, y,
10879 change->actual_trigger_x,
10880 change->actual_trigger_y);
10882 if (change->explode)
10889 if (change->use_target_content)
10891 boolean complete_replace = TRUE;
10892 boolean can_replace[3][3];
10895 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10898 boolean is_walkable;
10899 boolean is_diggable;
10900 boolean is_collectible;
10901 boolean is_removable;
10902 boolean is_destructible;
10903 int ex = x + xx - 1;
10904 int ey = y + yy - 1;
10905 int content_element = change->target_content.e[xx][yy];
10908 can_replace[xx][yy] = TRUE;
10910 if (ex == x && ey == y) // do not check changing element itself
10913 if (content_element == EL_EMPTY_SPACE)
10915 can_replace[xx][yy] = FALSE; // do not replace border with space
10920 if (!IN_LEV_FIELD(ex, ey))
10922 can_replace[xx][yy] = FALSE;
10923 complete_replace = FALSE;
10930 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10931 e = MovingOrBlocked2Element(ex, ey);
10933 is_empty = (IS_FREE(ex, ey) ||
10934 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10936 is_walkable = (is_empty || IS_WALKABLE(e));
10937 is_diggable = (is_empty || IS_DIGGABLE(e));
10938 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10939 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10940 is_removable = (is_diggable || is_collectible);
10942 can_replace[xx][yy] =
10943 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10944 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10945 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10946 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10947 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10948 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10949 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10951 if (!can_replace[xx][yy])
10952 complete_replace = FALSE;
10955 if (!change->only_if_complete || complete_replace)
10957 boolean something_has_changed = FALSE;
10959 if (change->only_if_complete && change->use_random_replace &&
10960 RND(100) < change->random_percentage)
10963 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10965 int ex = x + xx - 1;
10966 int ey = y + yy - 1;
10967 int content_element;
10969 if (can_replace[xx][yy] && (!change->use_random_replace ||
10970 RND(100) < change->random_percentage))
10972 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10973 RemoveMovingField(ex, ey);
10975 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10977 content_element = change->target_content.e[xx][yy];
10978 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10979 ce_value, ce_score);
10981 CreateElementFromChange(ex, ey, target_element);
10983 something_has_changed = TRUE;
10985 // for symmetry reasons, freeze newly created border elements
10986 if (ex != x || ey != y)
10987 Stop[ex][ey] = TRUE; // no more moving in this frame
10991 if (something_has_changed)
10993 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10994 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
11000 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
11001 ce_value, ce_score);
11003 if (element == EL_DIAGONAL_GROWING ||
11004 element == EL_DIAGONAL_SHRINKING)
11006 target_element = Store[x][y];
11008 Store[x][y] = EL_EMPTY;
11011 // special case: element changes to player (and may be kept if walkable)
11012 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
11013 CreateElementFromChange(x, y, EL_EMPTY);
11015 CreateElementFromChange(x, y, target_element);
11017 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
11018 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
11021 // this uses direct change before indirect change
11022 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
11027 static void HandleElementChange(int x, int y, int page)
11029 int element = MovingOrBlocked2Element(x, y);
11030 struct ElementInfo *ei = &element_info[element];
11031 struct ElementChangeInfo *change = &ei->change_page[page];
11032 boolean handle_action_before_change = FALSE;
11035 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
11036 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
11038 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
11039 x, y, element, element_info[element].token_name);
11040 Debug("game:playing:HandleElementChange", "This should never happen!");
11044 // this can happen with classic bombs on walkable, changing elements
11045 if (!CAN_CHANGE_OR_HAS_ACTION(element))
11050 if (ChangeDelay[x][y] == 0) // initialize element change
11052 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
11054 if (change->can_change)
11056 // !!! not clear why graphic animation should be reset at all here !!!
11057 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
11058 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
11061 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
11063 When using an animation frame delay of 1 (this only happens with
11064 "sp_zonk.moving.left/right" in the classic graphics), the default
11065 (non-moving) animation shows wrong animation frames (while the
11066 moving animation, like "sp_zonk.moving.left/right", is correct,
11067 so this graphical bug never shows up with the classic graphics).
11068 For an animation with 4 frames, this causes wrong frames 0,0,1,2
11069 be drawn instead of the correct frames 0,1,2,3. This is caused by
11070 "GfxFrame[][]" being reset *twice* (in two successive frames) after
11071 an element change: First when the change delay ("ChangeDelay[][]")
11072 counter has reached zero after decrementing, then a second time in
11073 the next frame (after "GfxFrame[][]" was already incremented) when
11074 "ChangeDelay[][]" is reset to the initial delay value again.
11076 This causes frame 0 to be drawn twice, while the last frame won't
11077 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
11079 As some animations may already be cleverly designed around this bug
11080 (at least the "Snake Bite" snake tail animation does this), it cannot
11081 simply be fixed here without breaking such existing animations.
11082 Unfortunately, it cannot easily be detected if a graphics set was
11083 designed "before" or "after" the bug was fixed. As a workaround,
11084 a new graphics set option "game.graphics_engine_version" was added
11085 to be able to specify the game's major release version for which the
11086 graphics set was designed, which can then be used to decide if the
11087 bugfix should be used (version 4 and above) or not (version 3 or
11088 below, or if no version was specified at all, as with old sets).
11090 (The wrong/fixed animation frames can be tested with the test level set
11091 "test_gfxframe" and level "000", which contains a specially prepared
11092 custom element at level position (x/y) == (11/9) which uses the zonk
11093 animation mentioned above. Using "game.graphics_engine_version: 4"
11094 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
11095 This can also be seen from the debug output for this test element.)
11098 // when a custom element is about to change (for example by change delay),
11099 // do not reset graphic animation when the custom element is moving
11100 if (game.graphics_engine_version < 4 &&
11103 ResetGfxAnimation(x, y);
11104 ResetRandomAnimationValue(x, y);
11107 if (change->pre_change_function)
11108 change->pre_change_function(x, y);
11112 ChangeDelay[x][y]--;
11114 if (ChangeDelay[x][y] != 0) // continue element change
11116 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11118 // also needed if CE can not change, but has CE delay with CE action
11119 if (IS_ANIMATED(graphic))
11120 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11122 if (change->can_change)
11124 if (change->change_function)
11125 change->change_function(x, y);
11128 else // finish element change
11130 if (ChangePage[x][y] != -1) // remember page from delayed change
11132 page = ChangePage[x][y];
11133 ChangePage[x][y] = -1;
11135 change = &ei->change_page[page];
11138 if (IS_MOVING(x, y)) // never change a running system ;-)
11140 ChangeDelay[x][y] = 1; // try change after next move step
11141 ChangePage[x][y] = page; // remember page to use for change
11146 // special case: set new level random seed before changing element
11147 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11148 handle_action_before_change = TRUE;
11150 if (change->has_action && handle_action_before_change)
11151 ExecuteCustomElementAction(x, y, element, page);
11153 if (change->can_change)
11155 if (ChangeElement(x, y, element, page))
11157 if (change->post_change_function)
11158 change->post_change_function(x, y);
11162 if (change->has_action && !handle_action_before_change)
11163 ExecuteCustomElementAction(x, y, element, page);
11167 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11168 int trigger_element,
11170 int trigger_player,
11174 boolean change_done_any = FALSE;
11175 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11178 if (!(trigger_events[trigger_element][trigger_event]))
11181 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11183 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11185 int element = EL_CUSTOM_START + i;
11186 boolean change_done = FALSE;
11189 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11190 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11193 for (p = 0; p < element_info[element].num_change_pages; p++)
11195 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11197 if (change->can_change_or_has_action &&
11198 change->has_event[trigger_event] &&
11199 change->trigger_side & trigger_side &&
11200 change->trigger_player & trigger_player &&
11201 change->trigger_page & trigger_page_bits &&
11202 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11204 change->actual_trigger_element = trigger_element;
11205 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11206 change->actual_trigger_player_bits = trigger_player;
11207 change->actual_trigger_side = trigger_side;
11208 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11209 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11210 change->actual_trigger_x = trigger_x;
11211 change->actual_trigger_y = trigger_y;
11213 if ((change->can_change && !change_done) || change->has_action)
11217 SCAN_PLAYFIELD(x, y)
11219 if (Tile[x][y] == element)
11221 if (change->can_change && !change_done)
11223 // if element already changed in this frame, not only prevent
11224 // another element change (checked in ChangeElement()), but
11225 // also prevent additional element actions for this element
11227 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11228 !level.use_action_after_change_bug)
11231 ChangeDelay[x][y] = 1;
11232 ChangeEvent[x][y] = trigger_event;
11234 HandleElementChange(x, y, p);
11236 else if (change->has_action)
11238 // if element already changed in this frame, not only prevent
11239 // another element change (checked in ChangeElement()), but
11240 // also prevent additional element actions for this element
11242 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11243 !level.use_action_after_change_bug)
11246 ExecuteCustomElementAction(x, y, element, p);
11247 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11252 if (change->can_change)
11254 change_done = TRUE;
11255 change_done_any = TRUE;
11262 RECURSION_LOOP_DETECTION_END();
11264 return change_done_any;
11267 static boolean CheckElementChangeExt(int x, int y,
11269 int trigger_element,
11271 int trigger_player,
11274 boolean change_done = FALSE;
11277 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11278 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11281 if (Tile[x][y] == EL_BLOCKED)
11283 Blocked2Moving(x, y, &x, &y);
11284 element = Tile[x][y];
11287 // check if element has already changed or is about to change after moving
11288 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11289 Tile[x][y] != element) ||
11291 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11292 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11293 ChangePage[x][y] != -1)))
11296 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11298 for (p = 0; p < element_info[element].num_change_pages; p++)
11300 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11302 /* check trigger element for all events where the element that is checked
11303 for changing interacts with a directly adjacent element -- this is
11304 different to element changes that affect other elements to change on the
11305 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11306 boolean check_trigger_element =
11307 (trigger_event == CE_NEXT_TO_X ||
11308 trigger_event == CE_TOUCHING_X ||
11309 trigger_event == CE_HITTING_X ||
11310 trigger_event == CE_HIT_BY_X ||
11311 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11313 if (change->can_change_or_has_action &&
11314 change->has_event[trigger_event] &&
11315 change->trigger_side & trigger_side &&
11316 change->trigger_player & trigger_player &&
11317 (!check_trigger_element ||
11318 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11320 change->actual_trigger_element = trigger_element;
11321 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11322 change->actual_trigger_player_bits = trigger_player;
11323 change->actual_trigger_side = trigger_side;
11324 change->actual_trigger_ce_value = CustomValue[x][y];
11325 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11326 change->actual_trigger_x = x;
11327 change->actual_trigger_y = y;
11329 // special case: trigger element not at (x,y) position for some events
11330 if (check_trigger_element)
11342 { 0, 0 }, { 0, 0 }, { 0, 0 },
11346 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11347 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11349 change->actual_trigger_ce_value = CustomValue[xx][yy];
11350 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11351 change->actual_trigger_x = xx;
11352 change->actual_trigger_y = yy;
11355 if (change->can_change && !change_done)
11357 ChangeDelay[x][y] = 1;
11358 ChangeEvent[x][y] = trigger_event;
11360 HandleElementChange(x, y, p);
11362 change_done = TRUE;
11364 else if (change->has_action)
11366 ExecuteCustomElementAction(x, y, element, p);
11367 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11372 RECURSION_LOOP_DETECTION_END();
11374 return change_done;
11377 static void PlayPlayerSound(struct PlayerInfo *player)
11379 int jx = player->jx, jy = player->jy;
11380 int sound_element = player->artwork_element;
11381 int last_action = player->last_action_waiting;
11382 int action = player->action_waiting;
11384 if (player->is_waiting)
11386 if (action != last_action)
11387 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11389 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11393 if (action != last_action)
11394 StopSound(element_info[sound_element].sound[last_action]);
11396 if (last_action == ACTION_SLEEPING)
11397 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11401 static void PlayAllPlayersSound(void)
11405 for (i = 0; i < MAX_PLAYERS; i++)
11406 if (stored_player[i].active)
11407 PlayPlayerSound(&stored_player[i]);
11410 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11412 boolean last_waiting = player->is_waiting;
11413 int move_dir = player->MovDir;
11415 player->dir_waiting = move_dir;
11416 player->last_action_waiting = player->action_waiting;
11420 if (!last_waiting) // not waiting -> waiting
11422 player->is_waiting = TRUE;
11424 player->frame_counter_bored =
11426 game.player_boring_delay_fixed +
11427 GetSimpleRandom(game.player_boring_delay_random);
11428 player->frame_counter_sleeping =
11430 game.player_sleeping_delay_fixed +
11431 GetSimpleRandom(game.player_sleeping_delay_random);
11433 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11436 if (game.player_sleeping_delay_fixed +
11437 game.player_sleeping_delay_random > 0 &&
11438 player->anim_delay_counter == 0 &&
11439 player->post_delay_counter == 0 &&
11440 FrameCounter >= player->frame_counter_sleeping)
11441 player->is_sleeping = TRUE;
11442 else if (game.player_boring_delay_fixed +
11443 game.player_boring_delay_random > 0 &&
11444 FrameCounter >= player->frame_counter_bored)
11445 player->is_bored = TRUE;
11447 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11448 player->is_bored ? ACTION_BORING :
11451 if (player->is_sleeping && player->use_murphy)
11453 // special case for sleeping Murphy when leaning against non-free tile
11455 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11456 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11457 !IS_MOVING(player->jx - 1, player->jy)))
11458 move_dir = MV_LEFT;
11459 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11460 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11461 !IS_MOVING(player->jx + 1, player->jy)))
11462 move_dir = MV_RIGHT;
11464 player->is_sleeping = FALSE;
11466 player->dir_waiting = move_dir;
11469 if (player->is_sleeping)
11471 if (player->num_special_action_sleeping > 0)
11473 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11475 int last_special_action = player->special_action_sleeping;
11476 int num_special_action = player->num_special_action_sleeping;
11477 int special_action =
11478 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11479 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11480 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11481 last_special_action + 1 : ACTION_SLEEPING);
11482 int special_graphic =
11483 el_act_dir2img(player->artwork_element, special_action, move_dir);
11485 player->anim_delay_counter =
11486 graphic_info[special_graphic].anim_delay_fixed +
11487 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11488 player->post_delay_counter =
11489 graphic_info[special_graphic].post_delay_fixed +
11490 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11492 player->special_action_sleeping = special_action;
11495 if (player->anim_delay_counter > 0)
11497 player->action_waiting = player->special_action_sleeping;
11498 player->anim_delay_counter--;
11500 else if (player->post_delay_counter > 0)
11502 player->post_delay_counter--;
11506 else if (player->is_bored)
11508 if (player->num_special_action_bored > 0)
11510 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11512 int special_action =
11513 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11514 int special_graphic =
11515 el_act_dir2img(player->artwork_element, special_action, move_dir);
11517 player->anim_delay_counter =
11518 graphic_info[special_graphic].anim_delay_fixed +
11519 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11520 player->post_delay_counter =
11521 graphic_info[special_graphic].post_delay_fixed +
11522 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11524 player->special_action_bored = special_action;
11527 if (player->anim_delay_counter > 0)
11529 player->action_waiting = player->special_action_bored;
11530 player->anim_delay_counter--;
11532 else if (player->post_delay_counter > 0)
11534 player->post_delay_counter--;
11539 else if (last_waiting) // waiting -> not waiting
11541 player->is_waiting = FALSE;
11542 player->is_bored = FALSE;
11543 player->is_sleeping = FALSE;
11545 player->frame_counter_bored = -1;
11546 player->frame_counter_sleeping = -1;
11548 player->anim_delay_counter = 0;
11549 player->post_delay_counter = 0;
11551 player->dir_waiting = player->MovDir;
11552 player->action_waiting = ACTION_DEFAULT;
11554 player->special_action_bored = ACTION_DEFAULT;
11555 player->special_action_sleeping = ACTION_DEFAULT;
11559 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11561 if ((!player->is_moving && player->was_moving) ||
11562 (player->MovPos == 0 && player->was_moving) ||
11563 (player->is_snapping && !player->was_snapping) ||
11564 (player->is_dropping && !player->was_dropping))
11566 if (!CheckSaveEngineSnapshotToList())
11569 player->was_moving = FALSE;
11570 player->was_snapping = TRUE;
11571 player->was_dropping = TRUE;
11575 if (player->is_moving)
11576 player->was_moving = TRUE;
11578 if (!player->is_snapping)
11579 player->was_snapping = FALSE;
11581 if (!player->is_dropping)
11582 player->was_dropping = FALSE;
11585 static struct MouseActionInfo mouse_action_last = { 0 };
11586 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11587 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11590 CheckSaveEngineSnapshotToList();
11592 mouse_action_last = mouse_action;
11595 static void CheckSingleStepMode(struct PlayerInfo *player)
11597 if (tape.single_step && tape.recording && !tape.pausing)
11599 // as it is called "single step mode", just return to pause mode when the
11600 // player stopped moving after one tile (or never starts moving at all)
11601 // (reverse logic needed here in case single step mode used in team mode)
11602 if (player->is_moving ||
11603 player->is_pushing ||
11604 player->is_dropping_pressed ||
11605 player->effective_mouse_action.button)
11606 game.enter_single_step_mode = FALSE;
11609 CheckSaveEngineSnapshot(player);
11612 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11614 int left = player_action & JOY_LEFT;
11615 int right = player_action & JOY_RIGHT;
11616 int up = player_action & JOY_UP;
11617 int down = player_action & JOY_DOWN;
11618 int button1 = player_action & JOY_BUTTON_1;
11619 int button2 = player_action & JOY_BUTTON_2;
11620 int dx = (left ? -1 : right ? 1 : 0);
11621 int dy = (up ? -1 : down ? 1 : 0);
11623 if (!player->active || tape.pausing)
11629 SnapField(player, dx, dy);
11633 DropElement(player);
11635 MovePlayer(player, dx, dy);
11638 CheckSingleStepMode(player);
11640 SetPlayerWaiting(player, FALSE);
11642 return player_action;
11646 // no actions for this player (no input at player's configured device)
11648 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11649 SnapField(player, 0, 0);
11650 CheckGravityMovementWhenNotMoving(player);
11652 if (player->MovPos == 0)
11653 SetPlayerWaiting(player, TRUE);
11655 if (player->MovPos == 0) // needed for tape.playing
11656 player->is_moving = FALSE;
11658 player->is_dropping = FALSE;
11659 player->is_dropping_pressed = FALSE;
11660 player->drop_pressed_delay = 0;
11662 CheckSingleStepMode(player);
11668 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11671 if (!tape.use_mouse_actions)
11674 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11675 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11676 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11679 static void SetTapeActionFromMouseAction(byte *tape_action,
11680 struct MouseActionInfo *mouse_action)
11682 if (!tape.use_mouse_actions)
11685 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11686 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11687 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11690 static void CheckLevelSolved(void)
11692 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11694 if (game_bd.level_solved &&
11695 !game_bd.game_over) // game won
11699 game_bd.game_over = TRUE;
11701 game.all_players_gone = TRUE;
11704 if (game_bd.game_over) // game lost
11705 game.all_players_gone = TRUE;
11707 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11709 if (game_em.level_solved &&
11710 !game_em.game_over) // game won
11714 game_em.game_over = TRUE;
11716 game.all_players_gone = TRUE;
11719 if (game_em.game_over) // game lost
11720 game.all_players_gone = TRUE;
11722 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11724 if (game_sp.level_solved &&
11725 !game_sp.game_over) // game won
11729 game_sp.game_over = TRUE;
11731 game.all_players_gone = TRUE;
11734 if (game_sp.game_over) // game lost
11735 game.all_players_gone = TRUE;
11737 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11739 if (game_mm.level_solved &&
11740 !game_mm.game_over) // game won
11744 game_mm.game_over = TRUE;
11746 game.all_players_gone = TRUE;
11749 if (game_mm.game_over) // game lost
11750 game.all_players_gone = TRUE;
11754 static void PlayTimeoutSound(int seconds_left)
11756 // will be played directly by BD engine (for classic bonus time sounds)
11757 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && checkBonusTime_BD())
11760 // try to use individual "running out of time" sound for each second left
11761 int sound = SND_GAME_RUNNING_OUT_OF_TIME_0 - seconds_left;
11763 // if special sound per second not defined, use default sound
11764 if (getSoundInfoEntryFilename(sound) == NULL)
11765 sound = SND_GAME_RUNNING_OUT_OF_TIME;
11767 // if out of time, but player still alive, play special "timeout" sound, if defined
11768 if (seconds_left == 0 && !checkGameFailed())
11769 if (getSoundInfoEntryFilename(SND_GAME_TIMEOUT) != NULL)
11770 sound = SND_GAME_TIMEOUT;
11775 static void CheckLevelTime_StepCounter(void)
11785 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11786 PlayTimeoutSound(TimeLeft);
11788 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11790 DisplayGameControlValues();
11792 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11793 for (i = 0; i < MAX_PLAYERS; i++)
11794 KillPlayer(&stored_player[i]);
11796 else if (game.no_level_time_limit && !game.all_players_gone)
11798 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11800 DisplayGameControlValues();
11804 static void CheckLevelTime(void)
11806 int frames_per_second = FRAMES_PER_SECOND;
11809 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11811 // level time may be running slower in native BD engine
11812 frames_per_second = getFramesPerSecond_BD();
11814 // if native engine time changed, force main engine time change
11815 if (getTimeLeft_BD() < TimeLeft)
11816 TimeFrames = frames_per_second;
11818 // if last second running, wait for native engine time to exactly reach zero
11819 if (getTimeLeft_BD() == 1 && TimeLeft == 1)
11820 TimeFrames = frames_per_second - 1;
11823 if (TimeFrames >= frames_per_second)
11827 for (i = 0; i < MAX_PLAYERS; i++)
11829 struct PlayerInfo *player = &stored_player[i];
11831 if (SHIELD_ON(player))
11833 player->shield_normal_time_left--;
11835 if (player->shield_deadly_time_left > 0)
11836 player->shield_deadly_time_left--;
11840 if (!game.LevelSolved && !level.use_step_counter)
11848 if (TimeLeft <= 10 && game.time_limit)
11849 PlayTimeoutSound(TimeLeft);
11851 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11852 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11854 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11856 if (!TimeLeft && game.time_limit)
11858 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11860 if (game_bd.game->cave->player_state == GD_PL_LIVING)
11861 game_bd.game->cave->player_state = GD_PL_TIMEOUT;
11863 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11865 game_em.lev->killed_out_of_time = TRUE;
11869 for (i = 0; i < MAX_PLAYERS; i++)
11870 KillPlayer(&stored_player[i]);
11874 else if (game.no_level_time_limit && !game.all_players_gone)
11876 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11879 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11883 if (TapeTimeFrames >= FRAMES_PER_SECOND)
11885 TapeTimeFrames = 0;
11888 if (tape.recording || tape.playing)
11889 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11892 if (tape.recording || tape.playing)
11893 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11895 UpdateAndDisplayGameControlValues();
11898 void AdvanceFrameAndPlayerCounters(int player_nr)
11902 // handle game and tape time differently for native BD game engine
11904 // tape time is running in native BD engine even if player is not hatched yet
11905 if (!checkGameRunning())
11908 // advance frame counters (global frame counter and tape time frame counter)
11912 // level time is running in native BD engine after player is being hatched
11913 if (!checkGamePlaying())
11916 // advance time frame counter (used to control available time to solve level)
11919 // advance player counters (counters for move delay, move animation etc.)
11920 for (i = 0; i < MAX_PLAYERS; i++)
11922 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11923 int move_delay_value = stored_player[i].move_delay_value;
11924 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11926 if (!advance_player_counters) // not all players may be affected
11929 if (move_frames == 0) // less than one move per game frame
11931 int stepsize = TILEX / move_delay_value;
11932 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11933 int count = (stored_player[i].is_moving ?
11934 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11936 if (count % delay == 0)
11940 stored_player[i].Frame += move_frames;
11942 if (stored_player[i].MovPos != 0)
11943 stored_player[i].StepFrame += move_frames;
11945 if (stored_player[i].move_delay > 0)
11946 stored_player[i].move_delay--;
11948 // due to bugs in previous versions, counter must count up, not down
11949 if (stored_player[i].push_delay != -1)
11950 stored_player[i].push_delay++;
11952 if (stored_player[i].drop_delay > 0)
11953 stored_player[i].drop_delay--;
11955 if (stored_player[i].is_dropping_pressed)
11956 stored_player[i].drop_pressed_delay++;
11960 void AdvanceFrameCounter(void)
11965 void AdvanceGfxFrame(void)
11969 SCAN_PLAYFIELD(x, y)
11975 static void HandleMouseAction(struct MouseActionInfo *mouse_action,
11976 struct MouseActionInfo *mouse_action_last)
11978 if (mouse_action->button)
11980 int new_button = (mouse_action->button && mouse_action_last->button == 0);
11981 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button);
11982 int x = mouse_action->lx;
11983 int y = mouse_action->ly;
11984 int element = Tile[x][y];
11988 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
11989 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
11993 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
11994 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
11997 if (level.use_step_counter)
11999 boolean counted_click = FALSE;
12001 // element clicked that can change when clicked/pressed
12002 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
12003 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
12004 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
12005 counted_click = TRUE;
12007 // element clicked that can trigger change when clicked/pressed
12008 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
12009 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
12010 counted_click = TRUE;
12012 if (new_button && counted_click)
12013 CheckLevelTime_StepCounter();
12018 void StartGameActions(boolean init_network_game, boolean record_tape,
12021 unsigned int new_random_seed = InitRND(random_seed);
12024 TapeStartRecording(new_random_seed);
12026 if (setup.auto_pause_on_start && !tape.pausing)
12027 TapeTogglePause(TAPE_TOGGLE_MANUAL);
12029 if (init_network_game)
12031 SendToServer_LevelFile();
12032 SendToServer_StartPlaying();
12040 static void GameActionsExt(void)
12043 static unsigned int game_frame_delay = 0;
12045 unsigned int game_frame_delay_value;
12046 byte *recorded_player_action;
12047 byte summarized_player_action = 0;
12048 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
12051 // detect endless loops, caused by custom element programming
12052 if (recursion_loop_detected && recursion_loop_depth == 0)
12054 char *message = getStringCat3("Internal Error! Element ",
12055 EL_NAME(recursion_loop_element),
12056 " caused endless loop! Quit the game?");
12058 Warn("element '%s' caused endless loop in game engine",
12059 EL_NAME(recursion_loop_element));
12061 RequestQuitGameExt(program.headless, level_editor_test_game, message);
12063 recursion_loop_detected = FALSE; // if game should be continued
12070 if (game.restart_level)
12071 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
12073 CheckLevelSolved();
12075 if (game.LevelSolved && !game.LevelSolved_GameEnd)
12078 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
12081 if (game_status != GAME_MODE_PLAYING) // status might have changed
12084 game_frame_delay_value =
12085 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
12087 if (tape.playing && tape.warp_forward && !tape.pausing)
12088 game_frame_delay_value = 0;
12090 SetVideoFrameDelay(game_frame_delay_value);
12092 // (de)activate virtual buttons depending on current game status
12093 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
12095 if (game.all_players_gone) // if no players there to be controlled anymore
12096 SetOverlayActive(FALSE);
12097 else if (!tape.playing) // if game continues after tape stopped playing
12098 SetOverlayActive(TRUE);
12103 // ---------- main game synchronization point ----------
12105 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12107 Debug("game:playing:skip", "skip == %d", skip);
12110 // ---------- main game synchronization point ----------
12112 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12116 if (network_playing && !network_player_action_received)
12118 // try to get network player actions in time
12120 // last chance to get network player actions without main loop delay
12121 HandleNetworking();
12123 // game was quit by network peer
12124 if (game_status != GAME_MODE_PLAYING)
12127 // check if network player actions still missing and game still running
12128 if (!network_player_action_received && !checkGameEnded())
12129 return; // failed to get network player actions in time
12131 // do not yet reset "network_player_action_received" (for tape.pausing)
12137 // at this point we know that we really continue executing the game
12139 network_player_action_received = FALSE;
12141 // when playing tape, read previously recorded player input from tape data
12142 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
12144 local_player->effective_mouse_action = local_player->mouse_action;
12146 if (recorded_player_action != NULL)
12147 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
12148 recorded_player_action);
12150 // TapePlayAction() may return NULL when toggling to "pause before death"
12154 if (tape.set_centered_player)
12156 game.centered_player_nr_next = tape.centered_player_nr_next;
12157 game.set_centered_player = TRUE;
12160 for (i = 0; i < MAX_PLAYERS; i++)
12162 summarized_player_action |= stored_player[i].action;
12164 if (!network_playing && (game.team_mode || tape.playing))
12165 stored_player[i].effective_action = stored_player[i].action;
12168 if (network_playing && !checkGameEnded())
12169 SendToServer_MovePlayer(summarized_player_action);
12171 // summarize all actions at local players mapped input device position
12172 // (this allows using different input devices in single player mode)
12173 if (!network.enabled && !game.team_mode)
12174 stored_player[map_player_action[local_player->index_nr]].effective_action =
12175 summarized_player_action;
12177 // summarize all actions at centered player in local team mode
12178 if (tape.recording &&
12179 setup.team_mode && !network.enabled &&
12180 setup.input_on_focus &&
12181 game.centered_player_nr != -1)
12183 for (i = 0; i < MAX_PLAYERS; i++)
12184 stored_player[map_player_action[i]].effective_action =
12185 (i == game.centered_player_nr ? summarized_player_action : 0);
12188 if (recorded_player_action != NULL)
12189 for (i = 0; i < MAX_PLAYERS; i++)
12190 stored_player[i].effective_action = recorded_player_action[i];
12192 for (i = 0; i < MAX_PLAYERS; i++)
12194 tape_action[i] = stored_player[i].effective_action;
12196 /* (this may happen in the RND game engine if a player was not present on
12197 the playfield on level start, but appeared later from a custom element */
12198 if (setup.team_mode &&
12201 !tape.player_participates[i])
12202 tape.player_participates[i] = TRUE;
12205 SetTapeActionFromMouseAction(tape_action,
12206 &local_player->effective_mouse_action);
12208 // only record actions from input devices, but not programmed actions
12209 if (tape.recording)
12210 TapeRecordAction(tape_action);
12212 // remember if game was played (especially after tape stopped playing)
12213 if (!tape.playing && summarized_player_action && !checkGameFailed())
12214 game.GamePlayed = TRUE;
12216 #if USE_NEW_PLAYER_ASSIGNMENTS
12217 // !!! also map player actions in single player mode !!!
12218 // if (game.team_mode)
12221 byte mapped_action[MAX_PLAYERS];
12223 #if DEBUG_PLAYER_ACTIONS
12224 for (i = 0; i < MAX_PLAYERS; i++)
12225 DebugContinued("", "%d, ", stored_player[i].effective_action);
12228 for (i = 0; i < MAX_PLAYERS; i++)
12229 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
12231 for (i = 0; i < MAX_PLAYERS; i++)
12232 stored_player[i].effective_action = mapped_action[i];
12234 #if DEBUG_PLAYER_ACTIONS
12235 DebugContinued("", "=> ");
12236 for (i = 0; i < MAX_PLAYERS; i++)
12237 DebugContinued("", "%d, ", stored_player[i].effective_action);
12238 DebugContinued("game:playing:player", "\n");
12241 #if DEBUG_PLAYER_ACTIONS
12244 for (i = 0; i < MAX_PLAYERS; i++)
12245 DebugContinued("", "%d, ", stored_player[i].effective_action);
12246 DebugContinued("game:playing:player", "\n");
12251 for (i = 0; i < MAX_PLAYERS; i++)
12253 // allow engine snapshot in case of changed movement attempt
12254 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12255 (stored_player[i].effective_action & KEY_MOTION))
12256 game.snapshot.changed_action = TRUE;
12258 // allow engine snapshot in case of snapping/dropping attempt
12259 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12260 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12261 game.snapshot.changed_action = TRUE;
12263 game.snapshot.last_action[i] = stored_player[i].effective_action;
12266 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
12268 GameActions_BD_Main();
12270 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12272 GameActions_EM_Main();
12274 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12276 GameActions_SP_Main();
12278 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12280 GameActions_MM_Main();
12284 GameActions_RND_Main();
12287 BlitScreenToBitmap(backbuffer);
12289 CheckLevelSolved();
12292 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12294 if (global.show_frames_per_second)
12296 static unsigned int fps_counter = 0;
12297 static int fps_frames = 0;
12298 unsigned int fps_delay_ms = Counter() - fps_counter;
12302 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12304 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12307 fps_counter = Counter();
12309 // always draw FPS to screen after FPS value was updated
12310 redraw_mask |= REDRAW_FPS;
12313 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12314 if (GetDrawDeactivationMask() == REDRAW_NONE)
12315 redraw_mask |= REDRAW_FPS;
12319 static void GameActions_CheckSaveEngineSnapshot(void)
12321 if (!game.snapshot.save_snapshot)
12324 // clear flag for saving snapshot _before_ saving snapshot
12325 game.snapshot.save_snapshot = FALSE;
12327 SaveEngineSnapshotToList();
12330 void GameActions(void)
12334 GameActions_CheckSaveEngineSnapshot();
12337 void GameActions_BD_Main(void)
12339 byte effective_action[MAX_PLAYERS];
12342 for (i = 0; i < MAX_PLAYERS; i++)
12343 effective_action[i] = stored_player[i].effective_action;
12345 GameActions_BD(effective_action);
12348 void GameActions_EM_Main(void)
12350 byte effective_action[MAX_PLAYERS];
12353 for (i = 0; i < MAX_PLAYERS; i++)
12354 effective_action[i] = stored_player[i].effective_action;
12356 GameActions_EM(effective_action);
12359 void GameActions_SP_Main(void)
12361 byte effective_action[MAX_PLAYERS];
12364 for (i = 0; i < MAX_PLAYERS; i++)
12365 effective_action[i] = stored_player[i].effective_action;
12367 GameActions_SP(effective_action);
12369 for (i = 0; i < MAX_PLAYERS; i++)
12371 if (stored_player[i].force_dropping)
12372 stored_player[i].action |= KEY_BUTTON_DROP;
12374 stored_player[i].force_dropping = FALSE;
12378 void GameActions_MM_Main(void)
12382 GameActions_MM(local_player->effective_mouse_action);
12385 void GameActions_RND_Main(void)
12390 void GameActions_RND(void)
12392 static struct MouseActionInfo mouse_action_last = { 0 };
12393 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12394 int magic_wall_x = 0, magic_wall_y = 0;
12395 int i, x, y, element, graphic, last_gfx_frame;
12397 InitPlayfieldScanModeVars();
12399 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12401 SCAN_PLAYFIELD(x, y)
12403 ChangeCount[x][y] = 0;
12404 ChangeEvent[x][y] = -1;
12408 if (game.set_centered_player)
12410 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12412 // switching to "all players" only possible if all players fit to screen
12413 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12415 game.centered_player_nr_next = game.centered_player_nr;
12416 game.set_centered_player = FALSE;
12419 // do not switch focus to non-existing (or non-active) player
12420 if (game.centered_player_nr_next >= 0 &&
12421 !stored_player[game.centered_player_nr_next].active)
12423 game.centered_player_nr_next = game.centered_player_nr;
12424 game.set_centered_player = FALSE;
12428 if (game.set_centered_player &&
12429 ScreenMovPos == 0) // screen currently aligned at tile position
12433 if (game.centered_player_nr_next == -1)
12435 setScreenCenteredToAllPlayers(&sx, &sy);
12439 sx = stored_player[game.centered_player_nr_next].jx;
12440 sy = stored_player[game.centered_player_nr_next].jy;
12443 game.centered_player_nr = game.centered_player_nr_next;
12444 game.set_centered_player = FALSE;
12446 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12447 DrawGameDoorValues();
12450 // check single step mode (set flag and clear again if any player is active)
12451 game.enter_single_step_mode =
12452 (tape.single_step && tape.recording && !tape.pausing);
12454 for (i = 0; i < MAX_PLAYERS; i++)
12456 int actual_player_action = stored_player[i].effective_action;
12459 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12460 - rnd_equinox_tetrachloride 048
12461 - rnd_equinox_tetrachloride_ii 096
12462 - rnd_emanuel_schmieg 002
12463 - doctor_sloan_ww 001, 020
12465 if (stored_player[i].MovPos == 0)
12466 CheckGravityMovement(&stored_player[i]);
12469 // overwrite programmed action with tape action
12470 if (stored_player[i].programmed_action)
12471 actual_player_action = stored_player[i].programmed_action;
12473 PlayerActions(&stored_player[i], actual_player_action);
12475 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12478 // single step pause mode may already have been toggled by "ScrollPlayer()"
12479 if (game.enter_single_step_mode && !tape.pausing)
12480 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12482 ScrollScreen(NULL, SCROLL_GO_ON);
12484 /* for backwards compatibility, the following code emulates a fixed bug that
12485 occured when pushing elements (causing elements that just made their last
12486 pushing step to already (if possible) make their first falling step in the
12487 same game frame, which is bad); this code is also needed to use the famous
12488 "spring push bug" which is used in older levels and might be wanted to be
12489 used also in newer levels, but in this case the buggy pushing code is only
12490 affecting the "spring" element and no other elements */
12492 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12494 for (i = 0; i < MAX_PLAYERS; i++)
12496 struct PlayerInfo *player = &stored_player[i];
12497 int x = player->jx;
12498 int y = player->jy;
12500 if (player->active && player->is_pushing && player->is_moving &&
12502 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12503 Tile[x][y] == EL_SPRING))
12505 ContinueMoving(x, y);
12507 // continue moving after pushing (this is actually a bug)
12508 if (!IS_MOVING(x, y))
12509 Stop[x][y] = FALSE;
12514 SCAN_PLAYFIELD(x, y)
12516 Last[x][y] = Tile[x][y];
12518 ChangeCount[x][y] = 0;
12519 ChangeEvent[x][y] = -1;
12521 // this must be handled before main playfield loop
12522 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12525 if (MovDelay[x][y] <= 0)
12529 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12532 if (MovDelay[x][y] <= 0)
12534 int element = Store[x][y];
12535 int move_direction = MovDir[x][y];
12536 int player_index_bit = Store2[x][y];
12542 TEST_DrawLevelField(x, y);
12544 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12546 if (IS_ENVELOPE(element))
12547 local_player->show_envelope = element;
12552 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12554 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12556 Debug("game:playing:GameActions_RND", "This should never happen!");
12558 ChangePage[x][y] = -1;
12562 Stop[x][y] = FALSE;
12563 if (WasJustMoving[x][y] > 0)
12564 WasJustMoving[x][y]--;
12565 if (WasJustFalling[x][y] > 0)
12566 WasJustFalling[x][y]--;
12567 if (CheckCollision[x][y] > 0)
12568 CheckCollision[x][y]--;
12569 if (CheckImpact[x][y] > 0)
12570 CheckImpact[x][y]--;
12574 /* reset finished pushing action (not done in ContinueMoving() to allow
12575 continuous pushing animation for elements with zero push delay) */
12576 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12578 ResetGfxAnimation(x, y);
12579 TEST_DrawLevelField(x, y);
12583 if (IS_BLOCKED(x, y))
12587 Blocked2Moving(x, y, &oldx, &oldy);
12588 if (!IS_MOVING(oldx, oldy))
12590 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12591 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12592 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12593 Debug("game:playing:GameActions_RND", "This should never happen!");
12599 HandleMouseAction(&mouse_action, &mouse_action_last);
12601 SCAN_PLAYFIELD(x, y)
12603 element = Tile[x][y];
12604 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12605 last_gfx_frame = GfxFrame[x][y];
12607 if (element == EL_EMPTY)
12608 graphic = el2img(GfxElementEmpty[x][y]);
12610 ResetGfxFrame(x, y);
12612 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12613 DrawLevelGraphicAnimation(x, y, graphic);
12615 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12616 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12617 ResetRandomAnimationValue(x, y);
12619 SetRandomAnimationValue(x, y);
12621 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12623 if (IS_INACTIVE(element))
12625 if (IS_ANIMATED(graphic))
12626 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12631 // this may take place after moving, so 'element' may have changed
12632 if (IS_CHANGING(x, y) &&
12633 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12635 int page = element_info[element].event_page_nr[CE_DELAY];
12637 HandleElementChange(x, y, page);
12639 element = Tile[x][y];
12640 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12643 CheckNextToConditions(x, y);
12645 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12649 element = Tile[x][y];
12650 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12652 if (IS_ANIMATED(graphic) &&
12653 !IS_MOVING(x, y) &&
12655 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12657 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12658 TEST_DrawTwinkleOnField(x, y);
12660 else if (element == EL_ACID)
12663 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12665 else if ((element == EL_EXIT_OPEN ||
12666 element == EL_EM_EXIT_OPEN ||
12667 element == EL_SP_EXIT_OPEN ||
12668 element == EL_STEEL_EXIT_OPEN ||
12669 element == EL_EM_STEEL_EXIT_OPEN ||
12670 element == EL_SP_TERMINAL ||
12671 element == EL_SP_TERMINAL_ACTIVE ||
12672 element == EL_EXTRA_TIME ||
12673 element == EL_SHIELD_NORMAL ||
12674 element == EL_SHIELD_DEADLY) &&
12675 IS_ANIMATED(graphic))
12676 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12677 else if (IS_MOVING(x, y))
12678 ContinueMoving(x, y);
12679 else if (IS_ACTIVE_BOMB(element))
12680 CheckDynamite(x, y);
12681 else if (element == EL_AMOEBA_GROWING)
12682 AmoebaGrowing(x, y);
12683 else if (element == EL_AMOEBA_SHRINKING)
12684 AmoebaShrinking(x, y);
12686 #if !USE_NEW_AMOEBA_CODE
12687 else if (IS_AMOEBALIVE(element))
12688 AmoebaReproduce(x, y);
12691 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12693 else if (element == EL_EXIT_CLOSED)
12695 else if (element == EL_EM_EXIT_CLOSED)
12697 else if (element == EL_STEEL_EXIT_CLOSED)
12698 CheckExitSteel(x, y);
12699 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12700 CheckExitSteelEM(x, y);
12701 else if (element == EL_SP_EXIT_CLOSED)
12703 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12704 element == EL_EXPANDABLE_STEELWALL_GROWING)
12706 else if (element == EL_EXPANDABLE_WALL ||
12707 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12708 element == EL_EXPANDABLE_WALL_VERTICAL ||
12709 element == EL_EXPANDABLE_WALL_ANY ||
12710 element == EL_BD_EXPANDABLE_WALL ||
12711 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12712 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12713 element == EL_EXPANDABLE_STEELWALL_ANY)
12714 CheckWallGrowing(x, y);
12715 else if (element == EL_FLAMES)
12716 CheckForDragon(x, y);
12717 else if (element == EL_EXPLOSION)
12718 ; // drawing of correct explosion animation is handled separately
12719 else if (element == EL_ELEMENT_SNAPPING ||
12720 element == EL_DIAGONAL_SHRINKING ||
12721 element == EL_DIAGONAL_GROWING)
12723 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]);
12725 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12727 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12728 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12730 if (IS_BELT_ACTIVE(element))
12731 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12733 if (game.magic_wall_active)
12735 int jx = local_player->jx, jy = local_player->jy;
12737 // play the element sound at the position nearest to the player
12738 if ((element == EL_MAGIC_WALL_FULL ||
12739 element == EL_MAGIC_WALL_ACTIVE ||
12740 element == EL_MAGIC_WALL_EMPTYING ||
12741 element == EL_BD_MAGIC_WALL_FULL ||
12742 element == EL_BD_MAGIC_WALL_ACTIVE ||
12743 element == EL_BD_MAGIC_WALL_EMPTYING ||
12744 element == EL_DC_MAGIC_WALL_FULL ||
12745 element == EL_DC_MAGIC_WALL_ACTIVE ||
12746 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12747 ABS(x - jx) + ABS(y - jy) <
12748 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12756 #if USE_NEW_AMOEBA_CODE
12757 // new experimental amoeba growth stuff
12758 if (!(FrameCounter % 8))
12760 static unsigned int random = 1684108901;
12762 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12764 x = RND(lev_fieldx);
12765 y = RND(lev_fieldy);
12766 element = Tile[x][y];
12768 if (!IS_PLAYER(x, y) &&
12769 (element == EL_EMPTY ||
12770 CAN_GROW_INTO(element) ||
12771 element == EL_QUICKSAND_EMPTY ||
12772 element == EL_QUICKSAND_FAST_EMPTY ||
12773 element == EL_ACID_SPLASH_LEFT ||
12774 element == EL_ACID_SPLASH_RIGHT))
12776 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12777 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12778 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12779 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12780 Tile[x][y] = EL_AMOEBA_DROP;
12783 random = random * 129 + 1;
12788 game.explosions_delayed = FALSE;
12790 SCAN_PLAYFIELD(x, y)
12792 element = Tile[x][y];
12794 if (ExplodeField[x][y])
12795 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12796 else if (element == EL_EXPLOSION)
12797 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12799 ExplodeField[x][y] = EX_TYPE_NONE;
12802 game.explosions_delayed = TRUE;
12804 if (game.magic_wall_active)
12806 if (!(game.magic_wall_time_left % 4))
12808 int element = Tile[magic_wall_x][magic_wall_y];
12810 if (element == EL_BD_MAGIC_WALL_FULL ||
12811 element == EL_BD_MAGIC_WALL_ACTIVE ||
12812 element == EL_BD_MAGIC_WALL_EMPTYING)
12813 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12814 else if (element == EL_DC_MAGIC_WALL_FULL ||
12815 element == EL_DC_MAGIC_WALL_ACTIVE ||
12816 element == EL_DC_MAGIC_WALL_EMPTYING)
12817 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12819 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12822 if (game.magic_wall_time_left > 0)
12824 game.magic_wall_time_left--;
12826 if (!game.magic_wall_time_left)
12828 SCAN_PLAYFIELD(x, y)
12830 element = Tile[x][y];
12832 if (element == EL_MAGIC_WALL_ACTIVE ||
12833 element == EL_MAGIC_WALL_FULL)
12835 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12836 TEST_DrawLevelField(x, y);
12838 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12839 element == EL_BD_MAGIC_WALL_FULL)
12841 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12842 TEST_DrawLevelField(x, y);
12844 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12845 element == EL_DC_MAGIC_WALL_FULL)
12847 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12848 TEST_DrawLevelField(x, y);
12852 game.magic_wall_active = FALSE;
12857 if (game.light_time_left > 0)
12859 game.light_time_left--;
12861 if (game.light_time_left == 0)
12862 RedrawAllLightSwitchesAndInvisibleElements();
12865 if (game.timegate_time_left > 0)
12867 game.timegate_time_left--;
12869 if (game.timegate_time_left == 0)
12870 CloseAllOpenTimegates();
12873 if (game.lenses_time_left > 0)
12875 game.lenses_time_left--;
12877 if (game.lenses_time_left == 0)
12878 RedrawAllInvisibleElementsForLenses();
12881 if (game.magnify_time_left > 0)
12883 game.magnify_time_left--;
12885 if (game.magnify_time_left == 0)
12886 RedrawAllInvisibleElementsForMagnifier();
12889 for (i = 0; i < MAX_PLAYERS; i++)
12891 struct PlayerInfo *player = &stored_player[i];
12893 if (SHIELD_ON(player))
12895 if (player->shield_deadly_time_left)
12896 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12897 else if (player->shield_normal_time_left)
12898 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12902 #if USE_DELAYED_GFX_REDRAW
12903 SCAN_PLAYFIELD(x, y)
12905 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12907 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12908 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12910 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12911 DrawLevelField(x, y);
12913 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12914 DrawLevelFieldCrumbled(x, y);
12916 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12917 DrawLevelFieldCrumbledNeighbours(x, y);
12919 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12920 DrawTwinkleOnField(x, y);
12923 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12928 PlayAllPlayersSound();
12930 for (i = 0; i < MAX_PLAYERS; i++)
12932 struct PlayerInfo *player = &stored_player[i];
12934 if (player->show_envelope != 0 && (!player->active ||
12935 player->MovPos == 0))
12937 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12939 player->show_envelope = 0;
12943 // use random number generator in every frame to make it less predictable
12944 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12947 mouse_action_last = mouse_action;
12950 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12952 int min_x = x, min_y = y, max_x = x, max_y = y;
12953 int scr_fieldx = getScreenFieldSizeX();
12954 int scr_fieldy = getScreenFieldSizeY();
12957 for (i = 0; i < MAX_PLAYERS; i++)
12959 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12961 if (!stored_player[i].active || &stored_player[i] == player)
12964 min_x = MIN(min_x, jx);
12965 min_y = MIN(min_y, jy);
12966 max_x = MAX(max_x, jx);
12967 max_y = MAX(max_y, jy);
12970 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12973 static boolean AllPlayersInVisibleScreen(void)
12977 for (i = 0; i < MAX_PLAYERS; i++)
12979 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12981 if (!stored_player[i].active)
12984 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12991 void ScrollLevel(int dx, int dy)
12993 int scroll_offset = 2 * TILEX_VAR;
12996 BlitBitmap(drawto_field, drawto_field,
12997 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12998 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12999 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
13000 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
13001 FX + TILEX_VAR * (dx == 1) - scroll_offset,
13002 FY + TILEY_VAR * (dy == 1) - scroll_offset);
13006 x = (dx == 1 ? BX1 : BX2);
13007 for (y = BY1; y <= BY2; y++)
13008 DrawScreenField(x, y);
13013 y = (dy == 1 ? BY1 : BY2);
13014 for (x = BX1; x <= BX2; x++)
13015 DrawScreenField(x, y);
13018 redraw_mask |= REDRAW_FIELD;
13021 static boolean canFallDown(struct PlayerInfo *player)
13023 int jx = player->jx, jy = player->jy;
13025 return (IN_LEV_FIELD(jx, jy + 1) &&
13026 (IS_FREE(jx, jy + 1) ||
13027 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
13028 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
13029 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
13032 static boolean canPassField(int x, int y, int move_dir)
13034 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13035 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13036 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13037 int nextx = x + dx;
13038 int nexty = y + dy;
13039 int element = Tile[x][y];
13041 return (IS_PASSABLE_FROM(element, opposite_dir) &&
13042 !CAN_MOVE(element) &&
13043 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
13044 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
13045 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
13048 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
13050 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13051 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13052 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13056 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
13057 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
13058 (IS_DIGGABLE(Tile[newx][newy]) ||
13059 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
13060 canPassField(newx, newy, move_dir)));
13063 static void CheckGravityMovement(struct PlayerInfo *player)
13065 if (player->gravity && !player->programmed_action)
13067 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
13068 int move_dir_vertical = player->effective_action & MV_VERTICAL;
13069 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
13070 int jx = player->jx, jy = player->jy;
13071 boolean player_is_moving_to_valid_field =
13072 (!player_is_snapping &&
13073 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
13074 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
13075 boolean player_can_fall_down = canFallDown(player);
13077 if (player_can_fall_down &&
13078 !player_is_moving_to_valid_field)
13079 player->programmed_action = MV_DOWN;
13083 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
13085 return CheckGravityMovement(player);
13087 if (player->gravity && !player->programmed_action)
13089 int jx = player->jx, jy = player->jy;
13090 boolean field_under_player_is_free =
13091 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
13092 boolean player_is_standing_on_valid_field =
13093 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
13094 (IS_WALKABLE(Tile[jx][jy]) &&
13095 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
13097 if (field_under_player_is_free && !player_is_standing_on_valid_field)
13098 player->programmed_action = MV_DOWN;
13103 MovePlayerOneStep()
13104 -----------------------------------------------------------------------------
13105 dx, dy: direction (non-diagonal) to try to move the player to
13106 real_dx, real_dy: direction as read from input device (can be diagonal)
13109 boolean MovePlayerOneStep(struct PlayerInfo *player,
13110 int dx, int dy, int real_dx, int real_dy)
13112 int jx = player->jx, jy = player->jy;
13113 int new_jx = jx + dx, new_jy = jy + dy;
13115 boolean player_can_move = !player->cannot_move;
13117 if (!player->active || (!dx && !dy))
13118 return MP_NO_ACTION;
13120 player->MovDir = (dx < 0 ? MV_LEFT :
13121 dx > 0 ? MV_RIGHT :
13123 dy > 0 ? MV_DOWN : MV_NONE);
13125 if (!IN_LEV_FIELD(new_jx, new_jy))
13126 return MP_NO_ACTION;
13128 if (!player_can_move)
13130 if (player->MovPos == 0)
13132 player->is_moving = FALSE;
13133 player->is_digging = FALSE;
13134 player->is_collecting = FALSE;
13135 player->is_snapping = FALSE;
13136 player->is_pushing = FALSE;
13140 if (!network.enabled && game.centered_player_nr == -1 &&
13141 !AllPlayersInSight(player, new_jx, new_jy))
13142 return MP_NO_ACTION;
13144 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG);
13145 if (can_move != MP_MOVING)
13148 // check if DigField() has caused relocation of the player
13149 if (player->jx != jx || player->jy != jy)
13150 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
13152 StorePlayer[jx][jy] = 0;
13153 player->last_jx = jx;
13154 player->last_jy = jy;
13155 player->jx = new_jx;
13156 player->jy = new_jy;
13157 StorePlayer[new_jx][new_jy] = player->element_nr;
13159 if (player->move_delay_value_next != -1)
13161 player->move_delay_value = player->move_delay_value_next;
13162 player->move_delay_value_next = -1;
13166 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
13168 player->step_counter++;
13170 PlayerVisit[jx][jy] = FrameCounter;
13172 player->is_moving = TRUE;
13175 // should better be called in MovePlayer(), but this breaks some tapes
13176 ScrollPlayer(player, SCROLL_INIT);
13182 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
13184 int jx = player->jx, jy = player->jy;
13185 int old_jx = jx, old_jy = jy;
13186 int moved = MP_NO_ACTION;
13188 if (!player->active)
13193 if (player->MovPos == 0)
13195 player->is_moving = FALSE;
13196 player->is_digging = FALSE;
13197 player->is_collecting = FALSE;
13198 player->is_snapping = FALSE;
13199 player->is_pushing = FALSE;
13205 if (player->move_delay > 0)
13208 player->move_delay = -1; // set to "uninitialized" value
13210 // store if player is automatically moved to next field
13211 player->is_auto_moving = (player->programmed_action != MV_NONE);
13213 // remove the last programmed player action
13214 player->programmed_action = 0;
13216 if (player->MovPos)
13218 // should only happen if pre-1.2 tape recordings are played
13219 // this is only for backward compatibility
13221 int original_move_delay_value = player->move_delay_value;
13224 Debug("game:playing:MovePlayer",
13225 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13229 // scroll remaining steps with finest movement resolution
13230 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13232 while (player->MovPos)
13234 ScrollPlayer(player, SCROLL_GO_ON);
13235 ScrollScreen(NULL, SCROLL_GO_ON);
13237 AdvanceFrameAndPlayerCounters(player->index_nr);
13240 BackToFront_WithFrameDelay(0);
13243 player->move_delay_value = original_move_delay_value;
13246 player->is_active = FALSE;
13248 if (player->last_move_dir & MV_HORIZONTAL)
13250 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13251 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13255 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13256 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13259 if (!moved && !player->is_active)
13261 player->is_moving = FALSE;
13262 player->is_digging = FALSE;
13263 player->is_collecting = FALSE;
13264 player->is_snapping = FALSE;
13265 player->is_pushing = FALSE;
13271 if (moved & MP_MOVING && !ScreenMovPos &&
13272 (player->index_nr == game.centered_player_nr ||
13273 game.centered_player_nr == -1))
13275 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13277 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13279 // actual player has left the screen -- scroll in that direction
13280 if (jx != old_jx) // player has moved horizontally
13281 scroll_x += (jx - old_jx);
13282 else // player has moved vertically
13283 scroll_y += (jy - old_jy);
13287 int offset_raw = game.scroll_delay_value;
13289 if (jx != old_jx) // player has moved horizontally
13291 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13292 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13293 int new_scroll_x = jx - MIDPOSX + offset_x;
13295 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13296 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13297 scroll_x = new_scroll_x;
13299 // don't scroll over playfield boundaries
13300 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13302 // don't scroll more than one field at a time
13303 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13305 // don't scroll against the player's moving direction
13306 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13307 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13308 scroll_x = old_scroll_x;
13310 else // player has moved vertically
13312 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13313 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13314 int new_scroll_y = jy - MIDPOSY + offset_y;
13316 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13317 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13318 scroll_y = new_scroll_y;
13320 // don't scroll over playfield boundaries
13321 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13323 // don't scroll more than one field at a time
13324 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13326 // don't scroll against the player's moving direction
13327 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13328 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13329 scroll_y = old_scroll_y;
13333 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13335 if (!network.enabled && game.centered_player_nr == -1 &&
13336 !AllPlayersInVisibleScreen())
13338 scroll_x = old_scroll_x;
13339 scroll_y = old_scroll_y;
13343 ScrollScreen(player, SCROLL_INIT);
13344 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13349 player->StepFrame = 0;
13351 if (moved & MP_MOVING)
13353 if (old_jx != jx && old_jy == jy)
13354 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13355 else if (old_jx == jx && old_jy != jy)
13356 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13358 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13360 player->last_move_dir = player->MovDir;
13361 player->is_moving = TRUE;
13362 player->is_snapping = FALSE;
13363 player->is_switching = FALSE;
13364 player->is_dropping = FALSE;
13365 player->is_dropping_pressed = FALSE;
13366 player->drop_pressed_delay = 0;
13369 // should better be called here than above, but this breaks some tapes
13370 ScrollPlayer(player, SCROLL_INIT);
13375 CheckGravityMovementWhenNotMoving(player);
13377 player->is_moving = FALSE;
13379 /* at this point, the player is allowed to move, but cannot move right now
13380 (e.g. because of something blocking the way) -- ensure that the player
13381 is also allowed to move in the next frame (in old versions before 3.1.1,
13382 the player was forced to wait again for eight frames before next try) */
13384 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13385 player->move_delay = 0; // allow direct movement in the next frame
13388 if (player->move_delay == -1) // not yet initialized by DigField()
13389 player->move_delay = player->move_delay_value;
13391 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13393 TestIfPlayerTouchesBadThing(jx, jy);
13394 TestIfPlayerTouchesCustomElement(jx, jy);
13397 if (!player->active)
13398 RemovePlayer(player);
13403 void ScrollPlayer(struct PlayerInfo *player, int mode)
13405 int jx = player->jx, jy = player->jy;
13406 int last_jx = player->last_jx, last_jy = player->last_jy;
13407 int move_stepsize = TILEX / player->move_delay_value;
13409 if (!player->active)
13412 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13415 if (mode == SCROLL_INIT)
13417 player->actual_frame_counter.count = FrameCounter;
13418 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13420 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13421 Tile[last_jx][last_jy] == EL_EMPTY)
13423 int last_field_block_delay = 0; // start with no blocking at all
13424 int block_delay_adjustment = player->block_delay_adjustment;
13426 // if player blocks last field, add delay for exactly one move
13427 if (player->block_last_field)
13429 last_field_block_delay += player->move_delay_value;
13431 // when blocking enabled, prevent moving up despite gravity
13432 if (player->gravity && player->MovDir == MV_UP)
13433 block_delay_adjustment = -1;
13436 // add block delay adjustment (also possible when not blocking)
13437 last_field_block_delay += block_delay_adjustment;
13439 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13440 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13443 if (player->MovPos != 0) // player has not yet reached destination
13446 else if (!FrameReached(&player->actual_frame_counter))
13449 if (player->MovPos != 0)
13451 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13452 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13454 // before DrawPlayer() to draw correct player graphic for this case
13455 if (player->MovPos == 0)
13456 CheckGravityMovement(player);
13459 if (player->MovPos == 0) // player reached destination field
13461 if (player->move_delay_reset_counter > 0)
13463 player->move_delay_reset_counter--;
13465 if (player->move_delay_reset_counter == 0)
13467 // continue with normal speed after quickly moving through gate
13468 HALVE_PLAYER_SPEED(player);
13470 // be able to make the next move without delay
13471 player->move_delay = 0;
13475 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13476 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13477 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13478 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13479 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13480 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13481 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13482 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13484 ExitPlayer(player);
13486 if (game.players_still_needed == 0 &&
13487 (game.friends_still_needed == 0 ||
13488 IS_SP_ELEMENT(Tile[jx][jy])))
13492 player->last_jx = jx;
13493 player->last_jy = jy;
13495 // this breaks one level: "machine", level 000
13497 int move_direction = player->MovDir;
13498 int enter_side = MV_DIR_OPPOSITE(move_direction);
13499 int leave_side = move_direction;
13500 int old_jx = last_jx;
13501 int old_jy = last_jy;
13502 int old_element = Tile[old_jx][old_jy];
13503 int new_element = Tile[jx][jy];
13505 if (IS_CUSTOM_ELEMENT(old_element))
13506 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13508 player->index_bit, leave_side);
13510 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13511 CE_PLAYER_LEAVES_X,
13512 player->index_bit, leave_side);
13514 // needed because pushed element has not yet reached its destination,
13515 // so it would trigger a change event at its previous field location
13516 if (!player->is_pushing)
13518 if (IS_CUSTOM_ELEMENT(new_element))
13519 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13520 player->index_bit, enter_side);
13522 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13523 CE_PLAYER_ENTERS_X,
13524 player->index_bit, enter_side);
13527 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13528 CE_MOVE_OF_X, move_direction);
13531 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13533 TestIfPlayerTouchesBadThing(jx, jy);
13534 TestIfPlayerTouchesCustomElement(jx, jy);
13536 // needed because pushed element has not yet reached its destination,
13537 // so it would trigger a change event at its previous field location
13538 if (!player->is_pushing)
13539 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13541 if (level.finish_dig_collect &&
13542 (player->is_digging || player->is_collecting))
13544 int last_element = player->last_removed_element;
13545 int move_direction = player->MovDir;
13546 int enter_side = MV_DIR_OPPOSITE(move_direction);
13547 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13548 CE_PLAYER_COLLECTS_X);
13550 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13551 player->index_bit, enter_side);
13553 player->last_removed_element = EL_UNDEFINED;
13556 if (!player->active)
13557 RemovePlayer(player);
13560 if (level.use_step_counter)
13561 CheckLevelTime_StepCounter();
13563 if (tape.single_step && tape.recording && !tape.pausing &&
13564 !player->programmed_action)
13565 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13567 if (!player->programmed_action)
13568 CheckSaveEngineSnapshot(player);
13572 void ScrollScreen(struct PlayerInfo *player, int mode)
13574 static DelayCounter screen_frame_counter = { 0 };
13576 if (mode == SCROLL_INIT)
13578 // set scrolling step size according to actual player's moving speed
13579 ScrollStepSize = TILEX / player->move_delay_value;
13581 screen_frame_counter.count = FrameCounter;
13582 screen_frame_counter.value = 1;
13584 ScreenMovDir = player->MovDir;
13585 ScreenMovPos = player->MovPos;
13586 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13589 else if (!FrameReached(&screen_frame_counter))
13594 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13595 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13596 redraw_mask |= REDRAW_FIELD;
13599 ScreenMovDir = MV_NONE;
13602 void CheckNextToConditions(int x, int y)
13604 int element = Tile[x][y];
13606 if (IS_PLAYER(x, y))
13607 TestIfPlayerNextToCustomElement(x, y);
13609 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13610 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13611 TestIfElementNextToCustomElement(x, y);
13614 void TestIfPlayerNextToCustomElement(int x, int y)
13616 struct XY *xy = xy_topdown;
13617 static int trigger_sides[4][2] =
13619 // center side border side
13620 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13621 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13622 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13623 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13627 if (!IS_PLAYER(x, y))
13630 struct PlayerInfo *player = PLAYERINFO(x, y);
13632 if (player->is_moving)
13635 for (i = 0; i < NUM_DIRECTIONS; i++)
13637 int xx = x + xy[i].x;
13638 int yy = y + xy[i].y;
13639 int border_side = trigger_sides[i][1];
13640 int border_element;
13642 if (!IN_LEV_FIELD(xx, yy))
13645 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13646 continue; // center and border element not connected
13648 border_element = Tile[xx][yy];
13650 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13651 player->index_bit, border_side);
13652 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13653 CE_PLAYER_NEXT_TO_X,
13654 player->index_bit, border_side);
13656 /* use player element that is initially defined in the level playfield,
13657 not the player element that corresponds to the runtime player number
13658 (example: a level that contains EL_PLAYER_3 as the only player would
13659 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13661 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13662 CE_NEXT_TO_X, border_side);
13666 void TestIfPlayerTouchesCustomElement(int x, int y)
13668 struct XY *xy = xy_topdown;
13669 static int trigger_sides[4][2] =
13671 // center side border side
13672 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13673 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13674 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13675 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13677 static int touch_dir[4] =
13679 MV_LEFT | MV_RIGHT,
13684 int center_element = Tile[x][y]; // should always be non-moving!
13687 for (i = 0; i < NUM_DIRECTIONS; i++)
13689 int xx = x + xy[i].x;
13690 int yy = y + xy[i].y;
13691 int center_side = trigger_sides[i][0];
13692 int border_side = trigger_sides[i][1];
13693 int border_element;
13695 if (!IN_LEV_FIELD(xx, yy))
13698 if (IS_PLAYER(x, y)) // player found at center element
13700 struct PlayerInfo *player = PLAYERINFO(x, y);
13702 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13703 border_element = Tile[xx][yy]; // may be moving!
13704 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13705 border_element = Tile[xx][yy];
13706 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13707 border_element = MovingOrBlocked2Element(xx, yy);
13709 continue; // center and border element do not touch
13711 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13712 player->index_bit, border_side);
13713 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13714 CE_PLAYER_TOUCHES_X,
13715 player->index_bit, border_side);
13718 /* use player element that is initially defined in the level playfield,
13719 not the player element that corresponds to the runtime player number
13720 (example: a level that contains EL_PLAYER_3 as the only player would
13721 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13722 int player_element = PLAYERINFO(x, y)->initial_element;
13724 // as element "X" is the player here, check opposite (center) side
13725 CheckElementChangeBySide(xx, yy, border_element, player_element,
13726 CE_TOUCHING_X, center_side);
13729 else if (IS_PLAYER(xx, yy)) // player found at border element
13731 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13733 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13735 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13736 continue; // center and border element do not touch
13739 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13740 player->index_bit, center_side);
13741 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13742 CE_PLAYER_TOUCHES_X,
13743 player->index_bit, center_side);
13746 /* use player element that is initially defined in the level playfield,
13747 not the player element that corresponds to the runtime player number
13748 (example: a level that contains EL_PLAYER_3 as the only player would
13749 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13750 int player_element = PLAYERINFO(xx, yy)->initial_element;
13752 // as element "X" is the player here, check opposite (border) side
13753 CheckElementChangeBySide(x, y, center_element, player_element,
13754 CE_TOUCHING_X, border_side);
13762 void TestIfElementNextToCustomElement(int x, int y)
13764 struct XY *xy = xy_topdown;
13765 static int trigger_sides[4][2] =
13767 // center side border side
13768 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13769 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13770 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13771 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13773 int center_element = Tile[x][y]; // should always be non-moving!
13776 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13779 for (i = 0; i < NUM_DIRECTIONS; i++)
13781 int xx = x + xy[i].x;
13782 int yy = y + xy[i].y;
13783 int border_side = trigger_sides[i][1];
13784 int border_element;
13786 if (!IN_LEV_FIELD(xx, yy))
13789 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13790 continue; // center and border element not connected
13792 border_element = Tile[xx][yy];
13794 // check for change of center element (but change it only once)
13795 if (CheckElementChangeBySide(x, y, center_element, border_element,
13796 CE_NEXT_TO_X, border_side))
13801 void TestIfElementTouchesCustomElement(int x, int y)
13803 struct XY *xy = xy_topdown;
13804 static int trigger_sides[4][2] =
13806 // center side border side
13807 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13808 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13809 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13810 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13812 static int touch_dir[4] =
13814 MV_LEFT | MV_RIGHT,
13819 boolean change_center_element = FALSE;
13820 int center_element = Tile[x][y]; // should always be non-moving!
13821 int border_element_old[NUM_DIRECTIONS];
13824 for (i = 0; i < NUM_DIRECTIONS; i++)
13826 int xx = x + xy[i].x;
13827 int yy = y + xy[i].y;
13828 int border_element;
13830 border_element_old[i] = -1;
13832 if (!IN_LEV_FIELD(xx, yy))
13835 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13836 border_element = Tile[xx][yy]; // may be moving!
13837 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13838 border_element = Tile[xx][yy];
13839 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13840 border_element = MovingOrBlocked2Element(xx, yy);
13842 continue; // center and border element do not touch
13844 border_element_old[i] = border_element;
13847 for (i = 0; i < NUM_DIRECTIONS; i++)
13849 int xx = x + xy[i].x;
13850 int yy = y + xy[i].y;
13851 int center_side = trigger_sides[i][0];
13852 int border_element = border_element_old[i];
13854 if (border_element == -1)
13857 // check for change of border element
13858 CheckElementChangeBySide(xx, yy, border_element, center_element,
13859 CE_TOUCHING_X, center_side);
13861 // (center element cannot be player, so we don't have to check this here)
13864 for (i = 0; i < NUM_DIRECTIONS; i++)
13866 int xx = x + xy[i].x;
13867 int yy = y + xy[i].y;
13868 int border_side = trigger_sides[i][1];
13869 int border_element = border_element_old[i];
13871 if (border_element == -1)
13874 // check for change of center element (but change it only once)
13875 if (!change_center_element)
13876 change_center_element =
13877 CheckElementChangeBySide(x, y, center_element, border_element,
13878 CE_TOUCHING_X, border_side);
13880 if (IS_PLAYER(xx, yy))
13882 /* use player element that is initially defined in the level playfield,
13883 not the player element that corresponds to the runtime player number
13884 (example: a level that contains EL_PLAYER_3 as the only player would
13885 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13886 int player_element = PLAYERINFO(xx, yy)->initial_element;
13888 // as element "X" is the player here, check opposite (border) side
13889 CheckElementChangeBySide(x, y, center_element, player_element,
13890 CE_TOUCHING_X, border_side);
13895 void TestIfElementHitsCustomElement(int x, int y, int direction)
13897 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13898 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13899 int hitx = x + dx, hity = y + dy;
13900 int hitting_element = Tile[x][y];
13901 int touched_element;
13903 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13906 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13907 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13909 if (IN_LEV_FIELD(hitx, hity))
13911 int opposite_direction = MV_DIR_OPPOSITE(direction);
13912 int hitting_side = direction;
13913 int touched_side = opposite_direction;
13914 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13915 MovDir[hitx][hity] != direction ||
13916 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13922 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13923 CE_HITTING_X, touched_side);
13925 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13926 CE_HIT_BY_X, hitting_side);
13928 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13929 CE_HIT_BY_SOMETHING, opposite_direction);
13931 if (IS_PLAYER(hitx, hity))
13933 /* use player element that is initially defined in the level playfield,
13934 not the player element that corresponds to the runtime player number
13935 (example: a level that contains EL_PLAYER_3 as the only player would
13936 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13937 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13939 CheckElementChangeBySide(x, y, hitting_element, player_element,
13940 CE_HITTING_X, touched_side);
13945 // "hitting something" is also true when hitting the playfield border
13946 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13947 CE_HITTING_SOMETHING, direction);
13950 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13952 int i, kill_x = -1, kill_y = -1;
13954 int bad_element = -1;
13955 struct XY *test_xy = xy_topdown;
13956 static int test_dir[4] =
13964 for (i = 0; i < NUM_DIRECTIONS; i++)
13966 int test_x, test_y, test_move_dir, test_element;
13968 test_x = good_x + test_xy[i].x;
13969 test_y = good_y + test_xy[i].y;
13971 if (!IN_LEV_FIELD(test_x, test_y))
13975 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13977 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13979 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13980 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13982 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13983 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13987 bad_element = test_element;
13993 if (kill_x != -1 || kill_y != -1)
13995 if (IS_PLAYER(good_x, good_y))
13997 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13999 if (player->shield_deadly_time_left > 0 &&
14000 !IS_INDESTRUCTIBLE(bad_element))
14001 Bang(kill_x, kill_y);
14002 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
14003 KillPlayer(player);
14006 Bang(good_x, good_y);
14010 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
14012 int i, kill_x = -1, kill_y = -1;
14013 int bad_element = Tile[bad_x][bad_y];
14014 struct XY *test_xy = xy_topdown;
14015 static int touch_dir[4] =
14017 MV_LEFT | MV_RIGHT,
14022 static int test_dir[4] =
14030 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
14033 for (i = 0; i < NUM_DIRECTIONS; i++)
14035 int test_x, test_y, test_move_dir, test_element;
14037 test_x = bad_x + test_xy[i].x;
14038 test_y = bad_y + test_xy[i].y;
14040 if (!IN_LEV_FIELD(test_x, test_y))
14044 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14046 test_element = Tile[test_x][test_y];
14048 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
14049 2nd case: DONT_TOUCH style bad thing does not move away from good thing
14051 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
14052 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
14054 // good thing is player or penguin that does not move away
14055 if (IS_PLAYER(test_x, test_y))
14057 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14059 if (bad_element == EL_ROBOT && player->is_moving)
14060 continue; // robot does not kill player if he is moving
14062 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
14064 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
14065 continue; // center and border element do not touch
14073 else if (test_element == EL_PENGUIN)
14083 if (kill_x != -1 || kill_y != -1)
14085 if (IS_PLAYER(kill_x, kill_y))
14087 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14089 if (player->shield_deadly_time_left > 0 &&
14090 !IS_INDESTRUCTIBLE(bad_element))
14091 Bang(bad_x, bad_y);
14092 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14093 KillPlayer(player);
14096 Bang(kill_x, kill_y);
14100 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
14102 int bad_element = Tile[bad_x][bad_y];
14103 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
14104 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
14105 int test_x = bad_x + dx, test_y = bad_y + dy;
14106 int test_move_dir, test_element;
14107 int kill_x = -1, kill_y = -1;
14109 if (!IN_LEV_FIELD(test_x, test_y))
14113 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14115 test_element = Tile[test_x][test_y];
14117 if (test_move_dir != bad_move_dir)
14119 // good thing can be player or penguin that does not move away
14120 if (IS_PLAYER(test_x, test_y))
14122 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14124 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
14125 player as being hit when he is moving towards the bad thing, because
14126 the "get hit by" condition would be lost after the player stops) */
14127 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
14128 return; // player moves away from bad thing
14133 else if (test_element == EL_PENGUIN)
14140 if (kill_x != -1 || kill_y != -1)
14142 if (IS_PLAYER(kill_x, kill_y))
14144 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14146 if (player->shield_deadly_time_left > 0 &&
14147 !IS_INDESTRUCTIBLE(bad_element))
14148 Bang(bad_x, bad_y);
14149 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14150 KillPlayer(player);
14153 Bang(kill_x, kill_y);
14157 void TestIfPlayerTouchesBadThing(int x, int y)
14159 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14162 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
14164 TestIfGoodThingHitsBadThing(x, y, move_dir);
14167 void TestIfBadThingTouchesPlayer(int x, int y)
14169 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14172 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
14174 TestIfBadThingHitsGoodThing(x, y, move_dir);
14177 void TestIfFriendTouchesBadThing(int x, int y)
14179 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14182 void TestIfBadThingTouchesFriend(int x, int y)
14184 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14187 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
14189 int i, kill_x = bad_x, kill_y = bad_y;
14190 struct XY *xy = xy_topdown;
14192 for (i = 0; i < NUM_DIRECTIONS; i++)
14196 x = bad_x + xy[i].x;
14197 y = bad_y + xy[i].y;
14198 if (!IN_LEV_FIELD(x, y))
14201 element = Tile[x][y];
14202 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14203 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14211 if (kill_x != bad_x || kill_y != bad_y)
14212 Bang(bad_x, bad_y);
14215 void KillPlayer(struct PlayerInfo *player)
14217 int jx = player->jx, jy = player->jy;
14219 if (!player->active)
14223 Debug("game:playing:KillPlayer",
14224 "0: killed == %d, active == %d, reanimated == %d",
14225 player->killed, player->active, player->reanimated);
14228 /* the following code was introduced to prevent an infinite loop when calling
14230 -> CheckTriggeredElementChangeExt()
14231 -> ExecuteCustomElementAction()
14233 -> (infinitely repeating the above sequence of function calls)
14234 which occurs when killing the player while having a CE with the setting
14235 "kill player X when explosion of <player X>"; the solution using a new
14236 field "player->killed" was chosen for backwards compatibility, although
14237 clever use of the fields "player->active" etc. would probably also work */
14239 if (player->killed)
14243 player->killed = TRUE;
14245 // remove accessible field at the player's position
14246 RemoveField(jx, jy);
14248 // deactivate shield (else Bang()/Explode() would not work right)
14249 player->shield_normal_time_left = 0;
14250 player->shield_deadly_time_left = 0;
14253 Debug("game:playing:KillPlayer",
14254 "1: killed == %d, active == %d, reanimated == %d",
14255 player->killed, player->active, player->reanimated);
14261 Debug("game:playing:KillPlayer",
14262 "2: killed == %d, active == %d, reanimated == %d",
14263 player->killed, player->active, player->reanimated);
14266 if (player->reanimated) // killed player may have been reanimated
14267 player->killed = player->reanimated = FALSE;
14269 BuryPlayer(player);
14272 static void KillPlayerUnlessEnemyProtected(int x, int y)
14274 if (!PLAYER_ENEMY_PROTECTED(x, y))
14275 KillPlayer(PLAYERINFO(x, y));
14278 static void KillPlayerUnlessExplosionProtected(int x, int y)
14280 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14281 KillPlayer(PLAYERINFO(x, y));
14284 void BuryPlayer(struct PlayerInfo *player)
14286 int jx = player->jx, jy = player->jy;
14288 if (!player->active)
14291 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14293 RemovePlayer(player);
14295 player->buried = TRUE;
14297 if (game.all_players_gone)
14298 game.GameOver = TRUE;
14301 void RemovePlayer(struct PlayerInfo *player)
14303 int jx = player->jx, jy = player->jy;
14304 int i, found = FALSE;
14306 player->present = FALSE;
14307 player->active = FALSE;
14309 // required for some CE actions (even if the player is not active anymore)
14310 player->MovPos = 0;
14312 if (!ExplodeField[jx][jy])
14313 StorePlayer[jx][jy] = 0;
14315 if (player->is_moving)
14316 TEST_DrawLevelField(player->last_jx, player->last_jy);
14318 for (i = 0; i < MAX_PLAYERS; i++)
14319 if (stored_player[i].active)
14324 game.all_players_gone = TRUE;
14325 game.GameOver = TRUE;
14328 game.exit_x = game.robot_wheel_x = jx;
14329 game.exit_y = game.robot_wheel_y = jy;
14332 void ExitPlayer(struct PlayerInfo *player)
14334 DrawPlayer(player); // needed here only to cleanup last field
14335 RemovePlayer(player);
14337 if (game.players_still_needed > 0)
14338 game.players_still_needed--;
14341 static void SetFieldForSnapping(int x, int y, int element, int direction,
14342 int player_index_bit)
14344 struct ElementInfo *ei = &element_info[element];
14345 int direction_bit = MV_DIR_TO_BIT(direction);
14346 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14347 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14348 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14350 Tile[x][y] = EL_ELEMENT_SNAPPING;
14351 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14352 MovDir[x][y] = direction;
14353 Store[x][y] = element;
14354 Store2[x][y] = player_index_bit;
14356 ResetGfxAnimation(x, y);
14358 GfxElement[x][y] = element;
14359 GfxAction[x][y] = action;
14360 GfxDir[x][y] = direction;
14361 GfxFrame[x][y] = -1;
14364 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14365 int player_index_bit)
14367 TestIfElementTouchesCustomElement(x, y); // for empty space
14369 if (level.finish_dig_collect)
14371 int dig_side = MV_DIR_OPPOSITE(direction);
14372 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14373 CE_PLAYER_COLLECTS_X);
14375 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14376 player_index_bit, dig_side);
14377 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14378 player_index_bit, dig_side);
14383 =============================================================================
14384 checkDiagonalPushing()
14385 -----------------------------------------------------------------------------
14386 check if diagonal input device direction results in pushing of object
14387 (by checking if the alternative direction is walkable, diggable, ...)
14388 =============================================================================
14391 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14392 int x, int y, int real_dx, int real_dy)
14394 int jx, jy, dx, dy, xx, yy;
14396 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14399 // diagonal direction: check alternative direction
14404 xx = jx + (dx == 0 ? real_dx : 0);
14405 yy = jy + (dy == 0 ? real_dy : 0);
14407 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14411 =============================================================================
14413 -----------------------------------------------------------------------------
14414 x, y: field next to player (non-diagonal) to try to dig to
14415 real_dx, real_dy: direction as read from input device (can be diagonal)
14416 =============================================================================
14419 static int DigField(struct PlayerInfo *player,
14420 int oldx, int oldy, int x, int y,
14421 int real_dx, int real_dy, int mode)
14423 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14424 boolean player_was_pushing = player->is_pushing;
14425 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14426 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14427 int jx = oldx, jy = oldy;
14428 int dx = x - jx, dy = y - jy;
14429 int nextx = x + dx, nexty = y + dy;
14430 int move_direction = (dx == -1 ? MV_LEFT :
14431 dx == +1 ? MV_RIGHT :
14433 dy == +1 ? MV_DOWN : MV_NONE);
14434 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14435 int dig_side = MV_DIR_OPPOSITE(move_direction);
14436 int old_element = Tile[jx][jy];
14437 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14440 if (is_player) // function can also be called by EL_PENGUIN
14442 if (player->MovPos == 0)
14444 player->is_digging = FALSE;
14445 player->is_collecting = FALSE;
14448 if (player->MovPos == 0) // last pushing move finished
14449 player->is_pushing = FALSE;
14451 if (mode == DF_NO_PUSH) // player just stopped pushing
14453 player->is_switching = FALSE;
14454 player->push_delay = -1;
14456 return MP_NO_ACTION;
14459 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14460 old_element = Back[jx][jy];
14462 // in case of element dropped at player position, check background
14463 else if (Back[jx][jy] != EL_EMPTY &&
14464 game.engine_version >= VERSION_IDENT(2,2,0,0))
14465 old_element = Back[jx][jy];
14467 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14468 return MP_NO_ACTION; // field has no opening in this direction
14470 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction))
14471 return MP_NO_ACTION; // field has no opening in this direction
14473 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14477 Tile[jx][jy] = player->artwork_element;
14478 InitMovingField(jx, jy, MV_DOWN);
14479 Store[jx][jy] = EL_ACID;
14480 ContinueMoving(jx, jy);
14481 BuryPlayer(player);
14483 return MP_DONT_RUN_INTO;
14486 if (player_can_move && DONT_RUN_INTO(element))
14488 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14490 return MP_DONT_RUN_INTO;
14493 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14494 return MP_NO_ACTION;
14496 collect_count = element_info[element].collect_count_initial;
14498 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14499 return MP_NO_ACTION;
14501 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14502 player_can_move = player_can_move_or_snap;
14504 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14505 game.engine_version >= VERSION_IDENT(2,2,0,0))
14507 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14508 player->index_bit, dig_side);
14509 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14510 player->index_bit, dig_side);
14512 if (element == EL_DC_LANDMINE)
14515 if (Tile[x][y] != element) // field changed by snapping
14518 return MP_NO_ACTION;
14521 if (player->gravity && is_player && !player->is_auto_moving &&
14522 canFallDown(player) && move_direction != MV_DOWN &&
14523 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14524 return MP_NO_ACTION; // player cannot walk here due to gravity
14526 if (player_can_move &&
14527 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14529 int sound_element = SND_ELEMENT(element);
14530 int sound_action = ACTION_WALKING;
14532 if (IS_RND_GATE(element))
14534 if (!player->key[RND_GATE_NR(element)])
14535 return MP_NO_ACTION;
14537 else if (IS_RND_GATE_GRAY(element))
14539 if (!player->key[RND_GATE_GRAY_NR(element)])
14540 return MP_NO_ACTION;
14542 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14544 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14545 return MP_NO_ACTION;
14547 else if (element == EL_EXIT_OPEN ||
14548 element == EL_EM_EXIT_OPEN ||
14549 element == EL_EM_EXIT_OPENING ||
14550 element == EL_STEEL_EXIT_OPEN ||
14551 element == EL_EM_STEEL_EXIT_OPEN ||
14552 element == EL_EM_STEEL_EXIT_OPENING ||
14553 element == EL_SP_EXIT_OPEN ||
14554 element == EL_SP_EXIT_OPENING)
14556 sound_action = ACTION_PASSING; // player is passing exit
14558 else if (element == EL_EMPTY)
14560 sound_action = ACTION_MOVING; // nothing to walk on
14563 // play sound from background or player, whatever is available
14564 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14565 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14567 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14569 else if (player_can_move &&
14570 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14572 if (!ACCESS_FROM(element, opposite_direction))
14573 return MP_NO_ACTION; // field not accessible from this direction
14575 if (CAN_MOVE(element)) // only fixed elements can be passed!
14576 return MP_NO_ACTION;
14578 if (IS_EM_GATE(element))
14580 if (!player->key[EM_GATE_NR(element)])
14581 return MP_NO_ACTION;
14583 else if (IS_EM_GATE_GRAY(element))
14585 if (!player->key[EM_GATE_GRAY_NR(element)])
14586 return MP_NO_ACTION;
14588 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14590 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14591 return MP_NO_ACTION;
14593 else if (IS_EMC_GATE(element))
14595 if (!player->key[EMC_GATE_NR(element)])
14596 return MP_NO_ACTION;
14598 else if (IS_EMC_GATE_GRAY(element))
14600 if (!player->key[EMC_GATE_GRAY_NR(element)])
14601 return MP_NO_ACTION;
14603 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14605 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14606 return MP_NO_ACTION;
14608 else if (element == EL_DC_GATE_WHITE ||
14609 element == EL_DC_GATE_WHITE_GRAY ||
14610 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14612 if (player->num_white_keys == 0)
14613 return MP_NO_ACTION;
14615 player->num_white_keys--;
14617 else if (IS_SP_PORT(element))
14619 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14620 element == EL_SP_GRAVITY_PORT_RIGHT ||
14621 element == EL_SP_GRAVITY_PORT_UP ||
14622 element == EL_SP_GRAVITY_PORT_DOWN)
14623 player->gravity = !player->gravity;
14624 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14625 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14626 element == EL_SP_GRAVITY_ON_PORT_UP ||
14627 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14628 player->gravity = TRUE;
14629 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14630 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14631 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14632 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14633 player->gravity = FALSE;
14636 // automatically move to the next field with double speed
14637 player->programmed_action = move_direction;
14639 if (player->move_delay_reset_counter == 0)
14641 player->move_delay_reset_counter = 2; // two double speed steps
14643 DOUBLE_PLAYER_SPEED(player);
14646 PlayLevelSoundAction(x, y, ACTION_PASSING);
14648 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14652 if (mode != DF_SNAP)
14654 GfxElement[x][y] = GFX_ELEMENT(element);
14655 player->is_digging = TRUE;
14658 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14660 // use old behaviour for old levels (digging)
14661 if (!level.finish_dig_collect)
14663 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14664 player->index_bit, dig_side);
14666 // if digging triggered player relocation, finish digging tile
14667 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14668 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14671 if (mode == DF_SNAP)
14673 if (level.block_snap_field)
14674 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14676 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14678 // use old behaviour for old levels (snapping)
14679 if (!level.finish_dig_collect)
14680 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14681 player->index_bit, dig_side);
14684 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14688 if (is_player && mode != DF_SNAP)
14690 GfxElement[x][y] = element;
14691 player->is_collecting = TRUE;
14694 if (element == EL_SPEED_PILL)
14696 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14698 else if (element == EL_EXTRA_TIME && level.time > 0)
14700 TimeLeft += level.extra_time;
14702 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14704 DisplayGameControlValues();
14706 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14708 int shield_time = (element == EL_SHIELD_DEADLY ?
14709 level.shield_deadly_time :
14710 level.shield_normal_time);
14712 player->shield_normal_time_left += shield_time;
14713 if (element == EL_SHIELD_DEADLY)
14714 player->shield_deadly_time_left += shield_time;
14716 else if (element == EL_DYNAMITE ||
14717 element == EL_EM_DYNAMITE ||
14718 element == EL_SP_DISK_RED)
14720 if (player->inventory_size < MAX_INVENTORY_SIZE)
14721 player->inventory_element[player->inventory_size++] = element;
14723 DrawGameDoorValues();
14725 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14727 player->dynabomb_count++;
14728 player->dynabombs_left++;
14730 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14732 player->dynabomb_size++;
14734 else if (element == EL_DYNABOMB_INCREASE_POWER)
14736 player->dynabomb_xl = TRUE;
14738 else if (IS_KEY(element))
14740 player->key[KEY_NR(element)] = TRUE;
14742 DrawGameDoorValues();
14744 else if (element == EL_DC_KEY_WHITE)
14746 player->num_white_keys++;
14748 // display white keys?
14749 // DrawGameDoorValues();
14751 else if (IS_ENVELOPE(element))
14753 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14755 if (!wait_for_snapping)
14756 player->show_envelope = element;
14758 else if (element == EL_EMC_LENSES)
14760 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14762 RedrawAllInvisibleElementsForLenses();
14764 else if (element == EL_EMC_MAGNIFIER)
14766 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14768 RedrawAllInvisibleElementsForMagnifier();
14770 else if (IS_DROPPABLE(element) ||
14771 IS_THROWABLE(element)) // can be collected and dropped
14775 if (collect_count == 0)
14776 player->inventory_infinite_element = element;
14778 for (i = 0; i < collect_count; i++)
14779 if (player->inventory_size < MAX_INVENTORY_SIZE)
14780 player->inventory_element[player->inventory_size++] = element;
14782 DrawGameDoorValues();
14784 else if (collect_count > 0)
14786 game.gems_still_needed -= collect_count;
14787 if (game.gems_still_needed < 0)
14788 game.gems_still_needed = 0;
14790 game.snapshot.collected_item = TRUE;
14792 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14794 DisplayGameControlValues();
14797 RaiseScoreElement(element);
14798 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14800 // use old behaviour for old levels (collecting)
14801 if (!level.finish_dig_collect && is_player)
14803 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14804 player->index_bit, dig_side);
14806 // if collecting triggered player relocation, finish collecting tile
14807 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14808 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14811 if (mode == DF_SNAP)
14813 if (level.block_snap_field)
14814 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14816 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14818 // use old behaviour for old levels (snapping)
14819 if (!level.finish_dig_collect)
14820 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14821 player->index_bit, dig_side);
14824 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14826 if (mode == DF_SNAP && element != EL_BD_ROCK)
14827 return MP_NO_ACTION;
14829 if (CAN_FALL(element) && dy)
14830 return MP_NO_ACTION;
14832 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14833 !(element == EL_SPRING && level.use_spring_bug))
14834 return MP_NO_ACTION;
14836 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14837 ((move_direction & MV_VERTICAL &&
14838 ((element_info[element].move_pattern & MV_LEFT &&
14839 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14840 (element_info[element].move_pattern & MV_RIGHT &&
14841 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14842 (move_direction & MV_HORIZONTAL &&
14843 ((element_info[element].move_pattern & MV_UP &&
14844 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14845 (element_info[element].move_pattern & MV_DOWN &&
14846 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14847 return MP_NO_ACTION;
14849 // do not push elements already moving away faster than player
14850 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14851 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14852 return MP_NO_ACTION;
14854 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14856 if (player->push_delay_value == -1 || !player_was_pushing)
14857 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14859 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14861 if (player->push_delay_value == -1)
14862 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14864 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14866 if (!player->is_pushing)
14867 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14870 player->is_pushing = TRUE;
14871 player->is_active = TRUE;
14873 if (!(IN_LEV_FIELD(nextx, nexty) &&
14874 (IS_FREE(nextx, nexty) ||
14875 (IS_SB_ELEMENT(element) &&
14876 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14877 (IS_CUSTOM_ELEMENT(element) &&
14878 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14879 return MP_NO_ACTION;
14881 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14882 return MP_NO_ACTION;
14884 if (player->push_delay == -1) // new pushing; restart delay
14885 player->push_delay = 0;
14887 if (player->push_delay < player->push_delay_value &&
14888 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14889 element != EL_SPRING && element != EL_BALLOON)
14891 // make sure that there is no move delay before next try to push
14892 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14893 player->move_delay = 0;
14895 return MP_NO_ACTION;
14898 if (IS_CUSTOM_ELEMENT(element) &&
14899 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14901 if (!DigFieldByCE(nextx, nexty, element))
14902 return MP_NO_ACTION;
14905 if (IS_SB_ELEMENT(element))
14907 boolean sokoban_task_solved = FALSE;
14909 if (element == EL_SOKOBAN_FIELD_FULL)
14911 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14913 IncrementSokobanFieldsNeeded();
14914 IncrementSokobanObjectsNeeded();
14917 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14919 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14921 DecrementSokobanFieldsNeeded();
14922 DecrementSokobanObjectsNeeded();
14924 // sokoban object was pushed from empty field to sokoban field
14925 if (Back[x][y] == EL_EMPTY)
14926 sokoban_task_solved = TRUE;
14929 Tile[x][y] = EL_SOKOBAN_OBJECT;
14931 if (Back[x][y] == Back[nextx][nexty])
14932 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14933 else if (Back[x][y] != 0)
14934 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14937 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14940 if (sokoban_task_solved &&
14941 game.sokoban_fields_still_needed == 0 &&
14942 game.sokoban_objects_still_needed == 0 &&
14943 level.auto_exit_sokoban)
14945 game.players_still_needed = 0;
14949 PlaySound(SND_GAME_SOKOBAN_SOLVING);
14953 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14955 InitMovingField(x, y, move_direction);
14956 GfxAction[x][y] = ACTION_PUSHING;
14958 if (mode == DF_SNAP)
14959 ContinueMoving(x, y);
14961 MovPos[x][y] = (dx != 0 ? dx : dy);
14963 Pushed[x][y] = TRUE;
14964 Pushed[nextx][nexty] = TRUE;
14966 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14967 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14969 player->push_delay_value = -1; // get new value later
14971 // check for element change _after_ element has been pushed
14972 if (game.use_change_when_pushing_bug)
14974 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14975 player->index_bit, dig_side);
14976 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14977 player->index_bit, dig_side);
14980 else if (IS_SWITCHABLE(element))
14982 if (PLAYER_SWITCHING(player, x, y))
14984 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14985 player->index_bit, dig_side);
14990 player->is_switching = TRUE;
14991 player->switch_x = x;
14992 player->switch_y = y;
14994 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14996 if (element == EL_ROBOT_WHEEL)
14998 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
15000 game.robot_wheel_x = x;
15001 game.robot_wheel_y = y;
15002 game.robot_wheel_active = TRUE;
15004 TEST_DrawLevelField(x, y);
15006 else if (element == EL_SP_TERMINAL)
15010 SCAN_PLAYFIELD(xx, yy)
15012 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
15016 else if (Tile[xx][yy] == EL_SP_TERMINAL)
15018 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
15020 ResetGfxAnimation(xx, yy);
15021 TEST_DrawLevelField(xx, yy);
15025 else if (IS_BELT_SWITCH(element))
15027 ToggleBeltSwitch(x, y);
15029 else if (element == EL_SWITCHGATE_SWITCH_UP ||
15030 element == EL_SWITCHGATE_SWITCH_DOWN ||
15031 element == EL_DC_SWITCHGATE_SWITCH_UP ||
15032 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
15034 ToggleSwitchgateSwitch();
15036 else if (element == EL_LIGHT_SWITCH ||
15037 element == EL_LIGHT_SWITCH_ACTIVE)
15039 ToggleLightSwitch(x, y);
15041 else if (element == EL_TIMEGATE_SWITCH ||
15042 element == EL_DC_TIMEGATE_SWITCH)
15044 ActivateTimegateSwitch(x, y);
15046 else if (element == EL_BALLOON_SWITCH_LEFT ||
15047 element == EL_BALLOON_SWITCH_RIGHT ||
15048 element == EL_BALLOON_SWITCH_UP ||
15049 element == EL_BALLOON_SWITCH_DOWN ||
15050 element == EL_BALLOON_SWITCH_NONE ||
15051 element == EL_BALLOON_SWITCH_ANY)
15053 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
15054 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
15055 element == EL_BALLOON_SWITCH_UP ? MV_UP :
15056 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
15057 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
15060 else if (element == EL_LAMP)
15062 Tile[x][y] = EL_LAMP_ACTIVE;
15063 game.lights_still_needed--;
15065 ResetGfxAnimation(x, y);
15066 TEST_DrawLevelField(x, y);
15068 else if (element == EL_TIME_ORB_FULL)
15070 Tile[x][y] = EL_TIME_ORB_EMPTY;
15072 if (level.time > 0 || level.use_time_orb_bug)
15074 TimeLeft += level.time_orb_time;
15075 game.no_level_time_limit = FALSE;
15077 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
15079 DisplayGameControlValues();
15082 ResetGfxAnimation(x, y);
15083 TEST_DrawLevelField(x, y);
15085 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
15086 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15090 game.ball_active = !game.ball_active;
15092 SCAN_PLAYFIELD(xx, yy)
15094 int e = Tile[xx][yy];
15096 if (game.ball_active)
15098 if (e == EL_EMC_MAGIC_BALL)
15099 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
15100 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
15101 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
15105 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
15106 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
15107 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15108 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
15113 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15114 player->index_bit, dig_side);
15116 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15117 player->index_bit, dig_side);
15119 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15120 player->index_bit, dig_side);
15126 if (!PLAYER_SWITCHING(player, x, y))
15128 player->is_switching = TRUE;
15129 player->switch_x = x;
15130 player->switch_y = y;
15132 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
15133 player->index_bit, dig_side);
15134 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15135 player->index_bit, dig_side);
15137 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
15138 player->index_bit, dig_side);
15139 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15140 player->index_bit, dig_side);
15143 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
15144 player->index_bit, dig_side);
15145 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15146 player->index_bit, dig_side);
15148 return MP_NO_ACTION;
15151 player->push_delay = -1;
15153 if (is_player) // function can also be called by EL_PENGUIN
15155 if (Tile[x][y] != element) // really digged/collected something
15157 player->is_collecting = !player->is_digging;
15158 player->is_active = TRUE;
15160 player->last_removed_element = element;
15167 static boolean DigFieldByCE(int x, int y, int digging_element)
15169 int element = Tile[x][y];
15171 if (!IS_FREE(x, y))
15173 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
15174 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
15177 // no element can dig solid indestructible elements
15178 if (IS_INDESTRUCTIBLE(element) &&
15179 !IS_DIGGABLE(element) &&
15180 !IS_COLLECTIBLE(element))
15183 if (AmoebaNr[x][y] &&
15184 (element == EL_AMOEBA_FULL ||
15185 element == EL_BD_AMOEBA ||
15186 element == EL_AMOEBA_GROWING))
15188 AmoebaCnt[AmoebaNr[x][y]]--;
15189 AmoebaCnt2[AmoebaNr[x][y]]--;
15192 if (IS_MOVING(x, y))
15193 RemoveMovingField(x, y);
15197 TEST_DrawLevelField(x, y);
15200 // if digged element was about to explode, prevent the explosion
15201 ExplodeField[x][y] = EX_TYPE_NONE;
15203 PlayLevelSoundAction(x, y, action);
15206 Store[x][y] = EL_EMPTY;
15208 // this makes it possible to leave the removed element again
15209 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15210 Store[x][y] = element;
15215 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15217 int jx = player->jx, jy = player->jy;
15218 int x = jx + dx, y = jy + dy;
15219 int snap_direction = (dx == -1 ? MV_LEFT :
15220 dx == +1 ? MV_RIGHT :
15222 dy == +1 ? MV_DOWN : MV_NONE);
15223 boolean can_continue_snapping = (level.continuous_snapping &&
15224 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15226 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15229 if (!player->active || !IN_LEV_FIELD(x, y))
15237 if (player->MovPos == 0)
15238 player->is_pushing = FALSE;
15240 player->is_snapping = FALSE;
15242 if (player->MovPos == 0)
15244 player->is_moving = FALSE;
15245 player->is_digging = FALSE;
15246 player->is_collecting = FALSE;
15252 // prevent snapping with already pressed snap key when not allowed
15253 if (player->is_snapping && !can_continue_snapping)
15256 player->MovDir = snap_direction;
15258 if (player->MovPos == 0)
15260 player->is_moving = FALSE;
15261 player->is_digging = FALSE;
15262 player->is_collecting = FALSE;
15265 player->is_dropping = FALSE;
15266 player->is_dropping_pressed = FALSE;
15267 player->drop_pressed_delay = 0;
15269 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15272 player->is_snapping = TRUE;
15273 player->is_active = TRUE;
15275 if (player->MovPos == 0)
15277 player->is_moving = FALSE;
15278 player->is_digging = FALSE;
15279 player->is_collecting = FALSE;
15282 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15283 TEST_DrawLevelField(player->last_jx, player->last_jy);
15285 TEST_DrawLevelField(x, y);
15290 static boolean DropElement(struct PlayerInfo *player)
15292 int old_element, new_element;
15293 int dropx = player->jx, dropy = player->jy;
15294 int drop_direction = player->MovDir;
15295 int drop_side = drop_direction;
15296 int drop_element = get_next_dropped_element(player);
15298 /* do not drop an element on top of another element; when holding drop key
15299 pressed without moving, dropped element must move away before the next
15300 element can be dropped (this is especially important if the next element
15301 is dynamite, which can be placed on background for historical reasons) */
15302 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15305 if (IS_THROWABLE(drop_element))
15307 dropx += GET_DX_FROM_DIR(drop_direction);
15308 dropy += GET_DY_FROM_DIR(drop_direction);
15310 if (!IN_LEV_FIELD(dropx, dropy))
15314 old_element = Tile[dropx][dropy]; // old element at dropping position
15315 new_element = drop_element; // default: no change when dropping
15317 // check if player is active, not moving and ready to drop
15318 if (!player->active || player->MovPos || player->drop_delay > 0)
15321 // check if player has anything that can be dropped
15322 if (new_element == EL_UNDEFINED)
15325 // only set if player has anything that can be dropped
15326 player->is_dropping_pressed = TRUE;
15328 // check if drop key was pressed long enough for EM style dynamite
15329 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15332 // check if anything can be dropped at the current position
15333 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15336 // collected custom elements can only be dropped on empty fields
15337 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15340 if (old_element != EL_EMPTY)
15341 Back[dropx][dropy] = old_element; // store old element on this field
15343 ResetGfxAnimation(dropx, dropy);
15344 ResetRandomAnimationValue(dropx, dropy);
15346 if (player->inventory_size > 0 ||
15347 player->inventory_infinite_element != EL_UNDEFINED)
15349 if (player->inventory_size > 0)
15351 player->inventory_size--;
15353 DrawGameDoorValues();
15355 if (new_element == EL_DYNAMITE)
15356 new_element = EL_DYNAMITE_ACTIVE;
15357 else if (new_element == EL_EM_DYNAMITE)
15358 new_element = EL_EM_DYNAMITE_ACTIVE;
15359 else if (new_element == EL_SP_DISK_RED)
15360 new_element = EL_SP_DISK_RED_ACTIVE;
15363 Tile[dropx][dropy] = new_element;
15365 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15366 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15367 el2img(Tile[dropx][dropy]), 0);
15369 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15371 // needed if previous element just changed to "empty" in the last frame
15372 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15374 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15375 player->index_bit, drop_side);
15376 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15378 player->index_bit, drop_side);
15380 TestIfElementTouchesCustomElement(dropx, dropy);
15382 else // player is dropping a dyna bomb
15384 player->dynabombs_left--;
15386 Tile[dropx][dropy] = new_element;
15388 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15389 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15390 el2img(Tile[dropx][dropy]), 0);
15392 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15395 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15396 InitField_WithBug1(dropx, dropy, FALSE);
15398 new_element = Tile[dropx][dropy]; // element might have changed
15400 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15401 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15403 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15404 MovDir[dropx][dropy] = drop_direction;
15406 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15408 // do not cause impact style collision by dropping elements that can fall
15409 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15412 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15413 player->is_dropping = TRUE;
15415 player->drop_pressed_delay = 0;
15416 player->is_dropping_pressed = FALSE;
15418 player->drop_x = dropx;
15419 player->drop_y = dropy;
15424 // ----------------------------------------------------------------------------
15425 // game sound playing functions
15426 // ----------------------------------------------------------------------------
15428 static int *loop_sound_frame = NULL;
15429 static int *loop_sound_volume = NULL;
15431 void InitPlayLevelSound(void)
15433 int num_sounds = getSoundListSize();
15435 checked_free(loop_sound_frame);
15436 checked_free(loop_sound_volume);
15438 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15439 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15442 static void PlayLevelSoundExt(int x, int y, int nr, boolean is_loop_sound)
15444 int sx = SCREENX(x), sy = SCREENY(y);
15445 int volume, stereo_position;
15446 int max_distance = 8;
15447 int type = (is_loop_sound ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15449 if ((!setup.sound_simple && !is_loop_sound) ||
15450 (!setup.sound_loops && is_loop_sound))
15453 if (!IN_LEV_FIELD(x, y) ||
15454 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15455 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15458 volume = SOUND_MAX_VOLUME;
15460 if (!IN_SCR_FIELD(sx, sy))
15462 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15463 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15465 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15468 stereo_position = (SOUND_MAX_LEFT +
15469 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15470 (SCR_FIELDX + 2 * max_distance));
15474 /* This assures that quieter loop sounds do not overwrite louder ones,
15475 while restarting sound volume comparison with each new game frame. */
15477 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15480 loop_sound_volume[nr] = volume;
15481 loop_sound_frame[nr] = FrameCounter;
15484 PlaySoundExt(nr, volume, stereo_position, type);
15487 static void PlayLevelSound(int x, int y, int nr)
15489 PlayLevelSoundExt(x, y, nr, IS_LOOP_SOUND(nr));
15492 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15494 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15495 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15496 y < LEVELY(BY1) ? LEVELY(BY1) :
15497 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15501 static void PlayLevelSoundAction(int x, int y, int action)
15503 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15506 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15508 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15510 if (sound_effect != SND_UNDEFINED)
15511 PlayLevelSound(x, y, sound_effect);
15514 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15517 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15519 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15520 PlayLevelSound(x, y, sound_effect);
15523 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15525 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15527 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15528 PlayLevelSound(x, y, sound_effect);
15531 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15533 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15535 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15536 StopSound(sound_effect);
15539 static int getLevelMusicNr(void)
15541 int level_pos = level_nr - leveldir_current->first_level;
15543 if (levelset.music[level_nr] != MUS_UNDEFINED)
15544 return levelset.music[level_nr]; // from config file
15546 return MAP_NOCONF_MUSIC(level_pos); // from music dir
15549 static void FadeLevelSounds(void)
15554 static void FadeLevelMusic(void)
15556 int music_nr = getLevelMusicNr();
15557 char *curr_music = getCurrentlyPlayingMusicFilename();
15558 char *next_music = getMusicInfoEntryFilename(music_nr);
15560 if (!strEqual(curr_music, next_music))
15564 void FadeLevelSoundsAndMusic(void)
15570 static void PlayLevelMusic(void)
15572 int music_nr = getLevelMusicNr();
15573 char *curr_music = getCurrentlyPlayingMusicFilename();
15574 char *next_music = getMusicInfoEntryFilename(music_nr);
15576 if (!strEqual(curr_music, next_music))
15577 PlayMusicLoop(music_nr);
15580 static int getSoundAction_BD(int sample)
15584 case GD_S_STONE_PUSHING:
15585 case GD_S_MEGA_STONE_PUSHING:
15586 case GD_S_FLYING_STONE_PUSHING:
15587 case GD_S_WAITING_STONE_PUSHING:
15588 case GD_S_CHASING_STONE_PUSHING:
15589 case GD_S_NUT_PUSHING:
15590 case GD_S_NITRO_PACK_PUSHING:
15591 case GD_S_BLADDER_PUSHING:
15592 case GD_S_BOX_PUSHING:
15593 return ACTION_PUSHING;
15595 case GD_S_STONE_FALLING:
15596 case GD_S_MEGA_STONE_FALLING:
15597 case GD_S_FLYING_STONE_FALLING:
15598 case GD_S_NUT_FALLING:
15599 case GD_S_DIRT_BALL_FALLING:
15600 case GD_S_DIRT_LOOSE_FALLING:
15601 case GD_S_NITRO_PACK_FALLING:
15602 case GD_S_FALLING_WALL_FALLING:
15603 return ACTION_FALLING;
15605 case GD_S_STONE_IMPACT:
15606 case GD_S_MEGA_STONE_IMPACT:
15607 case GD_S_FLYING_STONE_IMPACT:
15608 case GD_S_NUT_IMPACT:
15609 case GD_S_DIRT_BALL_IMPACT:
15610 case GD_S_DIRT_LOOSE_IMPACT:
15611 case GD_S_NITRO_PACK_IMPACT:
15612 case GD_S_FALLING_WALL_IMPACT:
15613 return ACTION_IMPACT;
15615 case GD_S_NUT_CRACKING:
15616 return ACTION_BREAKING;
15618 case GD_S_EXPANDING_WALL:
15619 case GD_S_WALL_REAPPEARING:
15622 case GD_S_ACID_SPREADING:
15623 return ACTION_GROWING;
15625 case GD_S_DIAMOND_COLLECTING:
15626 case GD_S_FLYING_DIAMOND_COLLECTING:
15627 case GD_S_SKELETON_COLLECTING:
15628 case GD_S_PNEUMATIC_COLLECTING:
15629 case GD_S_BOMB_COLLECTING:
15630 case GD_S_CLOCK_COLLECTING:
15631 case GD_S_SWEET_COLLECTING:
15632 case GD_S_KEY_COLLECTING:
15633 case GD_S_DIAMOND_KEY_COLLECTING:
15634 return ACTION_COLLECTING;
15636 case GD_S_BOMB_PLACING:
15637 case GD_S_REPLICATOR:
15638 return ACTION_DROPPING;
15640 case GD_S_BLADDER_MOVING:
15641 return ACTION_MOVING;
15643 case GD_S_BLADDER_SPENDER:
15644 case GD_S_BLADDER_CONVERTING:
15645 case GD_S_GRAVITY_CHANGING:
15646 return ACTION_CHANGING;
15648 case GD_S_BITER_EATING:
15649 return ACTION_EATING;
15651 case GD_S_DOOR_OPENING:
15652 case GD_S_CRACKING:
15653 return ACTION_OPENING;
15655 case GD_S_DIRT_WALKING:
15656 return ACTION_DIGGING;
15658 case GD_S_EMPTY_WALKING:
15659 return ACTION_WALKING;
15661 case GD_S_SWITCH_BITER:
15662 case GD_S_SWITCH_CREATURES:
15663 case GD_S_SWITCH_GRAVITY:
15664 case GD_S_SWITCH_EXPANDING:
15665 case GD_S_SWITCH_CONVEYOR:
15666 case GD_S_SWITCH_REPLICATOR:
15667 case GD_S_STIRRING:
15668 return ACTION_ACTIVATING;
15670 case GD_S_TELEPORTER:
15671 return ACTION_PASSING;
15673 case GD_S_EXPLODING:
15674 case GD_S_BOMB_EXPLODING:
15675 case GD_S_GHOST_EXPLODING:
15676 case GD_S_VOODOO_EXPLODING:
15677 case GD_S_NITRO_PACK_EXPLODING:
15678 return ACTION_EXPLODING;
15680 case GD_S_COVERING:
15682 case GD_S_MAGIC_WALL:
15683 case GD_S_PNEUMATIC_HAMMER:
15685 return ACTION_ACTIVE;
15687 case GD_S_DIAMOND_FALLING_RANDOM:
15688 case GD_S_DIAMOND_FALLING_1:
15689 case GD_S_DIAMOND_FALLING_2:
15690 case GD_S_DIAMOND_FALLING_3:
15691 case GD_S_DIAMOND_FALLING_4:
15692 case GD_S_DIAMOND_FALLING_5:
15693 case GD_S_DIAMOND_FALLING_6:
15694 case GD_S_DIAMOND_FALLING_7:
15695 case GD_S_DIAMOND_FALLING_8:
15696 case GD_S_DIAMOND_IMPACT_RANDOM:
15697 case GD_S_DIAMOND_IMPACT_1:
15698 case GD_S_DIAMOND_IMPACT_2:
15699 case GD_S_DIAMOND_IMPACT_3:
15700 case GD_S_DIAMOND_IMPACT_4:
15701 case GD_S_DIAMOND_IMPACT_5:
15702 case GD_S_DIAMOND_IMPACT_6:
15703 case GD_S_DIAMOND_IMPACT_7:
15704 case GD_S_DIAMOND_IMPACT_8:
15705 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15706 case GD_S_FLYING_DIAMOND_FALLING_1:
15707 case GD_S_FLYING_DIAMOND_FALLING_2:
15708 case GD_S_FLYING_DIAMOND_FALLING_3:
15709 case GD_S_FLYING_DIAMOND_FALLING_4:
15710 case GD_S_FLYING_DIAMOND_FALLING_5:
15711 case GD_S_FLYING_DIAMOND_FALLING_6:
15712 case GD_S_FLYING_DIAMOND_FALLING_7:
15713 case GD_S_FLYING_DIAMOND_FALLING_8:
15714 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15715 case GD_S_FLYING_DIAMOND_IMPACT_1:
15716 case GD_S_FLYING_DIAMOND_IMPACT_2:
15717 case GD_S_FLYING_DIAMOND_IMPACT_3:
15718 case GD_S_FLYING_DIAMOND_IMPACT_4:
15719 case GD_S_FLYING_DIAMOND_IMPACT_5:
15720 case GD_S_FLYING_DIAMOND_IMPACT_6:
15721 case GD_S_FLYING_DIAMOND_IMPACT_7:
15722 case GD_S_FLYING_DIAMOND_IMPACT_8:
15723 case GD_S_TIMEOUT_0:
15724 case GD_S_TIMEOUT_1:
15725 case GD_S_TIMEOUT_2:
15726 case GD_S_TIMEOUT_3:
15727 case GD_S_TIMEOUT_4:
15728 case GD_S_TIMEOUT_5:
15729 case GD_S_TIMEOUT_6:
15730 case GD_S_TIMEOUT_7:
15731 case GD_S_TIMEOUT_8:
15732 case GD_S_TIMEOUT_9:
15733 case GD_S_TIMEOUT_10:
15734 case GD_S_BONUS_LIFE:
15735 // trigger special post-processing (and force sound to be non-looping)
15736 return ACTION_OTHER;
15738 case GD_S_AMOEBA_MAGIC:
15739 case GD_S_FINISHED:
15740 // trigger special post-processing (and force sound to be looping)
15741 return ACTION_DEFAULT;
15744 return ACTION_DEFAULT;
15748 static int getSoundEffect_BD(int element_bd, int sample)
15750 int sound_action = getSoundAction_BD(sample);
15751 int sound_effect = element_info[SND_ELEMENT(element_bd)].sound[sound_action];
15755 if (sound_action != ACTION_OTHER &&
15756 sound_action != ACTION_DEFAULT)
15757 return sound_effect;
15759 // special post-processing for some sounds
15762 case GD_S_DIAMOND_FALLING_RANDOM:
15763 case GD_S_DIAMOND_FALLING_1:
15764 case GD_S_DIAMOND_FALLING_2:
15765 case GD_S_DIAMOND_FALLING_3:
15766 case GD_S_DIAMOND_FALLING_4:
15767 case GD_S_DIAMOND_FALLING_5:
15768 case GD_S_DIAMOND_FALLING_6:
15769 case GD_S_DIAMOND_FALLING_7:
15770 case GD_S_DIAMOND_FALLING_8:
15771 nr = (sample == GD_S_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15772 sample - GD_S_DIAMOND_FALLING_1);
15773 sound_effect = SND_BD_DIAMOND_FALLING_RANDOM_1 + nr;
15775 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15776 sound_effect = SND_BD_DIAMOND_FALLING;
15779 case GD_S_DIAMOND_IMPACT_RANDOM:
15780 case GD_S_DIAMOND_IMPACT_1:
15781 case GD_S_DIAMOND_IMPACT_2:
15782 case GD_S_DIAMOND_IMPACT_3:
15783 case GD_S_DIAMOND_IMPACT_4:
15784 case GD_S_DIAMOND_IMPACT_5:
15785 case GD_S_DIAMOND_IMPACT_6:
15786 case GD_S_DIAMOND_IMPACT_7:
15787 case GD_S_DIAMOND_IMPACT_8:
15788 nr = (sample == GD_S_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15789 sample - GD_S_DIAMOND_IMPACT_1);
15790 sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr;
15792 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15793 sound_effect = SND_BD_DIAMOND_IMPACT;
15796 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15797 case GD_S_FLYING_DIAMOND_FALLING_1:
15798 case GD_S_FLYING_DIAMOND_FALLING_2:
15799 case GD_S_FLYING_DIAMOND_FALLING_3:
15800 case GD_S_FLYING_DIAMOND_FALLING_4:
15801 case GD_S_FLYING_DIAMOND_FALLING_5:
15802 case GD_S_FLYING_DIAMOND_FALLING_6:
15803 case GD_S_FLYING_DIAMOND_FALLING_7:
15804 case GD_S_FLYING_DIAMOND_FALLING_8:
15805 nr = (sample == GD_S_FLYING_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15806 sample - GD_S_FLYING_DIAMOND_FALLING_1);
15807 sound_effect = SND_BD_FLYING_DIAMOND_FALLING_RANDOM_1 + nr;
15809 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15810 sound_effect = SND_BD_FLYING_DIAMOND_FALLING;
15813 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15814 case GD_S_FLYING_DIAMOND_IMPACT_1:
15815 case GD_S_FLYING_DIAMOND_IMPACT_2:
15816 case GD_S_FLYING_DIAMOND_IMPACT_3:
15817 case GD_S_FLYING_DIAMOND_IMPACT_4:
15818 case GD_S_FLYING_DIAMOND_IMPACT_5:
15819 case GD_S_FLYING_DIAMOND_IMPACT_6:
15820 case GD_S_FLYING_DIAMOND_IMPACT_7:
15821 case GD_S_FLYING_DIAMOND_IMPACT_8:
15822 nr = (sample == GD_S_FLYING_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15823 sample - GD_S_FLYING_DIAMOND_IMPACT_1);
15824 sound_effect = SND_BD_FLYING_DIAMOND_IMPACT_RANDOM_1 + nr;
15826 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15827 sound_effect = SND_BD_FLYING_DIAMOND_IMPACT;
15830 case GD_S_TIMEOUT_0:
15831 case GD_S_TIMEOUT_1:
15832 case GD_S_TIMEOUT_2:
15833 case GD_S_TIMEOUT_3:
15834 case GD_S_TIMEOUT_4:
15835 case GD_S_TIMEOUT_5:
15836 case GD_S_TIMEOUT_6:
15837 case GD_S_TIMEOUT_7:
15838 case GD_S_TIMEOUT_8:
15839 case GD_S_TIMEOUT_9:
15840 case GD_S_TIMEOUT_10:
15841 nr = sample - GD_S_TIMEOUT_0;
15842 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME_0 + nr;
15844 if (getSoundInfoEntryFilename(sound_effect) == NULL && sample != GD_S_TIMEOUT_0)
15845 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME;
15848 case GD_S_BONUS_LIFE:
15849 sound_effect = SND_GAME_HEALTH_BONUS;
15852 case GD_S_AMOEBA_MAGIC:
15853 sound_effect = SND_BD_AMOEBA_OTHER;
15856 case GD_S_FINISHED:
15857 sound_effect = SND_GAME_LEVELTIME_BONUS;
15861 sound_effect = SND_UNDEFINED;
15865 return sound_effect;
15868 void PlayLevelSound_BD(int xx, int yy, int element_bd, int sample)
15870 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15871 int sound_effect = getSoundEffect_BD(element, sample);
15872 int sound_action = getSoundAction_BD(sample);
15873 boolean is_loop_sound = IS_LOOP_SOUND(sound_effect);
15875 int x = xx - offset;
15876 int y = yy - offset;
15878 // some sound actions are always looping in native BD game engine
15879 if (sound_action == ACTION_DEFAULT)
15880 is_loop_sound = TRUE;
15882 // some sound actions are always non-looping in native BD game engine
15883 if (sound_action == ACTION_FALLING ||
15884 sound_action == ACTION_MOVING ||
15885 sound_action == ACTION_OTHER)
15886 is_loop_sound = FALSE;
15888 if (sound_effect != SND_UNDEFINED)
15889 PlayLevelSoundExt(x, y, sound_effect, is_loop_sound);
15892 void StopSound_BD(int element_bd, int sample)
15894 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15895 int sound_effect = getSoundEffect_BD(element, sample);
15897 if (sound_effect != SND_UNDEFINED)
15898 StopSound(sound_effect);
15901 boolean isSoundPlaying_BD(int element_bd, int sample)
15903 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15904 int sound_effect = getSoundEffect_BD(element, sample);
15906 if (sound_effect != SND_UNDEFINED)
15907 return isSoundPlaying(sound_effect);
15912 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15914 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15916 int x = xx - offset;
15917 int y = yy - offset;
15922 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15926 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15930 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15934 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15938 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15942 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15946 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15949 case SOUND_android_clone:
15950 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15953 case SOUND_android_move:
15954 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15958 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15962 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15966 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15969 case SOUND_eater_eat:
15970 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15974 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15977 case SOUND_collect:
15978 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15981 case SOUND_diamond:
15982 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15986 // !!! CHECK THIS !!!
15988 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15990 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15994 case SOUND_wonderfall:
15995 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15999 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
16003 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
16007 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
16011 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
16015 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16019 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
16023 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16027 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16030 case SOUND_exit_open:
16031 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
16034 case SOUND_exit_leave:
16035 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16038 case SOUND_dynamite:
16039 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16043 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16047 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
16051 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16055 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
16059 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
16063 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
16067 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
16072 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
16074 int element = map_element_SP_to_RND(element_sp);
16075 int action = map_action_SP_to_RND(action_sp);
16076 int offset = (setup.sp_show_border_elements ? 0 : 1);
16077 int x = xx - offset;
16078 int y = yy - offset;
16080 PlayLevelSoundElementAction(x, y, element, action);
16083 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
16085 int element = map_element_MM_to_RND(element_mm);
16086 int action = map_action_MM_to_RND(action_mm);
16088 int x = xx - offset;
16089 int y = yy - offset;
16091 if (!IS_MM_ELEMENT(element))
16092 element = EL_MM_DEFAULT;
16094 PlayLevelSoundElementAction(x, y, element, action);
16097 void PlaySound_MM(int sound_mm)
16099 int sound = map_sound_MM_to_RND(sound_mm);
16101 if (sound == SND_UNDEFINED)
16107 void PlaySoundLoop_MM(int sound_mm)
16109 int sound = map_sound_MM_to_RND(sound_mm);
16111 if (sound == SND_UNDEFINED)
16114 PlaySoundLoop(sound);
16117 void StopSound_MM(int sound_mm)
16119 int sound = map_sound_MM_to_RND(sound_mm);
16121 if (sound == SND_UNDEFINED)
16127 void RaiseScore(int value)
16129 game.score += value;
16131 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
16133 DisplayGameControlValues();
16136 void RaiseScoreElement(int element)
16141 case EL_BD_DIAMOND:
16142 case EL_EMERALD_YELLOW:
16143 case EL_EMERALD_RED:
16144 case EL_EMERALD_PURPLE:
16145 case EL_SP_INFOTRON:
16146 RaiseScore(level.score[SC_EMERALD]);
16149 RaiseScore(level.score[SC_DIAMOND]);
16152 RaiseScore(level.score[SC_CRYSTAL]);
16155 RaiseScore(level.score[SC_PEARL]);
16158 case EL_BD_BUTTERFLY:
16159 case EL_SP_ELECTRON:
16160 RaiseScore(level.score[SC_BUG]);
16163 case EL_BD_FIREFLY:
16164 case EL_SP_SNIKSNAK:
16165 RaiseScore(level.score[SC_SPACESHIP]);
16168 case EL_DARK_YAMYAM:
16169 RaiseScore(level.score[SC_YAMYAM]);
16172 RaiseScore(level.score[SC_ROBOT]);
16175 RaiseScore(level.score[SC_PACMAN]);
16178 RaiseScore(level.score[SC_NUT]);
16181 case EL_EM_DYNAMITE:
16182 case EL_SP_DISK_RED:
16183 case EL_DYNABOMB_INCREASE_NUMBER:
16184 case EL_DYNABOMB_INCREASE_SIZE:
16185 case EL_DYNABOMB_INCREASE_POWER:
16186 RaiseScore(level.score[SC_DYNAMITE]);
16188 case EL_SHIELD_NORMAL:
16189 case EL_SHIELD_DEADLY:
16190 RaiseScore(level.score[SC_SHIELD]);
16192 case EL_EXTRA_TIME:
16193 RaiseScore(level.extra_time_score);
16207 case EL_DC_KEY_WHITE:
16208 RaiseScore(level.score[SC_KEY]);
16211 RaiseScore(element_info[element].collect_score);
16216 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
16218 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
16222 // prevent short reactivation of overlay buttons while closing door
16223 SetOverlayActive(FALSE);
16224 UnmapGameButtons();
16226 // door may still be open due to skipped or envelope style request
16227 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
16230 if (network.enabled)
16232 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
16236 // when using BD game engine, cover screen before fading out
16237 if (!quick_quit && level.game_engine_type == GAME_ENGINE_TYPE_BD)
16238 game_bd.cover_screen = TRUE;
16241 FadeSkipNextFadeIn();
16243 SetGameStatus(GAME_MODE_MAIN);
16248 else // continue playing the game
16250 if (tape.playing && tape.deactivate_display)
16251 TapeDeactivateDisplayOff(TRUE);
16253 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
16255 if (tape.playing && tape.deactivate_display)
16256 TapeDeactivateDisplayOn();
16260 void RequestQuitGame(boolean escape_key_pressed)
16262 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
16263 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
16264 level_editor_test_game);
16265 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
16266 quick_quit || score_info_tape_play);
16268 RequestQuitGameExt(skip_request, quick_quit,
16269 "Do you really want to quit the game?");
16272 static char *getRestartGameMessage(void)
16274 boolean play_again = hasStartedNetworkGame();
16275 static char message[MAX_OUTPUT_LINESIZE];
16276 char *game_over_text = "Game over!";
16277 char *play_again_text = " Play it again?";
16279 if (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
16280 game_mm.game_over_message != NULL)
16281 game_over_text = game_mm.game_over_message;
16283 snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text,
16284 (play_again ? play_again_text : ""));
16289 static void RequestRestartGame(void)
16291 char *message = getRestartGameMessage();
16292 boolean has_started_game = hasStartedNetworkGame();
16293 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
16294 int door_state = DOOR_CLOSE_1;
16296 boolean restart_wanted = (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game);
16298 // if no restart wanted, continue with next level for BD style intermission levels
16299 if (!restart_wanted && !level_editor_test_game && level.bd_intermission)
16301 boolean success = AdvanceToNextLevel();
16303 restart_wanted = (success && setup.auto_play_next_level);
16306 if (restart_wanted)
16308 CloseDoor(door_state);
16310 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16314 // if game was invoked from level editor, also close tape recorder door
16315 if (level_editor_test_game)
16316 door_state = DOOR_CLOSE_ALL;
16318 CloseDoor(door_state);
16320 SetGameStatus(GAME_MODE_MAIN);
16326 boolean CheckRestartGame(void)
16328 static int game_over_delay = 0;
16329 int game_over_delay_value = 50;
16330 boolean game_over = checkGameFailed();
16334 game_over_delay = game_over_delay_value;
16339 if (game_over_delay > 0)
16341 if (game_over_delay == game_over_delay_value / 2)
16342 PlaySound(SND_GAME_LOSING);
16349 // do not ask to play again if request dialog is already active
16350 if (checkRequestActive())
16353 // do not ask to play again if request dialog already handled
16354 if (game.RestartGameRequested)
16357 // do not ask to play again if game was never actually played
16358 if (!game.GamePlayed)
16361 // do not ask to play again if this was disabled in setup menu
16362 if (!setup.ask_on_game_over)
16365 game.RestartGameRequested = TRUE;
16367 RequestRestartGame();
16372 boolean checkGameRunning(void)
16374 if (game_status != GAME_MODE_PLAYING)
16377 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGameRunning_BD())
16383 boolean checkGamePlaying(void)
16385 if (game_status != GAME_MODE_PLAYING)
16388 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGamePlaying_BD())
16394 boolean checkGameSolved(void)
16396 // set for all game engines if level was solved
16397 return game.LevelSolved_GameEnd;
16400 boolean checkGameFailed(void)
16402 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
16403 return (game_bd.game_over && !game_bd.level_solved);
16404 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16405 return (game_em.game_over && !game_em.level_solved);
16406 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16407 return (game_sp.game_over && !game_sp.level_solved);
16408 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16409 return (game_mm.game_over && !game_mm.level_solved);
16410 else // GAME_ENGINE_TYPE_RND
16411 return (game.GameOver && !game.LevelSolved);
16414 boolean checkGameEnded(void)
16416 return (checkGameSolved() || checkGameFailed());
16419 boolean checkRequestActive(void)
16421 return (game.request_active || game.envelope_active || game.any_door_active);
16425 // ----------------------------------------------------------------------------
16426 // random generator functions
16427 // ----------------------------------------------------------------------------
16429 unsigned int InitEngineRandom_RND(int seed)
16431 game.num_random_calls = 0;
16433 return InitEngineRandom(seed);
16436 unsigned int RND(int max)
16440 game.num_random_calls++;
16442 return GetEngineRandom(max);
16449 // ----------------------------------------------------------------------------
16450 // game engine snapshot handling functions
16451 // ----------------------------------------------------------------------------
16453 struct EngineSnapshotInfo
16455 // runtime values for custom element collect score
16456 int collect_score[NUM_CUSTOM_ELEMENTS];
16458 // runtime values for group element choice position
16459 int choice_pos[NUM_GROUP_ELEMENTS];
16461 // runtime values for belt position animations
16462 int belt_graphic[4][NUM_BELT_PARTS];
16463 int belt_anim_mode[4][NUM_BELT_PARTS];
16466 static struct EngineSnapshotInfo engine_snapshot_rnd;
16467 static char *snapshot_level_identifier = NULL;
16468 static int snapshot_level_nr = -1;
16470 static void SaveEngineSnapshotValues_RND(void)
16472 static int belt_base_active_element[4] =
16474 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
16475 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
16476 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
16477 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
16481 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16483 int element = EL_CUSTOM_START + i;
16485 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
16488 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16490 int element = EL_GROUP_START + i;
16492 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
16495 for (i = 0; i < 4; i++)
16497 for (j = 0; j < NUM_BELT_PARTS; j++)
16499 int element = belt_base_active_element[i] + j;
16500 int graphic = el2img(element);
16501 int anim_mode = graphic_info[graphic].anim_mode;
16503 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
16504 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
16509 static void LoadEngineSnapshotValues_RND(void)
16511 unsigned int num_random_calls = game.num_random_calls;
16514 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16516 int element = EL_CUSTOM_START + i;
16518 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
16521 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16523 int element = EL_GROUP_START + i;
16525 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
16528 for (i = 0; i < 4; i++)
16530 for (j = 0; j < NUM_BELT_PARTS; j++)
16532 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
16533 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
16535 graphic_info[graphic].anim_mode = anim_mode;
16539 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16541 InitRND(tape.random_seed);
16542 for (i = 0; i < num_random_calls; i++)
16546 if (game.num_random_calls != num_random_calls)
16548 Error("number of random calls out of sync");
16549 Error("number of random calls should be %d", num_random_calls);
16550 Error("number of random calls is %d", game.num_random_calls);
16552 Fail("this should not happen -- please debug");
16556 void FreeEngineSnapshotSingle(void)
16558 FreeSnapshotSingle();
16560 setString(&snapshot_level_identifier, NULL);
16561 snapshot_level_nr = -1;
16564 void FreeEngineSnapshotList(void)
16566 FreeSnapshotList();
16569 static ListNode *SaveEngineSnapshotBuffers(void)
16571 ListNode *buffers = NULL;
16573 // copy some special values to a structure better suited for the snapshot
16575 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16576 SaveEngineSnapshotValues_RND();
16577 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16578 SaveEngineSnapshotValues_EM();
16579 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16580 SaveEngineSnapshotValues_SP(&buffers);
16581 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16582 SaveEngineSnapshotValues_MM();
16584 // save values stored in special snapshot structure
16586 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16587 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
16588 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16589 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
16590 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16591 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
16592 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16593 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
16595 // save further RND engine values
16597 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
16598 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
16599 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
16601 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
16602 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
16603 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
16604 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16605 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTimeFrames));
16606 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16608 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16609 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16610 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16612 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16614 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16615 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16617 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16618 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16619 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16620 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16621 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16622 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16623 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16624 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16625 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16626 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16627 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16628 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16629 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16630 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16631 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16632 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16633 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16634 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16636 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16637 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16639 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16640 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16641 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16643 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16644 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16646 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16647 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16648 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16649 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16650 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16651 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16653 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16654 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16657 ListNode *node = engine_snapshot_list_rnd;
16660 while (node != NULL)
16662 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16667 Debug("game:playing:SaveEngineSnapshotBuffers",
16668 "size of engine snapshot: %d bytes", num_bytes);
16674 void SaveEngineSnapshotSingle(void)
16676 ListNode *buffers = SaveEngineSnapshotBuffers();
16678 // finally save all snapshot buffers to single snapshot
16679 SaveSnapshotSingle(buffers);
16681 // save level identification information
16682 setString(&snapshot_level_identifier, leveldir_current->identifier);
16683 snapshot_level_nr = level_nr;
16686 boolean CheckSaveEngineSnapshotToList(void)
16688 boolean save_snapshot =
16689 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16690 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16691 game.snapshot.changed_action) ||
16692 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16693 game.snapshot.collected_item));
16695 game.snapshot.changed_action = FALSE;
16696 game.snapshot.collected_item = FALSE;
16697 game.snapshot.save_snapshot = save_snapshot;
16699 return save_snapshot;
16702 void SaveEngineSnapshotToList(void)
16704 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16708 ListNode *buffers = SaveEngineSnapshotBuffers();
16710 // finally save all snapshot buffers to snapshot list
16711 SaveSnapshotToList(buffers);
16714 void SaveEngineSnapshotToListInitial(void)
16716 FreeEngineSnapshotList();
16718 SaveEngineSnapshotToList();
16721 static void LoadEngineSnapshotValues(void)
16723 // restore special values from snapshot structure
16725 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16726 LoadEngineSnapshotValues_RND();
16727 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16728 LoadEngineSnapshotValues_EM();
16729 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16730 LoadEngineSnapshotValues_SP();
16731 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16732 LoadEngineSnapshotValues_MM();
16735 void LoadEngineSnapshotSingle(void)
16737 LoadSnapshotSingle();
16739 LoadEngineSnapshotValues();
16742 static void LoadEngineSnapshot_Undo(int steps)
16744 LoadSnapshotFromList_Older(steps);
16746 LoadEngineSnapshotValues();
16749 static void LoadEngineSnapshot_Redo(int steps)
16751 LoadSnapshotFromList_Newer(steps);
16753 LoadEngineSnapshotValues();
16756 boolean CheckEngineSnapshotSingle(void)
16758 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16759 snapshot_level_nr == level_nr);
16762 boolean CheckEngineSnapshotList(void)
16764 return CheckSnapshotList();
16768 // ---------- new game button stuff -------------------------------------------
16775 boolean *setup_value;
16776 boolean allowed_on_tape;
16777 boolean is_touch_button;
16779 } gamebutton_info[NUM_GAME_BUTTONS] =
16782 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16783 GAME_CTRL_ID_STOP, NULL,
16784 TRUE, FALSE, "stop game"
16787 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16788 GAME_CTRL_ID_PAUSE, NULL,
16789 TRUE, FALSE, "pause game"
16792 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16793 GAME_CTRL_ID_PLAY, NULL,
16794 TRUE, FALSE, "play game"
16797 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16798 GAME_CTRL_ID_UNDO, NULL,
16799 TRUE, FALSE, "undo step"
16802 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16803 GAME_CTRL_ID_REDO, NULL,
16804 TRUE, FALSE, "redo step"
16807 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16808 GAME_CTRL_ID_SAVE, NULL,
16809 TRUE, FALSE, "save game"
16812 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16813 GAME_CTRL_ID_PAUSE2, NULL,
16814 TRUE, FALSE, "pause game"
16817 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16818 GAME_CTRL_ID_LOAD, NULL,
16819 TRUE, FALSE, "load game"
16822 IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart,
16823 GAME_CTRL_ID_RESTART, NULL,
16824 TRUE, FALSE, "restart game"
16827 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16828 GAME_CTRL_ID_PANEL_STOP, NULL,
16829 FALSE, FALSE, "stop game"
16832 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16833 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16834 FALSE, FALSE, "pause game"
16837 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16838 GAME_CTRL_ID_PANEL_PLAY, NULL,
16839 FALSE, FALSE, "play game"
16842 IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart,
16843 GAME_CTRL_ID_PANEL_RESTART, NULL,
16844 FALSE, FALSE, "restart game"
16847 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16848 GAME_CTRL_ID_TOUCH_STOP, NULL,
16849 FALSE, TRUE, "stop game"
16852 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16853 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16854 FALSE, TRUE, "pause game"
16857 IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart,
16858 GAME_CTRL_ID_TOUCH_RESTART, NULL,
16859 FALSE, TRUE, "restart game"
16862 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16863 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16864 TRUE, FALSE, "background music on/off"
16867 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16868 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16869 TRUE, FALSE, "sound loops on/off"
16872 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16873 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16874 TRUE, FALSE, "normal sounds on/off"
16877 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16878 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16879 FALSE, FALSE, "background music on/off"
16882 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16883 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16884 FALSE, FALSE, "sound loops on/off"
16887 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16888 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16889 FALSE, FALSE, "normal sounds on/off"
16893 void CreateGameButtons(void)
16897 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16899 int graphic = gamebutton_info[i].graphic;
16900 struct GraphicInfo *gfx = &graphic_info[graphic];
16901 struct XY *pos = gamebutton_info[i].pos;
16902 struct GadgetInfo *gi;
16905 unsigned int event_mask;
16906 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16907 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16908 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16909 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16910 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16911 int gd_x = gfx->src_x;
16912 int gd_y = gfx->src_y;
16913 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16914 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16915 int gd_xa = gfx->src_x + gfx->active_xoffset;
16916 int gd_ya = gfx->src_y + gfx->active_yoffset;
16917 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16918 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16919 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16920 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16923 // do not use touch buttons if overlay touch buttons are disabled
16924 if (is_touch_button && !setup.touch.overlay_buttons)
16927 if (gfx->bitmap == NULL)
16929 game_gadget[id] = NULL;
16934 if (id == GAME_CTRL_ID_STOP ||
16935 id == GAME_CTRL_ID_PANEL_STOP ||
16936 id == GAME_CTRL_ID_TOUCH_STOP ||
16937 id == GAME_CTRL_ID_PLAY ||
16938 id == GAME_CTRL_ID_PANEL_PLAY ||
16939 id == GAME_CTRL_ID_SAVE ||
16940 id == GAME_CTRL_ID_LOAD ||
16941 id == GAME_CTRL_ID_RESTART ||
16942 id == GAME_CTRL_ID_PANEL_RESTART ||
16943 id == GAME_CTRL_ID_TOUCH_RESTART)
16945 button_type = GD_TYPE_NORMAL_BUTTON;
16947 event_mask = GD_EVENT_RELEASED;
16949 else if (id == GAME_CTRL_ID_UNDO ||
16950 id == GAME_CTRL_ID_REDO)
16952 button_type = GD_TYPE_NORMAL_BUTTON;
16954 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16958 button_type = GD_TYPE_CHECK_BUTTON;
16959 checked = (gamebutton_info[i].setup_value != NULL ?
16960 *gamebutton_info[i].setup_value : FALSE);
16961 event_mask = GD_EVENT_PRESSED;
16964 gi = CreateGadget(GDI_CUSTOM_ID, id,
16965 GDI_IMAGE_ID, graphic,
16966 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16969 GDI_WIDTH, gfx->width,
16970 GDI_HEIGHT, gfx->height,
16971 GDI_TYPE, button_type,
16972 GDI_STATE, GD_BUTTON_UNPRESSED,
16973 GDI_CHECKED, checked,
16974 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16975 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16976 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16977 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16978 GDI_DIRECT_DRAW, FALSE,
16979 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16980 GDI_EVENT_MASK, event_mask,
16981 GDI_CALLBACK_ACTION, HandleGameButtons,
16985 Fail("cannot create gadget");
16987 game_gadget[id] = gi;
16991 void FreeGameButtons(void)
16995 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16996 FreeGadget(game_gadget[i]);
16999 static void UnmapGameButtonsAtSamePosition(int id)
17003 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17005 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
17006 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
17007 UnmapGadget(game_gadget[i]);
17010 static void UnmapGameButtonsAtSamePosition_All(void)
17012 if (setup.show_load_save_buttons)
17014 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
17015 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
17016 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
17018 else if (setup.show_undo_redo_buttons)
17020 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
17021 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
17022 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
17026 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
17027 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
17028 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
17030 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
17031 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
17032 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
17036 void MapLoadSaveButtons(void)
17038 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
17039 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
17041 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
17042 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
17045 void MapUndoRedoButtons(void)
17047 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
17048 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
17050 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
17051 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
17054 void ModifyPauseButtons(void)
17058 GAME_CTRL_ID_PAUSE,
17059 GAME_CTRL_ID_PAUSE2,
17060 GAME_CTRL_ID_PANEL_PAUSE,
17061 GAME_CTRL_ID_TOUCH_PAUSE,
17066 // do not redraw pause button on closed door (may happen when restarting game)
17067 if (!(GetDoorState() & DOOR_OPEN_1))
17070 for (i = 0; ids[i] > -1; i++)
17071 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
17074 static void MapGameButtonsExt(boolean on_tape)
17078 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17080 if ((i == GAME_CTRL_ID_UNDO ||
17081 i == GAME_CTRL_ID_REDO) &&
17082 game_status != GAME_MODE_PLAYING)
17085 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17086 MapGadget(game_gadget[i]);
17089 UnmapGameButtonsAtSamePosition_All();
17091 RedrawGameButtons();
17094 static void UnmapGameButtonsExt(boolean on_tape)
17098 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17099 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17100 UnmapGadget(game_gadget[i]);
17103 static void RedrawGameButtonsExt(boolean on_tape)
17107 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17108 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17109 RedrawGadget(game_gadget[i]);
17112 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
17117 gi->checked = state;
17120 static void RedrawSoundButtonGadget(int id)
17122 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
17123 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
17124 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
17125 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
17126 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
17127 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
17130 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
17131 RedrawGadget(game_gadget[id2]);
17134 void MapGameButtons(void)
17136 MapGameButtonsExt(FALSE);
17139 void UnmapGameButtons(void)
17141 UnmapGameButtonsExt(FALSE);
17144 void RedrawGameButtons(void)
17146 RedrawGameButtonsExt(FALSE);
17149 void MapGameButtonsOnTape(void)
17151 MapGameButtonsExt(TRUE);
17154 void UnmapGameButtonsOnTape(void)
17156 UnmapGameButtonsExt(TRUE);
17159 void RedrawGameButtonsOnTape(void)
17161 RedrawGameButtonsExt(TRUE);
17164 static void GameUndoRedoExt(void)
17166 ClearPlayerAction();
17168 tape.pausing = TRUE;
17171 UpdateAndDisplayGameControlValues();
17173 DrawCompleteVideoDisplay();
17174 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
17175 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
17176 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
17178 ModifyPauseButtons();
17183 static void GameUndo(int steps)
17185 if (!CheckEngineSnapshotList())
17188 int tape_property_bits = tape.property_bits;
17190 LoadEngineSnapshot_Undo(steps);
17192 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17197 static void GameRedo(int steps)
17199 if (!CheckEngineSnapshotList())
17202 int tape_property_bits = tape.property_bits;
17204 LoadEngineSnapshot_Redo(steps);
17206 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17211 static void HandleGameButtonsExt(int id, int button)
17213 static boolean game_undo_executed = FALSE;
17214 int steps = BUTTON_STEPSIZE(button);
17215 boolean handle_game_buttons =
17216 (game_status == GAME_MODE_PLAYING ||
17217 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
17219 if (!handle_game_buttons)
17224 case GAME_CTRL_ID_STOP:
17225 case GAME_CTRL_ID_PANEL_STOP:
17226 case GAME_CTRL_ID_TOUCH_STOP:
17231 case GAME_CTRL_ID_PAUSE:
17232 case GAME_CTRL_ID_PAUSE2:
17233 case GAME_CTRL_ID_PANEL_PAUSE:
17234 case GAME_CTRL_ID_TOUCH_PAUSE:
17235 if (network.enabled && game_status == GAME_MODE_PLAYING)
17238 SendToServer_ContinuePlaying();
17240 SendToServer_PausePlaying();
17243 TapeTogglePause(TAPE_TOGGLE_MANUAL);
17245 game_undo_executed = FALSE;
17249 case GAME_CTRL_ID_PLAY:
17250 case GAME_CTRL_ID_PANEL_PLAY:
17251 if (game_status == GAME_MODE_MAIN)
17253 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
17255 else if (tape.pausing)
17257 if (network.enabled)
17258 SendToServer_ContinuePlaying();
17260 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
17264 case GAME_CTRL_ID_UNDO:
17265 // Important: When using "save snapshot when collecting an item" mode,
17266 // load last (current) snapshot for first "undo" after pressing "pause"
17267 // (else the last-but-one snapshot would be loaded, because the snapshot
17268 // pointer already points to the last snapshot when pressing "pause",
17269 // which is fine for "every step/move" mode, but not for "every collect")
17270 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
17271 !game_undo_executed)
17274 game_undo_executed = TRUE;
17279 case GAME_CTRL_ID_REDO:
17283 case GAME_CTRL_ID_SAVE:
17287 case GAME_CTRL_ID_LOAD:
17291 case GAME_CTRL_ID_RESTART:
17292 case GAME_CTRL_ID_PANEL_RESTART:
17293 case GAME_CTRL_ID_TOUCH_RESTART:
17298 case SOUND_CTRL_ID_MUSIC:
17299 case SOUND_CTRL_ID_PANEL_MUSIC:
17300 if (setup.sound_music)
17302 setup.sound_music = FALSE;
17306 else if (audio.music_available)
17308 setup.sound = setup.sound_music = TRUE;
17310 SetAudioMode(setup.sound);
17312 if (game_status == GAME_MODE_PLAYING)
17316 RedrawSoundButtonGadget(id);
17320 case SOUND_CTRL_ID_LOOPS:
17321 case SOUND_CTRL_ID_PANEL_LOOPS:
17322 if (setup.sound_loops)
17323 setup.sound_loops = FALSE;
17324 else if (audio.loops_available)
17326 setup.sound = setup.sound_loops = TRUE;
17328 SetAudioMode(setup.sound);
17331 RedrawSoundButtonGadget(id);
17335 case SOUND_CTRL_ID_SIMPLE:
17336 case SOUND_CTRL_ID_PANEL_SIMPLE:
17337 if (setup.sound_simple)
17338 setup.sound_simple = FALSE;
17339 else if (audio.sound_available)
17341 setup.sound = setup.sound_simple = TRUE;
17343 SetAudioMode(setup.sound);
17346 RedrawSoundButtonGadget(id);
17355 static void HandleGameButtons(struct GadgetInfo *gi)
17357 HandleGameButtonsExt(gi->custom_id, gi->event.button);
17360 void HandleSoundButtonKeys(Key key)
17362 if (key == setup.shortcut.sound_simple)
17363 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
17364 else if (key == setup.shortcut.sound_loops)
17365 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
17366 else if (key == setup.shortcut.sound_music)
17367 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);