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 game.envelope_active = FALSE;
3979 // special case: set custom artwork setting to initial value
3980 game.use_masked_elements = game.use_masked_elements_initial;
3982 for (i = 0; i < NUM_BELTS; i++)
3984 game.belt_dir[i] = MV_NONE;
3985 game.belt_dir_nr[i] = 3; // not moving, next moving left
3988 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3989 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3991 #if DEBUG_INIT_PLAYER
3992 DebugPrintPlayerStatus("Player status at level initialization");
3995 SCAN_PLAYFIELD(x, y)
3997 Tile[x][y] = Last[x][y] = level.field[x][y];
3998 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3999 ChangeDelay[x][y] = 0;
4000 ChangePage[x][y] = -1;
4001 CustomValue[x][y] = 0; // initialized in InitField()
4002 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
4004 WasJustMoving[x][y] = 0;
4005 WasJustFalling[x][y] = 0;
4006 CheckCollision[x][y] = 0;
4007 CheckImpact[x][y] = 0;
4009 Pushed[x][y] = FALSE;
4011 ChangeCount[x][y] = 0;
4012 ChangeEvent[x][y] = -1;
4014 ExplodePhase[x][y] = 0;
4015 ExplodeDelay[x][y] = 0;
4016 ExplodeField[x][y] = EX_TYPE_NONE;
4018 RunnerVisit[x][y] = 0;
4019 PlayerVisit[x][y] = 0;
4022 GfxRandom[x][y] = INIT_GFX_RANDOM();
4023 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
4024 GfxElement[x][y] = EL_UNDEFINED;
4025 GfxElementEmpty[x][y] = EL_EMPTY;
4026 GfxAction[x][y] = ACTION_DEFAULT;
4027 GfxDir[x][y] = MV_NONE;
4028 GfxRedraw[x][y] = GFX_REDRAW_NONE;
4031 SCAN_PLAYFIELD(x, y)
4033 InitFieldForEngine(x, y);
4035 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
4037 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
4040 InitField(x, y, TRUE);
4042 ResetGfxAnimation(x, y);
4047 // required if level does not contain any "empty space" element
4048 if (element_info[EL_EMPTY].use_gfx_element)
4049 game.use_masked_elements = TRUE;
4051 for (i = 0; i < MAX_PLAYERS; i++)
4053 struct PlayerInfo *player = &stored_player[i];
4055 // set number of special actions for bored and sleeping animation
4056 player->num_special_action_bored =
4057 get_num_special_action(player->artwork_element,
4058 ACTION_BORING_1, ACTION_BORING_LAST);
4059 player->num_special_action_sleeping =
4060 get_num_special_action(player->artwork_element,
4061 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
4064 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
4065 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
4067 // initialize type of slippery elements
4068 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4070 if (!IS_CUSTOM_ELEMENT(i))
4072 // default: elements slip down either to the left or right randomly
4073 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
4075 // SP style elements prefer to slip down on the left side
4076 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
4077 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4079 // BD style elements prefer to slip down on the left side
4080 if (game.emulation == EMU_BOULDERDASH)
4081 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4085 // initialize explosion and ignition delay
4086 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4088 if (!IS_CUSTOM_ELEMENT(i))
4091 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
4092 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
4093 game.emulation == EMU_SUPAPLEX ? 3 : 2);
4094 int last_phase = (num_phase + 1) * delay;
4095 int half_phase = (num_phase / 2) * delay;
4097 element_info[i].explosion_delay = last_phase - 1;
4098 element_info[i].ignition_delay = half_phase;
4100 if (i == EL_BLACK_ORB)
4101 element_info[i].ignition_delay = 1;
4105 // correct non-moving belts to start moving left
4106 for (i = 0; i < NUM_BELTS; i++)
4107 if (game.belt_dir[i] == MV_NONE)
4108 game.belt_dir_nr[i] = 3; // not moving, next moving left
4110 #if USE_NEW_PLAYER_ASSIGNMENTS
4111 // use preferred player also in local single-player mode
4112 if (!network.enabled && !game.team_mode)
4114 int new_index_nr = setup.network_player_nr;
4116 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
4118 for (i = 0; i < MAX_PLAYERS; i++)
4119 stored_player[i].connected_locally = FALSE;
4121 stored_player[new_index_nr].connected_locally = TRUE;
4125 for (i = 0; i < MAX_PLAYERS; i++)
4127 stored_player[i].connected = FALSE;
4129 // in network game mode, the local player might not be the first player
4130 if (stored_player[i].connected_locally)
4131 local_player = &stored_player[i];
4134 if (!network.enabled)
4135 local_player->connected = TRUE;
4139 for (i = 0; i < MAX_PLAYERS; i++)
4140 stored_player[i].connected = tape.player_participates[i];
4142 else if (network.enabled)
4144 // add team mode players connected over the network (needed for correct
4145 // assignment of player figures from level to locally playing players)
4147 for (i = 0; i < MAX_PLAYERS; i++)
4148 if (stored_player[i].connected_network)
4149 stored_player[i].connected = TRUE;
4151 else if (game.team_mode)
4153 // try to guess locally connected team mode players (needed for correct
4154 // assignment of player figures from level to locally playing players)
4156 for (i = 0; i < MAX_PLAYERS; i++)
4157 if (setup.input[i].use_joystick ||
4158 setup.input[i].key.left != KSYM_UNDEFINED)
4159 stored_player[i].connected = TRUE;
4162 #if DEBUG_INIT_PLAYER
4163 DebugPrintPlayerStatus("Player status after level initialization");
4166 #if DEBUG_INIT_PLAYER
4167 Debug("game:init:player", "Reassigning players ...");
4170 // check if any connected player was not found in playfield
4171 for (i = 0; i < MAX_PLAYERS; i++)
4173 struct PlayerInfo *player = &stored_player[i];
4175 if (player->connected && !player->present)
4177 struct PlayerInfo *field_player = NULL;
4179 #if DEBUG_INIT_PLAYER
4180 Debug("game:init:player",
4181 "- looking for field player for player %d ...", i + 1);
4184 // assign first free player found that is present in the playfield
4186 // first try: look for unmapped playfield player that is not connected
4187 for (j = 0; j < MAX_PLAYERS; j++)
4188 if (field_player == NULL &&
4189 stored_player[j].present &&
4190 !stored_player[j].mapped &&
4191 !stored_player[j].connected)
4192 field_player = &stored_player[j];
4194 // second try: look for *any* unmapped playfield player
4195 for (j = 0; j < MAX_PLAYERS; j++)
4196 if (field_player == NULL &&
4197 stored_player[j].present &&
4198 !stored_player[j].mapped)
4199 field_player = &stored_player[j];
4201 if (field_player != NULL)
4203 int jx = field_player->jx, jy = field_player->jy;
4205 #if DEBUG_INIT_PLAYER
4206 Debug("game:init:player", "- found player %d",
4207 field_player->index_nr + 1);
4210 player->present = FALSE;
4211 player->active = FALSE;
4213 field_player->present = TRUE;
4214 field_player->active = TRUE;
4217 player->initial_element = field_player->initial_element;
4218 player->artwork_element = field_player->artwork_element;
4220 player->block_last_field = field_player->block_last_field;
4221 player->block_delay_adjustment = field_player->block_delay_adjustment;
4224 StorePlayer[jx][jy] = field_player->element_nr;
4226 field_player->jx = field_player->last_jx = jx;
4227 field_player->jy = field_player->last_jy = jy;
4229 if (local_player == player)
4230 local_player = field_player;
4232 map_player_action[field_player->index_nr] = i;
4234 field_player->mapped = TRUE;
4236 #if DEBUG_INIT_PLAYER
4237 Debug("game:init:player", "- map_player_action[%d] == %d",
4238 field_player->index_nr + 1, i + 1);
4243 if (player->connected && player->present)
4244 player->mapped = TRUE;
4247 #if DEBUG_INIT_PLAYER
4248 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4253 // check if any connected player was not found in playfield
4254 for (i = 0; i < MAX_PLAYERS; i++)
4256 struct PlayerInfo *player = &stored_player[i];
4258 if (player->connected && !player->present)
4260 for (j = 0; j < MAX_PLAYERS; j++)
4262 struct PlayerInfo *field_player = &stored_player[j];
4263 int jx = field_player->jx, jy = field_player->jy;
4265 // assign first free player found that is present in the playfield
4266 if (field_player->present && !field_player->connected)
4268 player->present = TRUE;
4269 player->active = TRUE;
4271 field_player->present = FALSE;
4272 field_player->active = FALSE;
4274 player->initial_element = field_player->initial_element;
4275 player->artwork_element = field_player->artwork_element;
4277 player->block_last_field = field_player->block_last_field;
4278 player->block_delay_adjustment = field_player->block_delay_adjustment;
4280 StorePlayer[jx][jy] = player->element_nr;
4282 player->jx = player->last_jx = jx;
4283 player->jy = player->last_jy = jy;
4293 Debug("game:init:player", "local_player->present == %d",
4294 local_player->present);
4297 // set focus to local player for network games, else to all players
4298 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4299 game.centered_player_nr_next = game.centered_player_nr;
4300 game.set_centered_player = FALSE;
4301 game.set_centered_player_wrap = FALSE;
4303 if (network_playing && tape.recording)
4305 // store client dependent player focus when recording network games
4306 tape.centered_player_nr_next = game.centered_player_nr_next;
4307 tape.set_centered_player = TRUE;
4312 // when playing a tape, eliminate all players who do not participate
4314 #if USE_NEW_PLAYER_ASSIGNMENTS
4316 if (!game.team_mode)
4318 for (i = 0; i < MAX_PLAYERS; i++)
4320 if (stored_player[i].active &&
4321 !tape.player_participates[map_player_action[i]])
4323 struct PlayerInfo *player = &stored_player[i];
4324 int jx = player->jx, jy = player->jy;
4326 #if DEBUG_INIT_PLAYER
4327 Debug("game:init:player", "Removing player %d at (%d, %d)",
4331 player->active = FALSE;
4332 StorePlayer[jx][jy] = 0;
4333 Tile[jx][jy] = EL_EMPTY;
4340 for (i = 0; i < MAX_PLAYERS; i++)
4342 if (stored_player[i].active &&
4343 !tape.player_participates[i])
4345 struct PlayerInfo *player = &stored_player[i];
4346 int jx = player->jx, jy = player->jy;
4348 player->active = FALSE;
4349 StorePlayer[jx][jy] = 0;
4350 Tile[jx][jy] = EL_EMPTY;
4355 else if (!network.enabled && !game.team_mode) // && !tape.playing
4357 // when in single player mode, eliminate all but the local player
4359 for (i = 0; i < MAX_PLAYERS; i++)
4361 struct PlayerInfo *player = &stored_player[i];
4363 if (player->active && player != local_player)
4365 int jx = player->jx, jy = player->jy;
4367 player->active = FALSE;
4368 player->present = FALSE;
4370 StorePlayer[jx][jy] = 0;
4371 Tile[jx][jy] = EL_EMPTY;
4376 for (i = 0; i < MAX_PLAYERS; i++)
4377 if (stored_player[i].active)
4378 game.players_still_needed++;
4380 if (level.solved_by_one_player)
4381 game.players_still_needed = 1;
4383 // when recording the game, store which players take part in the game
4386 #if USE_NEW_PLAYER_ASSIGNMENTS
4387 for (i = 0; i < MAX_PLAYERS; i++)
4388 if (stored_player[i].connected)
4389 tape.player_participates[i] = TRUE;
4391 for (i = 0; i < MAX_PLAYERS; i++)
4392 if (stored_player[i].active)
4393 tape.player_participates[i] = TRUE;
4397 #if DEBUG_INIT_PLAYER
4398 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4401 if (BorderElement == EL_EMPTY)
4404 SBX_Right = lev_fieldx - SCR_FIELDX;
4406 SBY_Lower = lev_fieldy - SCR_FIELDY;
4411 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4413 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4416 if (full_lev_fieldx <= SCR_FIELDX)
4417 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4418 if (full_lev_fieldy <= SCR_FIELDY)
4419 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4421 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4423 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4426 // if local player not found, look for custom element that might create
4427 // the player (make some assumptions about the right custom element)
4428 if (!local_player->present)
4430 int start_x = 0, start_y = 0;
4431 int found_rating = 0;
4432 int found_element = EL_UNDEFINED;
4433 int player_nr = local_player->index_nr;
4435 SCAN_PLAYFIELD(x, y)
4437 int element = Tile[x][y];
4442 if (level.use_start_element[player_nr] &&
4443 level.start_element[player_nr] == element &&
4450 found_element = element;
4453 if (!IS_CUSTOM_ELEMENT(element))
4456 if (CAN_CHANGE(element))
4458 for (i = 0; i < element_info[element].num_change_pages; i++)
4460 // check for player created from custom element as single target
4461 content = element_info[element].change_page[i].target_element;
4462 is_player = IS_PLAYER_ELEMENT(content);
4464 if (is_player && (found_rating < 3 ||
4465 (found_rating == 3 && element < found_element)))
4471 found_element = element;
4476 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4478 // check for player created from custom element as explosion content
4479 content = element_info[element].content.e[xx][yy];
4480 is_player = IS_PLAYER_ELEMENT(content);
4482 if (is_player && (found_rating < 2 ||
4483 (found_rating == 2 && element < found_element)))
4485 start_x = x + xx - 1;
4486 start_y = y + yy - 1;
4489 found_element = element;
4492 if (!CAN_CHANGE(element))
4495 for (i = 0; i < element_info[element].num_change_pages; i++)
4497 // check for player created from custom element as extended target
4499 element_info[element].change_page[i].target_content.e[xx][yy];
4501 is_player = IS_PLAYER_ELEMENT(content);
4503 if (is_player && (found_rating < 1 ||
4504 (found_rating == 1 && element < found_element)))
4506 start_x = x + xx - 1;
4507 start_y = y + yy - 1;
4510 found_element = element;
4516 scroll_x = SCROLL_POSITION_X(start_x);
4517 scroll_y = SCROLL_POSITION_Y(start_y);
4521 scroll_x = SCROLL_POSITION_X(local_player->jx);
4522 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4525 if (game.forced_scroll_x != ARG_UNDEFINED_VALUE)
4526 scroll_x = game.forced_scroll_x;
4527 if (game.forced_scroll_y != ARG_UNDEFINED_VALUE)
4528 scroll_y = game.forced_scroll_y;
4530 // !!! FIX THIS (START) !!!
4531 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
4533 InitGameEngine_BD();
4535 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4537 InitGameEngine_EM();
4539 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4541 InitGameEngine_SP();
4543 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4545 InitGameEngine_MM();
4549 DrawLevel(REDRAW_FIELD);
4552 // after drawing the level, correct some elements
4553 if (game.timegate_time_left == 0)
4554 CloseAllOpenTimegates();
4557 // blit playfield from scroll buffer to normal back buffer for fading in
4558 BlitScreenToBitmap(backbuffer);
4559 // !!! FIX THIS (END) !!!
4561 DrawMaskedBorder(fade_mask);
4566 // full screen redraw is required at this point in the following cases:
4567 // - special editor door undrawn when game was started from level editor
4568 // - drawing area (playfield) was changed and has to be removed completely
4569 redraw_mask = REDRAW_ALL;
4573 if (!game.restart_level)
4575 // copy default game door content to main double buffer
4577 // !!! CHECK AGAIN !!!
4578 SetPanelBackground();
4579 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4580 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4583 SetPanelBackground();
4584 SetDrawBackgroundMask(REDRAW_DOOR_1);
4586 UpdateAndDisplayGameControlValues();
4588 if (!game.restart_level)
4594 CreateGameButtons();
4599 // copy actual game door content to door double buffer for OpenDoor()
4600 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4602 OpenDoor(DOOR_OPEN_ALL);
4604 KeyboardAutoRepeatOffUnlessAutoplay();
4606 #if DEBUG_INIT_PLAYER
4607 DebugPrintPlayerStatus("Player status (final)");
4616 if (!game.restart_level && !tape.playing)
4618 LevelStats_incPlayed(level_nr);
4620 SaveLevelSetup_SeriesInfo();
4623 game.restart_level = FALSE;
4624 game.request_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 game.LevelSolved_GameEnd = TRUE;
5167 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5169 // make sure that request dialog to save tape does not open door again
5170 if (!global.use_envelope_request)
5171 CloseDoor(DOOR_CLOSE_1);
5174 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5176 // set unique basename for score tape (also saved in high score table)
5177 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5180 // if no tape is to be saved, close both doors simultaneously
5181 CloseDoor(DOOR_CLOSE_ALL);
5183 if (level_editor_test_game || score_info_tape_play)
5185 SetGameStatus(GAME_MODE_MAIN);
5192 if (!game.LevelSolved_SaveScore)
5194 SetGameStatus(GAME_MODE_MAIN);
5201 if (level_nr == leveldir_current->handicap_level)
5203 leveldir_current->handicap_level++;
5205 SaveLevelSetup_SeriesInfo();
5208 // save score and score tape before potentially erasing tape below
5209 NewHighScore(last_level_nr, tape_saved);
5211 // increment and load next level (if possible and not configured otherwise)
5212 AdvanceToNextLevel();
5214 if (scores.last_added >= 0 && setup.show_scores_after_game)
5216 SetGameStatus(GAME_MODE_SCORES);
5218 DrawHallOfFame(last_level_nr);
5220 else if (scores.continue_playing)
5222 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5226 SetGameStatus(GAME_MODE_MAIN);
5232 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5233 boolean one_score_entry_per_name)
5237 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5240 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5242 struct ScoreEntry *entry = &list->entry[i];
5243 boolean score_is_better = (new_entry->score > entry->score);
5244 boolean score_is_equal = (new_entry->score == entry->score);
5245 boolean time_is_better = (new_entry->time < entry->time);
5246 boolean time_is_equal = (new_entry->time == entry->time);
5247 boolean better_by_score = (score_is_better ||
5248 (score_is_equal && time_is_better));
5249 boolean better_by_time = (time_is_better ||
5250 (time_is_equal && score_is_better));
5251 boolean is_better = (level.rate_time_over_score ? better_by_time :
5253 boolean entry_is_empty = (entry->score == 0 &&
5256 // prevent adding server score entries if also existing in local score file
5257 // (special case: historic score entries have an empty tape basename entry)
5258 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5259 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5261 // add fields from server score entry not stored in local score entry
5262 // (currently, this means setting platform, version and country fields;
5263 // in rare cases, this may also correct an invalid score value, as
5264 // historic scores might have been truncated to 16-bit values locally)
5265 *entry = *new_entry;
5270 if (is_better || entry_is_empty)
5272 // player has made it to the hall of fame
5274 if (i < MAX_SCORE_ENTRIES - 1)
5276 int m = MAX_SCORE_ENTRIES - 1;
5279 if (one_score_entry_per_name)
5281 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5282 if (strEqual(list->entry[l].name, new_entry->name))
5285 if (m == i) // player's new highscore overwrites his old one
5289 for (l = m; l > i; l--)
5290 list->entry[l] = list->entry[l - 1];
5295 *entry = *new_entry;
5299 else if (one_score_entry_per_name &&
5300 strEqual(entry->name, new_entry->name))
5302 // player already in high score list with better score or time
5308 // special case: new score is beyond the last high score list position
5309 return MAX_SCORE_ENTRIES;
5312 void NewHighScore(int level_nr, boolean tape_saved)
5314 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5315 boolean one_per_name = FALSE;
5317 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5318 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5320 new_entry.score = game.score_final;
5321 new_entry.time = game.score_time_final;
5323 LoadScore(level_nr);
5325 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5327 if (scores.last_added >= MAX_SCORE_ENTRIES)
5329 scores.last_added = MAX_SCORE_ENTRIES - 1;
5330 scores.force_last_added = TRUE;
5332 scores.entry[scores.last_added] = new_entry;
5334 // store last added local score entry (before merging server scores)
5335 scores.last_added_local = scores.last_added;
5340 if (scores.last_added < 0)
5343 SaveScore(level_nr);
5345 // store last added local score entry (before merging server scores)
5346 scores.last_added_local = scores.last_added;
5348 if (!game.LevelSolved_SaveTape)
5351 SaveScoreTape(level_nr);
5353 if (setup.ask_for_using_api_server)
5355 setup.use_api_server =
5356 Request("Upload your score and tape to the high score server?", REQ_ASK);
5358 if (!setup.use_api_server)
5359 Request("Not using high score server! Use setup menu to enable again!",
5362 runtime.use_api_server = setup.use_api_server;
5364 // after asking for using API server once, do not ask again
5365 setup.ask_for_using_api_server = FALSE;
5367 SaveSetup_ServerSetup();
5370 SaveServerScore(level_nr, tape_saved);
5373 void MergeServerScore(void)
5375 struct ScoreEntry last_added_entry;
5376 boolean one_per_name = FALSE;
5379 if (scores.last_added >= 0)
5380 last_added_entry = scores.entry[scores.last_added];
5382 for (i = 0; i < server_scores.num_entries; i++)
5384 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5386 if (pos >= 0 && pos <= scores.last_added)
5387 scores.last_added++;
5390 if (scores.last_added >= MAX_SCORE_ENTRIES)
5392 scores.last_added = MAX_SCORE_ENTRIES - 1;
5393 scores.force_last_added = TRUE;
5395 scores.entry[scores.last_added] = last_added_entry;
5399 static int getElementMoveStepsizeExt(int x, int y, int direction)
5401 int element = Tile[x][y];
5402 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5403 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5404 int horiz_move = (dx != 0);
5405 int sign = (horiz_move ? dx : dy);
5406 int step = sign * element_info[element].move_stepsize;
5408 // special values for move stepsize for spring and things on conveyor belt
5411 if (CAN_FALL(element) &&
5412 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5413 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5414 else if (element == EL_SPRING)
5415 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5421 static int getElementMoveStepsize(int x, int y)
5423 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5426 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5428 if (player->GfxAction != action || player->GfxDir != dir)
5430 player->GfxAction = action;
5431 player->GfxDir = dir;
5433 player->StepFrame = 0;
5437 static void ResetGfxFrame(int x, int y)
5439 // profiling showed that "autotest" spends 10~20% of its time in this function
5440 if (DrawingDeactivatedField())
5443 int element = Tile[x][y];
5444 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5446 if (graphic_info[graphic].anim_global_sync)
5447 GfxFrame[x][y] = FrameCounter;
5448 else if (graphic_info[graphic].anim_global_anim_sync)
5449 GfxFrame[x][y] = getGlobalAnimSyncFrame();
5450 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5451 GfxFrame[x][y] = CustomValue[x][y];
5452 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5453 GfxFrame[x][y] = element_info[element].collect_score;
5454 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5455 GfxFrame[x][y] = ChangeDelay[x][y];
5458 static void ResetGfxAnimation(int x, int y)
5460 GfxAction[x][y] = ACTION_DEFAULT;
5461 GfxDir[x][y] = MovDir[x][y];
5464 ResetGfxFrame(x, y);
5467 static void ResetRandomAnimationValue(int x, int y)
5469 GfxRandom[x][y] = INIT_GFX_RANDOM();
5472 static void InitMovingField(int x, int y, int direction)
5474 int element = Tile[x][y];
5475 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5476 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5479 boolean is_moving_before, is_moving_after;
5481 // check if element was/is moving or being moved before/after mode change
5482 is_moving_before = (WasJustMoving[x][y] != 0);
5483 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5485 // reset animation only for moving elements which change direction of moving
5486 // or which just started or stopped moving
5487 // (else CEs with property "can move" / "not moving" are reset each frame)
5488 if (is_moving_before != is_moving_after ||
5489 direction != MovDir[x][y])
5490 ResetGfxAnimation(x, y);
5492 MovDir[x][y] = direction;
5493 GfxDir[x][y] = direction;
5495 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5496 direction == MV_DOWN && CAN_FALL(element) ?
5497 ACTION_FALLING : ACTION_MOVING);
5499 // this is needed for CEs with property "can move" / "not moving"
5501 if (is_moving_after)
5503 if (Tile[newx][newy] == EL_EMPTY)
5504 Tile[newx][newy] = EL_BLOCKED;
5506 MovDir[newx][newy] = MovDir[x][y];
5508 CustomValue[newx][newy] = CustomValue[x][y];
5510 GfxFrame[newx][newy] = GfxFrame[x][y];
5511 GfxRandom[newx][newy] = GfxRandom[x][y];
5512 GfxAction[newx][newy] = GfxAction[x][y];
5513 GfxDir[newx][newy] = GfxDir[x][y];
5517 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5519 int direction = MovDir[x][y];
5520 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5521 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5527 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5529 int direction = MovDir[x][y];
5530 int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0);
5531 int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0);
5533 *comes_from_x = oldx;
5534 *comes_from_y = oldy;
5537 static int MovingOrBlocked2Element(int x, int y)
5539 int element = Tile[x][y];
5541 if (element == EL_BLOCKED)
5545 Blocked2Moving(x, y, &oldx, &oldy);
5547 return Tile[oldx][oldy];
5553 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5555 // like MovingOrBlocked2Element(), but if element is moving
5556 // and (x, y) is the field the moving element is just leaving,
5557 // return EL_BLOCKED instead of the element value
5558 int element = Tile[x][y];
5560 if (IS_MOVING(x, y))
5562 if (element == EL_BLOCKED)
5566 Blocked2Moving(x, y, &oldx, &oldy);
5567 return Tile[oldx][oldy];
5576 static void RemoveField(int x, int y)
5578 Tile[x][y] = EL_EMPTY;
5584 CustomValue[x][y] = 0;
5587 ChangeDelay[x][y] = 0;
5588 ChangePage[x][y] = -1;
5589 Pushed[x][y] = FALSE;
5591 GfxElement[x][y] = EL_UNDEFINED;
5592 GfxAction[x][y] = ACTION_DEFAULT;
5593 GfxDir[x][y] = MV_NONE;
5596 static void RemoveMovingField(int x, int y)
5598 int oldx = x, oldy = y, newx = x, newy = y;
5599 int element = Tile[x][y];
5600 int next_element = EL_UNDEFINED;
5602 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5605 if (IS_MOVING(x, y))
5607 Moving2Blocked(x, y, &newx, &newy);
5609 if (Tile[newx][newy] != EL_BLOCKED)
5611 // element is moving, but target field is not free (blocked), but
5612 // already occupied by something different (example: acid pool);
5613 // in this case, only remove the moving field, but not the target
5615 RemoveField(oldx, oldy);
5617 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5619 TEST_DrawLevelField(oldx, oldy);
5624 else if (element == EL_BLOCKED)
5626 Blocked2Moving(x, y, &oldx, &oldy);
5627 if (!IS_MOVING(oldx, oldy))
5631 if (element == EL_BLOCKED &&
5632 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5633 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5634 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5635 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5636 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5637 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5638 next_element = get_next_element(Tile[oldx][oldy]);
5640 RemoveField(oldx, oldy);
5641 RemoveField(newx, newy);
5643 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5645 if (next_element != EL_UNDEFINED)
5646 Tile[oldx][oldy] = next_element;
5648 TEST_DrawLevelField(oldx, oldy);
5649 TEST_DrawLevelField(newx, newy);
5652 void DrawDynamite(int x, int y)
5654 int sx = SCREENX(x), sy = SCREENY(y);
5655 int graphic = el2img(Tile[x][y]);
5658 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5661 if (IS_WALKABLE_INSIDE(Back[x][y]))
5665 DrawLevelElement(x, y, Back[x][y]);
5666 else if (Store[x][y])
5667 DrawLevelElement(x, y, Store[x][y]);
5668 else if (game.use_masked_elements)
5669 DrawLevelElement(x, y, EL_EMPTY);
5671 frame = getGraphicAnimationFrameXY(graphic, x, y);
5673 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5674 DrawGraphicThruMask(sx, sy, graphic, frame);
5676 DrawGraphic(sx, sy, graphic, frame);
5679 static void CheckDynamite(int x, int y)
5681 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5685 if (MovDelay[x][y] != 0)
5688 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5694 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5699 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5701 boolean num_checked_players = 0;
5704 for (i = 0; i < MAX_PLAYERS; i++)
5706 if (stored_player[i].active)
5708 int sx = stored_player[i].jx;
5709 int sy = stored_player[i].jy;
5711 if (num_checked_players == 0)
5718 *sx1 = MIN(*sx1, sx);
5719 *sy1 = MIN(*sy1, sy);
5720 *sx2 = MAX(*sx2, sx);
5721 *sy2 = MAX(*sy2, sy);
5724 num_checked_players++;
5729 static boolean checkIfAllPlayersFitToScreen_RND(void)
5731 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5733 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5735 return (sx2 - sx1 < SCR_FIELDX &&
5736 sy2 - sy1 < SCR_FIELDY);
5739 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5741 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5743 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5745 *sx = (sx1 + sx2) / 2;
5746 *sy = (sy1 + sy2) / 2;
5749 static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
5750 boolean center_screen, boolean quick_relocation)
5752 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5753 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5754 boolean no_delay = (tape.warp_forward);
5755 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5756 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5757 int new_scroll_x, new_scroll_y;
5759 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5761 // case 1: quick relocation inside visible screen (without scrolling)
5768 if (!level.shifted_relocation || center_screen)
5770 // relocation _with_ centering of screen
5772 new_scroll_x = SCROLL_POSITION_X(x);
5773 new_scroll_y = SCROLL_POSITION_Y(y);
5777 // relocation _without_ centering of screen
5779 // apply distance between old and new player position to scroll position
5780 int shifted_scroll_x = scroll_x + (x - old_x);
5781 int shifted_scroll_y = scroll_y + (y - old_y);
5783 // make sure that shifted scroll position does not scroll beyond screen
5784 new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
5785 new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
5787 // special case for teleporting from one end of the playfield to the other
5788 // (this kludge prevents the destination area to be shifted by half a tile
5789 // against the source destination for even screen width or screen height;
5790 // probably most useful when used with high "game.forced_scroll_delay_value"
5791 // in combination with "game.forced_scroll_x" and "game.forced_scroll_y")
5792 if (quick_relocation)
5794 if (EVEN(SCR_FIELDX))
5796 // relocate (teleport) between left and right border (half or full)
5797 if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1)
5798 new_scroll_x = SBX_Right;
5799 else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right)
5800 new_scroll_x = SBX_Right - 1;
5801 else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1)
5802 new_scroll_x = SBX_Left;
5803 else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left)
5804 new_scroll_x = SBX_Left + 1;
5807 if (EVEN(SCR_FIELDY))
5809 // relocate (teleport) between top and bottom border (half or full)
5810 if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1)
5811 new_scroll_y = SBY_Lower;
5812 else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower)
5813 new_scroll_y = SBY_Lower - 1;
5814 else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1)
5815 new_scroll_y = SBY_Upper;
5816 else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper)
5817 new_scroll_y = SBY_Upper + 1;
5822 if (quick_relocation)
5824 // case 2: quick relocation (redraw without visible scrolling)
5826 scroll_x = new_scroll_x;
5827 scroll_y = new_scroll_y;
5834 // case 3: visible relocation (with scrolling to new position)
5836 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5838 SetVideoFrameDelay(wait_delay_value);
5840 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5842 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5843 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5845 if (dx == 0 && dy == 0) // no scrolling needed at all
5851 // set values for horizontal/vertical screen scrolling (half tile size)
5852 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5853 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5854 int pos_x = dx * TILEX / 2;
5855 int pos_y = dy * TILEY / 2;
5856 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5857 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5859 ScrollLevel(dx, dy);
5862 // scroll in two steps of half tile size to make things smoother
5863 BlitScreenToBitmapExt_RND(window, fx, fy);
5865 // scroll second step to align at full tile size
5866 BlitScreenToBitmap(window);
5872 SetVideoFrameDelay(frame_delay_value_old);
5875 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5877 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5878 int player_nr = GET_PLAYER_NR(el_player);
5879 struct PlayerInfo *player = &stored_player[player_nr];
5880 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5881 boolean no_delay = (tape.warp_forward);
5882 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5883 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5884 int old_jx = player->jx;
5885 int old_jy = player->jy;
5886 int old_element = Tile[old_jx][old_jy];
5887 int element = Tile[jx][jy];
5888 boolean player_relocated = (old_jx != jx || old_jy != jy);
5890 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5891 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5892 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5893 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5894 int leave_side_horiz = move_dir_horiz;
5895 int leave_side_vert = move_dir_vert;
5896 int enter_side = enter_side_horiz | enter_side_vert;
5897 int leave_side = leave_side_horiz | leave_side_vert;
5899 if (player->buried) // do not reanimate dead player
5902 if (!player_relocated) // no need to relocate the player
5905 if (IS_PLAYER(jx, jy)) // player already placed at new position
5907 RemoveField(jx, jy); // temporarily remove newly placed player
5908 DrawLevelField(jx, jy);
5911 if (player->present)
5913 while (player->MovPos)
5915 ScrollPlayer(player, SCROLL_GO_ON);
5916 ScrollScreen(NULL, SCROLL_GO_ON);
5918 AdvanceFrameAndPlayerCounters(player->index_nr);
5922 BackToFront_WithFrameDelay(wait_delay_value);
5925 DrawPlayer(player); // needed here only to cleanup last field
5926 DrawLevelField(player->jx, player->jy); // remove player graphic
5928 player->is_moving = FALSE;
5931 if (IS_CUSTOM_ELEMENT(old_element))
5932 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5934 player->index_bit, leave_side);
5936 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5938 player->index_bit, leave_side);
5940 Tile[jx][jy] = el_player;
5941 InitPlayerField(jx, jy, el_player, TRUE);
5943 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5944 possible that the relocation target field did not contain a player element,
5945 but a walkable element, to which the new player was relocated -- in this
5946 case, restore that (already initialized!) element on the player field */
5947 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5949 Tile[jx][jy] = element; // restore previously existing element
5952 // only visually relocate centered player
5953 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy,
5954 FALSE, level.instant_relocation);
5956 TestIfPlayerTouchesBadThing(jx, jy);
5957 TestIfPlayerTouchesCustomElement(jx, jy);
5959 if (IS_CUSTOM_ELEMENT(element))
5960 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5961 player->index_bit, enter_side);
5963 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5964 player->index_bit, enter_side);
5966 if (player->is_switching)
5968 /* ensure that relocation while still switching an element does not cause
5969 a new element to be treated as also switched directly after relocation
5970 (this is important for teleporter switches that teleport the player to
5971 a place where another teleporter switch is in the same direction, which
5972 would then incorrectly be treated as immediately switched before the
5973 direction key that caused the switch was released) */
5975 player->switch_x += jx - old_jx;
5976 player->switch_y += jy - old_jy;
5980 static void Explode(int ex, int ey, int phase, int mode)
5986 if (game.explosions_delayed)
5988 ExplodeField[ex][ey] = mode;
5992 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5994 int center_element = Tile[ex][ey];
5995 int ce_value = CustomValue[ex][ey];
5996 int ce_score = element_info[center_element].collect_score;
5997 int artwork_element, explosion_element; // set these values later
5999 // remove things displayed in background while burning dynamite
6000 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
6003 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
6005 // put moving element to center field (and let it explode there)
6006 center_element = MovingOrBlocked2Element(ex, ey);
6007 RemoveMovingField(ex, ey);
6008 Tile[ex][ey] = center_element;
6011 // now "center_element" is finally determined -- set related values now
6012 artwork_element = center_element; // for custom player artwork
6013 explosion_element = center_element; // for custom player artwork
6015 if (IS_PLAYER(ex, ey))
6017 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
6019 artwork_element = stored_player[player_nr].artwork_element;
6021 if (level.use_explosion_element[player_nr])
6023 explosion_element = level.explosion_element[player_nr];
6024 artwork_element = explosion_element;
6028 if (mode == EX_TYPE_NORMAL ||
6029 mode == EX_TYPE_CENTER ||
6030 mode == EX_TYPE_CROSS)
6031 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
6033 last_phase = element_info[explosion_element].explosion_delay + 1;
6035 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
6037 int xx = x - ex + 1;
6038 int yy = y - ey + 1;
6041 if (!IN_LEV_FIELD(x, y) ||
6042 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
6043 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
6046 element = Tile[x][y];
6048 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
6050 element = MovingOrBlocked2Element(x, y);
6052 if (!IS_EXPLOSION_PROOF(element))
6053 RemoveMovingField(x, y);
6056 // indestructible elements can only explode in center (but not flames)
6057 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
6058 mode == EX_TYPE_BORDER)) ||
6059 element == EL_FLAMES)
6062 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
6063 behaviour, for example when touching a yamyam that explodes to rocks
6064 with active deadly shield, a rock is created under the player !!! */
6065 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
6067 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
6068 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
6069 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
6071 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
6074 if (IS_ACTIVE_BOMB(element))
6076 // re-activate things under the bomb like gate or penguin
6077 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
6084 // save walkable background elements while explosion on same tile
6085 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
6086 (x != ex || y != ey || mode == EX_TYPE_BORDER))
6087 Back[x][y] = element;
6089 // ignite explodable elements reached by other explosion
6090 if (element == EL_EXPLOSION)
6091 element = Store2[x][y];
6093 if (AmoebaNr[x][y] &&
6094 (element == EL_AMOEBA_FULL ||
6095 element == EL_BD_AMOEBA ||
6096 element == EL_AMOEBA_GROWING))
6098 AmoebaCnt[AmoebaNr[x][y]]--;
6099 AmoebaCnt2[AmoebaNr[x][y]]--;
6104 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
6106 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
6108 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
6110 if (PLAYERINFO(ex, ey)->use_murphy)
6111 Store[x][y] = EL_EMPTY;
6114 // !!! check this case -- currently needed for rnd_rado_negundo_v,
6115 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
6116 else if (IS_PLAYER_ELEMENT(center_element))
6117 Store[x][y] = EL_EMPTY;
6118 else if (center_element == EL_YAMYAM)
6119 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
6120 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
6121 Store[x][y] = element_info[center_element].content.e[xx][yy];
6123 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
6124 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
6125 // otherwise) -- FIX THIS !!!
6126 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
6127 Store[x][y] = element_info[element].content.e[1][1];
6129 else if (!CAN_EXPLODE(element))
6130 Store[x][y] = element_info[element].content.e[1][1];
6133 Store[x][y] = EL_EMPTY;
6135 if (IS_CUSTOM_ELEMENT(center_element))
6136 Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value :
6137 Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score :
6138 Store[x][y] >= EL_PREV_CE_8 &&
6139 Store[x][y] <= EL_NEXT_CE_8 ?
6140 RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) :
6143 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
6144 center_element == EL_AMOEBA_TO_DIAMOND)
6145 Store2[x][y] = element;
6147 Tile[x][y] = EL_EXPLOSION;
6148 GfxElement[x][y] = artwork_element;
6150 ExplodePhase[x][y] = 1;
6151 ExplodeDelay[x][y] = last_phase;
6156 if (center_element == EL_YAMYAM)
6157 game.yamyam_content_nr =
6158 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
6170 GfxFrame[x][y] = 0; // restart explosion animation
6172 last_phase = ExplodeDelay[x][y];
6174 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
6176 // this can happen if the player leaves an explosion just in time
6177 if (GfxElement[x][y] == EL_UNDEFINED)
6178 GfxElement[x][y] = EL_EMPTY;
6180 border_element = Store2[x][y];
6181 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6182 border_element = StorePlayer[x][y];
6184 if (phase == element_info[border_element].ignition_delay ||
6185 phase == last_phase)
6187 boolean border_explosion = FALSE;
6189 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6190 !PLAYER_EXPLOSION_PROTECTED(x, y))
6192 KillPlayerUnlessExplosionProtected(x, y);
6193 border_explosion = TRUE;
6195 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6197 Tile[x][y] = Store2[x][y];
6200 border_explosion = TRUE;
6202 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6204 AmoebaToDiamond(x, y);
6206 border_explosion = TRUE;
6209 // if an element just explodes due to another explosion (chain-reaction),
6210 // do not immediately end the new explosion when it was the last frame of
6211 // the explosion (as it would be done in the following "if"-statement!)
6212 if (border_explosion && phase == last_phase)
6216 // this can happen if the player was just killed by an explosion
6217 if (GfxElement[x][y] == EL_UNDEFINED)
6218 GfxElement[x][y] = EL_EMPTY;
6220 if (phase == last_phase)
6224 element = Tile[x][y] = Store[x][y];
6225 Store[x][y] = Store2[x][y] = 0;
6226 GfxElement[x][y] = EL_UNDEFINED;
6228 // player can escape from explosions and might therefore be still alive
6229 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6230 element <= EL_PLAYER_IS_EXPLODING_4)
6232 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6233 int explosion_element = EL_PLAYER_1 + player_nr;
6234 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6235 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6237 if (level.use_explosion_element[player_nr])
6238 explosion_element = level.explosion_element[player_nr];
6240 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6241 element_info[explosion_element].content.e[xx][yy]);
6244 // restore probably existing indestructible background element
6245 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6246 element = Tile[x][y] = Back[x][y];
6249 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6250 GfxDir[x][y] = MV_NONE;
6251 ChangeDelay[x][y] = 0;
6252 ChangePage[x][y] = -1;
6254 CustomValue[x][y] = 0;
6256 InitField_WithBug2(x, y, FALSE);
6258 TEST_DrawLevelField(x, y);
6260 TestIfElementTouchesCustomElement(x, y);
6262 if (GFX_CRUMBLED(element))
6263 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6265 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6266 StorePlayer[x][y] = 0;
6268 if (IS_PLAYER_ELEMENT(element))
6269 RelocatePlayer(x, y, element);
6271 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6273 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6274 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6277 TEST_DrawLevelFieldCrumbled(x, y);
6279 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6281 DrawLevelElement(x, y, Back[x][y]);
6282 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6284 else if (IS_WALKABLE_UNDER(Back[x][y]))
6286 DrawLevelGraphic(x, y, graphic, frame);
6287 DrawLevelElementThruMask(x, y, Back[x][y]);
6289 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6290 DrawLevelGraphic(x, y, graphic, frame);
6294 static void DynaExplode(int ex, int ey)
6297 int dynabomb_element = Tile[ex][ey];
6298 int dynabomb_size = 1;
6299 boolean dynabomb_xl = FALSE;
6300 struct PlayerInfo *player;
6301 struct XY *xy = xy_topdown;
6303 if (IS_ACTIVE_BOMB(dynabomb_element))
6305 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6306 dynabomb_size = player->dynabomb_size;
6307 dynabomb_xl = player->dynabomb_xl;
6308 player->dynabombs_left++;
6311 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6313 for (i = 0; i < NUM_DIRECTIONS; i++)
6315 for (j = 1; j <= dynabomb_size; j++)
6317 int x = ex + j * xy[i].x;
6318 int y = ey + j * xy[i].y;
6321 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6324 element = Tile[x][y];
6326 // do not restart explosions of fields with active bombs
6327 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6330 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6332 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6333 !IS_DIGGABLE(element) && !dynabomb_xl)
6339 void Bang(int x, int y)
6341 int element = MovingOrBlocked2Element(x, y);
6342 int explosion_type = EX_TYPE_NORMAL;
6344 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6346 struct PlayerInfo *player = PLAYERINFO(x, y);
6348 element = Tile[x][y] = player->initial_element;
6350 if (level.use_explosion_element[player->index_nr])
6352 int explosion_element = level.explosion_element[player->index_nr];
6354 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6355 explosion_type = EX_TYPE_CROSS;
6356 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6357 explosion_type = EX_TYPE_CENTER;
6365 case EL_BD_BUTTERFLY:
6368 case EL_DARK_YAMYAM:
6372 RaiseScoreElement(element);
6375 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6376 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6377 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6378 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6379 case EL_DYNABOMB_INCREASE_NUMBER:
6380 case EL_DYNABOMB_INCREASE_SIZE:
6381 case EL_DYNABOMB_INCREASE_POWER:
6382 explosion_type = EX_TYPE_DYNA;
6385 case EL_DC_LANDMINE:
6386 explosion_type = EX_TYPE_CENTER;
6391 case EL_LAMP_ACTIVE:
6392 case EL_AMOEBA_TO_DIAMOND:
6393 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6394 explosion_type = EX_TYPE_CENTER;
6398 if (element_info[element].explosion_type == EXPLODES_CROSS)
6399 explosion_type = EX_TYPE_CROSS;
6400 else if (element_info[element].explosion_type == EXPLODES_1X1)
6401 explosion_type = EX_TYPE_CENTER;
6405 if (explosion_type == EX_TYPE_DYNA)
6408 Explode(x, y, EX_PHASE_START, explosion_type);
6410 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6413 static void SplashAcid(int x, int y)
6415 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6416 (!IN_LEV_FIELD(x - 1, y - 2) ||
6417 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6418 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6420 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6421 (!IN_LEV_FIELD(x + 1, y - 2) ||
6422 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6423 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6425 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6428 static void InitBeltMovement(void)
6430 static int belt_base_element[4] =
6432 EL_CONVEYOR_BELT_1_LEFT,
6433 EL_CONVEYOR_BELT_2_LEFT,
6434 EL_CONVEYOR_BELT_3_LEFT,
6435 EL_CONVEYOR_BELT_4_LEFT
6437 static int belt_base_active_element[4] =
6439 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6440 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6441 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6442 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6447 // set frame order for belt animation graphic according to belt direction
6448 for (i = 0; i < NUM_BELTS; i++)
6452 for (j = 0; j < NUM_BELT_PARTS; j++)
6454 int element = belt_base_active_element[belt_nr] + j;
6455 int graphic_1 = el2img(element);
6456 int graphic_2 = el2panelimg(element);
6458 if (game.belt_dir[i] == MV_LEFT)
6460 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6461 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6465 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6466 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6471 SCAN_PLAYFIELD(x, y)
6473 int element = Tile[x][y];
6475 for (i = 0; i < NUM_BELTS; i++)
6477 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6479 int e_belt_nr = getBeltNrFromBeltElement(element);
6482 if (e_belt_nr == belt_nr)
6484 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6486 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6493 static void ToggleBeltSwitch(int x, int y)
6495 static int belt_base_element[4] =
6497 EL_CONVEYOR_BELT_1_LEFT,
6498 EL_CONVEYOR_BELT_2_LEFT,
6499 EL_CONVEYOR_BELT_3_LEFT,
6500 EL_CONVEYOR_BELT_4_LEFT
6502 static int belt_base_active_element[4] =
6504 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6505 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6506 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6507 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6509 static int belt_base_switch_element[4] =
6511 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6512 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6513 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6514 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6516 static int belt_move_dir[4] =
6524 int element = Tile[x][y];
6525 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6526 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6527 int belt_dir = belt_move_dir[belt_dir_nr];
6530 if (!IS_BELT_SWITCH(element))
6533 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6534 game.belt_dir[belt_nr] = belt_dir;
6536 if (belt_dir_nr == 3)
6539 // set frame order for belt animation graphic according to belt direction
6540 for (i = 0; i < NUM_BELT_PARTS; i++)
6542 int element = belt_base_active_element[belt_nr] + i;
6543 int graphic_1 = el2img(element);
6544 int graphic_2 = el2panelimg(element);
6546 if (belt_dir == MV_LEFT)
6548 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6549 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6553 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6554 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6558 SCAN_PLAYFIELD(xx, yy)
6560 int element = Tile[xx][yy];
6562 if (IS_BELT_SWITCH(element))
6564 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6566 if (e_belt_nr == belt_nr)
6568 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6569 TEST_DrawLevelField(xx, yy);
6572 else if (IS_BELT(element) && belt_dir != MV_NONE)
6574 int e_belt_nr = getBeltNrFromBeltElement(element);
6576 if (e_belt_nr == belt_nr)
6578 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6580 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6581 TEST_DrawLevelField(xx, yy);
6584 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6586 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6588 if (e_belt_nr == belt_nr)
6590 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6592 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6593 TEST_DrawLevelField(xx, yy);
6599 static void ToggleSwitchgateSwitch(void)
6603 game.switchgate_pos = !game.switchgate_pos;
6605 SCAN_PLAYFIELD(xx, yy)
6607 int element = Tile[xx][yy];
6609 if (element == EL_SWITCHGATE_SWITCH_UP)
6611 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6612 TEST_DrawLevelField(xx, yy);
6614 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6616 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6617 TEST_DrawLevelField(xx, yy);
6619 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6621 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6622 TEST_DrawLevelField(xx, yy);
6624 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6626 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6627 TEST_DrawLevelField(xx, yy);
6629 else if (element == EL_SWITCHGATE_OPEN ||
6630 element == EL_SWITCHGATE_OPENING)
6632 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6634 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6636 else if (element == EL_SWITCHGATE_CLOSED ||
6637 element == EL_SWITCHGATE_CLOSING)
6639 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6641 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6646 static int getInvisibleActiveFromInvisibleElement(int element)
6648 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6649 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6650 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6654 static int getInvisibleFromInvisibleActiveElement(int element)
6656 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6657 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6658 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6662 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6666 SCAN_PLAYFIELD(x, y)
6668 int element = Tile[x][y];
6670 if (element == EL_LIGHT_SWITCH &&
6671 game.light_time_left > 0)
6673 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6674 TEST_DrawLevelField(x, y);
6676 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6677 game.light_time_left == 0)
6679 Tile[x][y] = EL_LIGHT_SWITCH;
6680 TEST_DrawLevelField(x, y);
6682 else if (element == EL_EMC_DRIPPER &&
6683 game.light_time_left > 0)
6685 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6686 TEST_DrawLevelField(x, y);
6688 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6689 game.light_time_left == 0)
6691 Tile[x][y] = EL_EMC_DRIPPER;
6692 TEST_DrawLevelField(x, y);
6694 else if (element == EL_INVISIBLE_STEELWALL ||
6695 element == EL_INVISIBLE_WALL ||
6696 element == EL_INVISIBLE_SAND)
6698 if (game.light_time_left > 0)
6699 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6701 TEST_DrawLevelField(x, y);
6703 // uncrumble neighbour fields, if needed
6704 if (element == EL_INVISIBLE_SAND)
6705 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6707 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6708 element == EL_INVISIBLE_WALL_ACTIVE ||
6709 element == EL_INVISIBLE_SAND_ACTIVE)
6711 if (game.light_time_left == 0)
6712 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6714 TEST_DrawLevelField(x, y);
6716 // re-crumble neighbour fields, if needed
6717 if (element == EL_INVISIBLE_SAND)
6718 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6723 static void RedrawAllInvisibleElementsForLenses(void)
6727 SCAN_PLAYFIELD(x, y)
6729 int element = Tile[x][y];
6731 if (element == EL_EMC_DRIPPER &&
6732 game.lenses_time_left > 0)
6734 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6735 TEST_DrawLevelField(x, y);
6737 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6738 game.lenses_time_left == 0)
6740 Tile[x][y] = EL_EMC_DRIPPER;
6741 TEST_DrawLevelField(x, y);
6743 else if (element == EL_INVISIBLE_STEELWALL ||
6744 element == EL_INVISIBLE_WALL ||
6745 element == EL_INVISIBLE_SAND)
6747 if (game.lenses_time_left > 0)
6748 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6750 TEST_DrawLevelField(x, y);
6752 // uncrumble neighbour fields, if needed
6753 if (element == EL_INVISIBLE_SAND)
6754 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6756 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6757 element == EL_INVISIBLE_WALL_ACTIVE ||
6758 element == EL_INVISIBLE_SAND_ACTIVE)
6760 if (game.lenses_time_left == 0)
6761 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6763 TEST_DrawLevelField(x, y);
6765 // re-crumble neighbour fields, if needed
6766 if (element == EL_INVISIBLE_SAND)
6767 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6772 static void RedrawAllInvisibleElementsForMagnifier(void)
6776 SCAN_PLAYFIELD(x, y)
6778 int element = Tile[x][y];
6780 if (element == EL_EMC_FAKE_GRASS &&
6781 game.magnify_time_left > 0)
6783 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6784 TEST_DrawLevelField(x, y);
6786 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6787 game.magnify_time_left == 0)
6789 Tile[x][y] = EL_EMC_FAKE_GRASS;
6790 TEST_DrawLevelField(x, y);
6792 else if (IS_GATE_GRAY(element) &&
6793 game.magnify_time_left > 0)
6795 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6796 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6797 IS_EM_GATE_GRAY(element) ?
6798 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6799 IS_EMC_GATE_GRAY(element) ?
6800 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6801 IS_DC_GATE_GRAY(element) ?
6802 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6804 TEST_DrawLevelField(x, y);
6806 else if (IS_GATE_GRAY_ACTIVE(element) &&
6807 game.magnify_time_left == 0)
6809 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6810 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6811 IS_EM_GATE_GRAY_ACTIVE(element) ?
6812 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6813 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6814 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6815 IS_DC_GATE_GRAY_ACTIVE(element) ?
6816 EL_DC_GATE_WHITE_GRAY :
6818 TEST_DrawLevelField(x, y);
6823 static void ToggleLightSwitch(int x, int y)
6825 int element = Tile[x][y];
6827 game.light_time_left =
6828 (element == EL_LIGHT_SWITCH ?
6829 level.time_light * FRAMES_PER_SECOND : 0);
6831 RedrawAllLightSwitchesAndInvisibleElements();
6834 static void ActivateTimegateSwitch(int x, int y)
6838 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6840 SCAN_PLAYFIELD(xx, yy)
6842 int element = Tile[xx][yy];
6844 if (element == EL_TIMEGATE_CLOSED ||
6845 element == EL_TIMEGATE_CLOSING)
6847 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6848 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6852 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6854 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6855 TEST_DrawLevelField(xx, yy);
6861 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6862 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6865 static void Impact(int x, int y)
6867 boolean last_line = (y == lev_fieldy - 1);
6868 boolean object_hit = FALSE;
6869 boolean impact = (last_line || object_hit);
6870 int element = Tile[x][y];
6871 int smashed = EL_STEELWALL;
6873 if (!last_line) // check if element below was hit
6875 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6878 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6879 MovDir[x][y + 1] != MV_DOWN ||
6880 MovPos[x][y + 1] <= TILEY / 2));
6882 // do not smash moving elements that left the smashed field in time
6883 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6884 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6887 #if USE_QUICKSAND_IMPACT_BUGFIX
6888 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6890 RemoveMovingField(x, y + 1);
6891 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6892 Tile[x][y + 2] = EL_ROCK;
6893 TEST_DrawLevelField(x, y + 2);
6898 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6900 RemoveMovingField(x, y + 1);
6901 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6902 Tile[x][y + 2] = EL_ROCK;
6903 TEST_DrawLevelField(x, y + 2);
6910 smashed = MovingOrBlocked2Element(x, y + 1);
6912 impact = (last_line || object_hit);
6915 if (!last_line && smashed == EL_ACID) // element falls into acid
6917 SplashAcid(x, y + 1);
6921 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6922 // only reset graphic animation if graphic really changes after impact
6924 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6926 ResetGfxAnimation(x, y);
6927 TEST_DrawLevelField(x, y);
6930 if (impact && CAN_EXPLODE_IMPACT(element))
6935 else if (impact && element == EL_PEARL &&
6936 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6938 ResetGfxAnimation(x, y);
6940 Tile[x][y] = EL_PEARL_BREAKING;
6941 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6944 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6946 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6951 if (impact && element == EL_AMOEBA_DROP)
6953 if (object_hit && IS_PLAYER(x, y + 1))
6954 KillPlayerUnlessEnemyProtected(x, y + 1);
6955 else if (object_hit && smashed == EL_PENGUIN)
6959 Tile[x][y] = EL_AMOEBA_GROWING;
6960 Store[x][y] = EL_AMOEBA_WET;
6962 ResetRandomAnimationValue(x, y);
6967 if (object_hit) // check which object was hit
6969 if ((CAN_PASS_MAGIC_WALL(element) &&
6970 (smashed == EL_MAGIC_WALL ||
6971 smashed == EL_BD_MAGIC_WALL)) ||
6972 (CAN_PASS_DC_MAGIC_WALL(element) &&
6973 smashed == EL_DC_MAGIC_WALL))
6976 int activated_magic_wall =
6977 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6978 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6979 EL_DC_MAGIC_WALL_ACTIVE);
6981 // activate magic wall / mill
6982 SCAN_PLAYFIELD(xx, yy)
6984 if (Tile[xx][yy] == smashed)
6985 Tile[xx][yy] = activated_magic_wall;
6988 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6989 game.magic_wall_active = TRUE;
6991 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6992 SND_MAGIC_WALL_ACTIVATING :
6993 smashed == EL_BD_MAGIC_WALL ?
6994 SND_BD_MAGIC_WALL_ACTIVATING :
6995 SND_DC_MAGIC_WALL_ACTIVATING));
6998 if (IS_PLAYER(x, y + 1))
7000 if (CAN_SMASH_PLAYER(element))
7002 KillPlayerUnlessEnemyProtected(x, y + 1);
7006 else if (smashed == EL_PENGUIN)
7008 if (CAN_SMASH_PLAYER(element))
7014 else if (element == EL_BD_DIAMOND)
7016 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
7022 else if (((element == EL_SP_INFOTRON ||
7023 element == EL_SP_ZONK) &&
7024 (smashed == EL_SP_SNIKSNAK ||
7025 smashed == EL_SP_ELECTRON ||
7026 smashed == EL_SP_DISK_ORANGE)) ||
7027 (element == EL_SP_INFOTRON &&
7028 smashed == EL_SP_DISK_YELLOW))
7033 else if (CAN_SMASH_EVERYTHING(element))
7035 if (IS_CLASSIC_ENEMY(smashed) ||
7036 CAN_EXPLODE_SMASHED(smashed))
7041 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
7043 if (smashed == EL_LAMP ||
7044 smashed == EL_LAMP_ACTIVE)
7049 else if (smashed == EL_NUT)
7051 Tile[x][y + 1] = EL_NUT_BREAKING;
7052 PlayLevelSound(x, y, SND_NUT_BREAKING);
7053 RaiseScoreElement(EL_NUT);
7056 else if (smashed == EL_PEARL)
7058 ResetGfxAnimation(x, y);
7060 Tile[x][y + 1] = EL_PEARL_BREAKING;
7061 PlayLevelSound(x, y, SND_PEARL_BREAKING);
7064 else if (smashed == EL_DIAMOND)
7066 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
7067 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
7070 else if (IS_BELT_SWITCH(smashed))
7072 ToggleBeltSwitch(x, y + 1);
7074 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
7075 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
7076 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
7077 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
7079 ToggleSwitchgateSwitch();
7081 else if (smashed == EL_LIGHT_SWITCH ||
7082 smashed == EL_LIGHT_SWITCH_ACTIVE)
7084 ToggleLightSwitch(x, y + 1);
7088 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7090 CheckElementChangeBySide(x, y + 1, smashed, element,
7091 CE_SWITCHED, CH_SIDE_TOP);
7092 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
7098 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7103 // play sound of magic wall / mill
7105 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7106 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
7107 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
7109 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7110 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
7111 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7112 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
7113 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7114 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
7119 // play sound of object that hits the ground
7120 if (last_line || object_hit)
7121 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
7124 static void TurnRoundExt(int x, int y)
7136 { 0, 0 }, { 0, 0 }, { 0, 0 },
7141 int left, right, back;
7145 { MV_DOWN, MV_UP, MV_RIGHT },
7146 { MV_UP, MV_DOWN, MV_LEFT },
7148 { MV_LEFT, MV_RIGHT, MV_DOWN },
7152 { MV_RIGHT, MV_LEFT, MV_UP }
7155 int element = Tile[x][y];
7156 int move_pattern = element_info[element].move_pattern;
7158 int old_move_dir = MovDir[x][y];
7159 int left_dir = turn[old_move_dir].left;
7160 int right_dir = turn[old_move_dir].right;
7161 int back_dir = turn[old_move_dir].back;
7163 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
7164 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
7165 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
7166 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
7168 int left_x = x + left_dx, left_y = y + left_dy;
7169 int right_x = x + right_dx, right_y = y + right_dy;
7170 int move_x = x + move_dx, move_y = y + move_dy;
7174 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
7176 TestIfBadThingTouchesOtherBadThing(x, y);
7178 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
7179 MovDir[x][y] = right_dir;
7180 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7181 MovDir[x][y] = left_dir;
7183 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7185 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7188 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7190 TestIfBadThingTouchesOtherBadThing(x, y);
7192 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7193 MovDir[x][y] = left_dir;
7194 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7195 MovDir[x][y] = right_dir;
7197 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7199 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7202 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7204 TestIfBadThingTouchesOtherBadThing(x, y);
7206 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7207 MovDir[x][y] = left_dir;
7208 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7209 MovDir[x][y] = right_dir;
7211 if (MovDir[x][y] != old_move_dir)
7214 else if (element == EL_YAMYAM)
7216 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7217 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7219 if (can_turn_left && can_turn_right)
7220 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7221 else if (can_turn_left)
7222 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7223 else if (can_turn_right)
7224 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7226 MovDir[x][y] = back_dir;
7228 MovDelay[x][y] = 16 + 16 * RND(3);
7230 else if (element == EL_DARK_YAMYAM)
7232 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7234 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7237 if (can_turn_left && can_turn_right)
7238 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7239 else if (can_turn_left)
7240 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7241 else if (can_turn_right)
7242 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7244 MovDir[x][y] = back_dir;
7246 MovDelay[x][y] = 16 + 16 * RND(3);
7248 else if (element == EL_PACMAN)
7250 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7251 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7253 if (can_turn_left && can_turn_right)
7254 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7255 else if (can_turn_left)
7256 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7257 else if (can_turn_right)
7258 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7260 MovDir[x][y] = back_dir;
7262 MovDelay[x][y] = 6 + RND(40);
7264 else if (element == EL_PIG)
7266 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7267 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7268 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7269 boolean should_turn_left, should_turn_right, should_move_on;
7271 int rnd = RND(rnd_value);
7273 should_turn_left = (can_turn_left &&
7275 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7276 y + back_dy + left_dy)));
7277 should_turn_right = (can_turn_right &&
7279 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7280 y + back_dy + right_dy)));
7281 should_move_on = (can_move_on &&
7284 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7285 y + move_dy + left_dy) ||
7286 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7287 y + move_dy + right_dy)));
7289 if (should_turn_left || should_turn_right || should_move_on)
7291 if (should_turn_left && should_turn_right && should_move_on)
7292 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7293 rnd < 2 * rnd_value / 3 ? right_dir :
7295 else if (should_turn_left && should_turn_right)
7296 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7297 else if (should_turn_left && should_move_on)
7298 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7299 else if (should_turn_right && should_move_on)
7300 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7301 else if (should_turn_left)
7302 MovDir[x][y] = left_dir;
7303 else if (should_turn_right)
7304 MovDir[x][y] = right_dir;
7305 else if (should_move_on)
7306 MovDir[x][y] = old_move_dir;
7308 else if (can_move_on && rnd > rnd_value / 8)
7309 MovDir[x][y] = old_move_dir;
7310 else if (can_turn_left && can_turn_right)
7311 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7312 else if (can_turn_left && rnd > rnd_value / 8)
7313 MovDir[x][y] = left_dir;
7314 else if (can_turn_right && rnd > rnd_value/8)
7315 MovDir[x][y] = right_dir;
7317 MovDir[x][y] = back_dir;
7319 xx = x + move_xy[MovDir[x][y]].dx;
7320 yy = y + move_xy[MovDir[x][y]].dy;
7322 if (!IN_LEV_FIELD(xx, yy) ||
7323 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7324 MovDir[x][y] = old_move_dir;
7328 else if (element == EL_DRAGON)
7330 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7331 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7332 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7334 int rnd = RND(rnd_value);
7336 if (can_move_on && rnd > rnd_value / 8)
7337 MovDir[x][y] = old_move_dir;
7338 else if (can_turn_left && can_turn_right)
7339 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7340 else if (can_turn_left && rnd > rnd_value / 8)
7341 MovDir[x][y] = left_dir;
7342 else if (can_turn_right && rnd > rnd_value / 8)
7343 MovDir[x][y] = right_dir;
7345 MovDir[x][y] = back_dir;
7347 xx = x + move_xy[MovDir[x][y]].dx;
7348 yy = y + move_xy[MovDir[x][y]].dy;
7350 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7351 MovDir[x][y] = old_move_dir;
7355 else if (element == EL_MOLE)
7357 boolean can_move_on =
7358 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7359 IS_AMOEBOID(Tile[move_x][move_y]) ||
7360 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7363 boolean can_turn_left =
7364 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7365 IS_AMOEBOID(Tile[left_x][left_y])));
7367 boolean can_turn_right =
7368 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7369 IS_AMOEBOID(Tile[right_x][right_y])));
7371 if (can_turn_left && can_turn_right)
7372 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7373 else if (can_turn_left)
7374 MovDir[x][y] = left_dir;
7376 MovDir[x][y] = right_dir;
7379 if (MovDir[x][y] != old_move_dir)
7382 else if (element == EL_BALLOON)
7384 MovDir[x][y] = game.wind_direction;
7387 else if (element == EL_SPRING)
7389 if (MovDir[x][y] & MV_HORIZONTAL)
7391 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7392 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7394 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7395 ResetGfxAnimation(move_x, move_y);
7396 TEST_DrawLevelField(move_x, move_y);
7398 MovDir[x][y] = back_dir;
7400 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7401 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7402 MovDir[x][y] = MV_NONE;
7407 else if (element == EL_ROBOT ||
7408 element == EL_SATELLITE ||
7409 element == EL_PENGUIN ||
7410 element == EL_EMC_ANDROID)
7412 int attr_x = -1, attr_y = -1;
7414 if (game.all_players_gone)
7416 attr_x = game.exit_x;
7417 attr_y = game.exit_y;
7423 for (i = 0; i < MAX_PLAYERS; i++)
7425 struct PlayerInfo *player = &stored_player[i];
7426 int jx = player->jx, jy = player->jy;
7428 if (!player->active)
7432 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7440 if (element == EL_ROBOT &&
7441 game.robot_wheel_x >= 0 &&
7442 game.robot_wheel_y >= 0 &&
7443 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7444 game.engine_version < VERSION_IDENT(3,1,0,0)))
7446 attr_x = game.robot_wheel_x;
7447 attr_y = game.robot_wheel_y;
7450 if (element == EL_PENGUIN)
7453 struct XY *xy = xy_topdown;
7455 for (i = 0; i < NUM_DIRECTIONS; i++)
7457 int ex = x + xy[i].x;
7458 int ey = y + xy[i].y;
7460 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7461 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7462 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7463 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7472 MovDir[x][y] = MV_NONE;
7474 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7475 else if (attr_x > x)
7476 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7478 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7479 else if (attr_y > y)
7480 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7482 if (element == EL_ROBOT)
7486 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7487 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7488 Moving2Blocked(x, y, &newx, &newy);
7490 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7491 MovDelay[x][y] = 8 + 8 * !RND(3);
7493 MovDelay[x][y] = 16;
7495 else if (element == EL_PENGUIN)
7501 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7503 boolean first_horiz = RND(2);
7504 int new_move_dir = MovDir[x][y];
7507 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7508 Moving2Blocked(x, y, &newx, &newy);
7510 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7514 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7515 Moving2Blocked(x, y, &newx, &newy);
7517 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7520 MovDir[x][y] = old_move_dir;
7524 else if (element == EL_SATELLITE)
7530 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7532 boolean first_horiz = RND(2);
7533 int new_move_dir = MovDir[x][y];
7536 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7537 Moving2Blocked(x, y, &newx, &newy);
7539 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7543 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7544 Moving2Blocked(x, y, &newx, &newy);
7546 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7549 MovDir[x][y] = old_move_dir;
7553 else if (element == EL_EMC_ANDROID)
7555 static int check_pos[16] =
7557 -1, // 0 => (invalid)
7560 -1, // 3 => (invalid)
7562 0, // 5 => MV_LEFT | MV_UP
7563 2, // 6 => MV_RIGHT | MV_UP
7564 -1, // 7 => (invalid)
7566 6, // 9 => MV_LEFT | MV_DOWN
7567 4, // 10 => MV_RIGHT | MV_DOWN
7568 -1, // 11 => (invalid)
7569 -1, // 12 => (invalid)
7570 -1, // 13 => (invalid)
7571 -1, // 14 => (invalid)
7572 -1, // 15 => (invalid)
7580 { -1, -1, MV_LEFT | MV_UP },
7582 { +1, -1, MV_RIGHT | MV_UP },
7583 { +1, 0, MV_RIGHT },
7584 { +1, +1, MV_RIGHT | MV_DOWN },
7586 { -1, +1, MV_LEFT | MV_DOWN },
7589 int start_pos, check_order;
7590 boolean can_clone = FALSE;
7593 // check if there is any free field around current position
7594 for (i = 0; i < 8; i++)
7596 int newx = x + check_xy[i].dx;
7597 int newy = y + check_xy[i].dy;
7599 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7607 if (can_clone) // randomly find an element to clone
7611 start_pos = check_pos[RND(8)];
7612 check_order = (RND(2) ? -1 : +1);
7614 for (i = 0; i < 8; i++)
7616 int pos_raw = start_pos + i * check_order;
7617 int pos = (pos_raw + 8) % 8;
7618 int newx = x + check_xy[pos].dx;
7619 int newy = y + check_xy[pos].dy;
7621 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7623 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7624 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7626 Store[x][y] = Tile[newx][newy];
7635 if (can_clone) // randomly find a direction to move
7639 start_pos = check_pos[RND(8)];
7640 check_order = (RND(2) ? -1 : +1);
7642 for (i = 0; i < 8; i++)
7644 int pos_raw = start_pos + i * check_order;
7645 int pos = (pos_raw + 8) % 8;
7646 int newx = x + check_xy[pos].dx;
7647 int newy = y + check_xy[pos].dy;
7648 int new_move_dir = check_xy[pos].dir;
7650 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7652 MovDir[x][y] = new_move_dir;
7653 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7662 if (can_clone) // cloning and moving successful
7665 // cannot clone -- try to move towards player
7667 start_pos = check_pos[MovDir[x][y] & 0x0f];
7668 check_order = (RND(2) ? -1 : +1);
7670 for (i = 0; i < 3; i++)
7672 // first check start_pos, then previous/next or (next/previous) pos
7673 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7674 int pos = (pos_raw + 8) % 8;
7675 int newx = x + check_xy[pos].dx;
7676 int newy = y + check_xy[pos].dy;
7677 int new_move_dir = check_xy[pos].dir;
7679 if (IS_PLAYER(newx, newy))
7682 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7684 MovDir[x][y] = new_move_dir;
7685 MovDelay[x][y] = level.android_move_time * 8 + 1;
7692 else if (move_pattern == MV_TURNING_LEFT ||
7693 move_pattern == MV_TURNING_RIGHT ||
7694 move_pattern == MV_TURNING_LEFT_RIGHT ||
7695 move_pattern == MV_TURNING_RIGHT_LEFT ||
7696 move_pattern == MV_TURNING_RANDOM ||
7697 move_pattern == MV_ALL_DIRECTIONS)
7699 boolean can_turn_left =
7700 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7701 boolean can_turn_right =
7702 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y);
7704 if (element_info[element].move_stepsize == 0) // "not moving"
7707 if (move_pattern == MV_TURNING_LEFT)
7708 MovDir[x][y] = left_dir;
7709 else if (move_pattern == MV_TURNING_RIGHT)
7710 MovDir[x][y] = right_dir;
7711 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7712 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7713 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7714 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7715 else if (move_pattern == MV_TURNING_RANDOM)
7716 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7717 can_turn_right && !can_turn_left ? right_dir :
7718 RND(2) ? left_dir : right_dir);
7719 else if (can_turn_left && can_turn_right)
7720 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7721 else if (can_turn_left)
7722 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7723 else if (can_turn_right)
7724 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7726 MovDir[x][y] = back_dir;
7728 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7730 else if (move_pattern == MV_HORIZONTAL ||
7731 move_pattern == MV_VERTICAL)
7733 if (move_pattern & old_move_dir)
7734 MovDir[x][y] = back_dir;
7735 else if (move_pattern == MV_HORIZONTAL)
7736 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7737 else if (move_pattern == MV_VERTICAL)
7738 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7740 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7742 else if (move_pattern & MV_ANY_DIRECTION)
7744 MovDir[x][y] = move_pattern;
7745 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7747 else if (move_pattern & MV_WIND_DIRECTION)
7749 MovDir[x][y] = game.wind_direction;
7750 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7752 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7754 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7755 MovDir[x][y] = left_dir;
7756 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7757 MovDir[x][y] = right_dir;
7759 if (MovDir[x][y] != old_move_dir)
7760 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7762 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7764 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7765 MovDir[x][y] = right_dir;
7766 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7767 MovDir[x][y] = left_dir;
7769 if (MovDir[x][y] != old_move_dir)
7770 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7772 else if (move_pattern == MV_TOWARDS_PLAYER ||
7773 move_pattern == MV_AWAY_FROM_PLAYER)
7775 int attr_x = -1, attr_y = -1;
7777 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7779 if (game.all_players_gone)
7781 attr_x = game.exit_x;
7782 attr_y = game.exit_y;
7788 for (i = 0; i < MAX_PLAYERS; i++)
7790 struct PlayerInfo *player = &stored_player[i];
7791 int jx = player->jx, jy = player->jy;
7793 if (!player->active)
7797 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7805 MovDir[x][y] = MV_NONE;
7807 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7808 else if (attr_x > x)
7809 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7811 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7812 else if (attr_y > y)
7813 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7815 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7817 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7819 boolean first_horiz = RND(2);
7820 int new_move_dir = MovDir[x][y];
7822 if (element_info[element].move_stepsize == 0) // "not moving"
7824 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7825 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7831 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7832 Moving2Blocked(x, y, &newx, &newy);
7834 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7838 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7839 Moving2Blocked(x, y, &newx, &newy);
7841 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7844 MovDir[x][y] = old_move_dir;
7847 else if (move_pattern == MV_WHEN_PUSHED ||
7848 move_pattern == MV_WHEN_DROPPED)
7850 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7851 MovDir[x][y] = MV_NONE;
7855 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7857 struct XY *test_xy = xy_topdown;
7858 static int test_dir[4] =
7865 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7866 int move_preference = -1000000; // start with very low preference
7867 int new_move_dir = MV_NONE;
7868 int start_test = RND(4);
7871 for (i = 0; i < NUM_DIRECTIONS; i++)
7873 int j = (start_test + i) % 4;
7874 int move_dir = test_dir[j];
7875 int move_dir_preference;
7877 xx = x + test_xy[j].x;
7878 yy = y + test_xy[j].y;
7880 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7881 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7883 new_move_dir = move_dir;
7888 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7891 move_dir_preference = -1 * RunnerVisit[xx][yy];
7892 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7893 move_dir_preference = PlayerVisit[xx][yy];
7895 if (move_dir_preference > move_preference)
7897 // prefer field that has not been visited for the longest time
7898 move_preference = move_dir_preference;
7899 new_move_dir = move_dir;
7901 else if (move_dir_preference == move_preference &&
7902 move_dir == old_move_dir)
7904 // prefer last direction when all directions are preferred equally
7905 move_preference = move_dir_preference;
7906 new_move_dir = move_dir;
7910 MovDir[x][y] = new_move_dir;
7911 if (old_move_dir != new_move_dir)
7912 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7916 static void TurnRound(int x, int y)
7918 int direction = MovDir[x][y];
7922 GfxDir[x][y] = MovDir[x][y];
7924 if (direction != MovDir[x][y])
7928 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7930 ResetGfxFrame(x, y);
7933 static boolean JustBeingPushed(int x, int y)
7937 for (i = 0; i < MAX_PLAYERS; i++)
7939 struct PlayerInfo *player = &stored_player[i];
7941 if (player->active && player->is_pushing && player->MovPos)
7943 int next_jx = player->jx + (player->jx - player->last_jx);
7944 int next_jy = player->jy + (player->jy - player->last_jy);
7946 if (x == next_jx && y == next_jy)
7954 static void StartMoving(int x, int y)
7956 boolean started_moving = FALSE; // some elements can fall _and_ move
7957 int element = Tile[x][y];
7962 if (MovDelay[x][y] == 0)
7963 GfxAction[x][y] = ACTION_DEFAULT;
7965 if (CAN_FALL(element) && y < lev_fieldy - 1)
7967 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7968 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7969 if (JustBeingPushed(x, y))
7972 if (element == EL_QUICKSAND_FULL)
7974 if (IS_FREE(x, y + 1))
7976 InitMovingField(x, y, MV_DOWN);
7977 started_moving = TRUE;
7979 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7980 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7981 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7982 Store[x][y] = EL_ROCK;
7984 Store[x][y] = EL_ROCK;
7987 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7989 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7991 if (!MovDelay[x][y])
7993 MovDelay[x][y] = TILEY + 1;
7995 ResetGfxAnimation(x, y);
7996 ResetGfxAnimation(x, y + 1);
8001 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
8002 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8009 Tile[x][y] = EL_QUICKSAND_EMPTY;
8010 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8011 Store[x][y + 1] = Store[x][y];
8014 PlayLevelSoundAction(x, y, ACTION_FILLING);
8016 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8018 if (!MovDelay[x][y])
8020 MovDelay[x][y] = TILEY + 1;
8022 ResetGfxAnimation(x, y);
8023 ResetGfxAnimation(x, y + 1);
8028 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
8029 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8036 Tile[x][y] = EL_QUICKSAND_EMPTY;
8037 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8038 Store[x][y + 1] = Store[x][y];
8041 PlayLevelSoundAction(x, y, ACTION_FILLING);
8044 else if (element == EL_QUICKSAND_FAST_FULL)
8046 if (IS_FREE(x, y + 1))
8048 InitMovingField(x, y, MV_DOWN);
8049 started_moving = TRUE;
8051 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
8052 #if USE_QUICKSAND_BD_ROCK_BUGFIX
8053 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
8054 Store[x][y] = EL_ROCK;
8056 Store[x][y] = EL_ROCK;
8059 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
8061 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8063 if (!MovDelay[x][y])
8065 MovDelay[x][y] = TILEY + 1;
8067 ResetGfxAnimation(x, y);
8068 ResetGfxAnimation(x, y + 1);
8073 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8074 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8081 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8082 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8083 Store[x][y + 1] = Store[x][y];
8086 PlayLevelSoundAction(x, y, ACTION_FILLING);
8088 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8090 if (!MovDelay[x][y])
8092 MovDelay[x][y] = TILEY + 1;
8094 ResetGfxAnimation(x, y);
8095 ResetGfxAnimation(x, y + 1);
8100 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8101 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8108 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8109 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8110 Store[x][y + 1] = Store[x][y];
8113 PlayLevelSoundAction(x, y, ACTION_FILLING);
8116 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8117 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8119 InitMovingField(x, y, MV_DOWN);
8120 started_moving = TRUE;
8122 Tile[x][y] = EL_QUICKSAND_FILLING;
8123 Store[x][y] = element;
8125 PlayLevelSoundAction(x, y, ACTION_FILLING);
8127 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8128 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8130 InitMovingField(x, y, MV_DOWN);
8131 started_moving = TRUE;
8133 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
8134 Store[x][y] = element;
8136 PlayLevelSoundAction(x, y, ACTION_FILLING);
8138 else if (element == EL_MAGIC_WALL_FULL)
8140 if (IS_FREE(x, y + 1))
8142 InitMovingField(x, y, MV_DOWN);
8143 started_moving = TRUE;
8145 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
8146 Store[x][y] = EL_CHANGED(Store[x][y]);
8148 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
8150 if (!MovDelay[x][y])
8151 MovDelay[x][y] = TILEY / 4 + 1;
8160 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
8161 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
8162 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8166 else if (element == EL_BD_MAGIC_WALL_FULL)
8168 if (IS_FREE(x, y + 1))
8170 InitMovingField(x, y, MV_DOWN);
8171 started_moving = TRUE;
8173 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8174 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8176 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8178 if (!MovDelay[x][y])
8179 MovDelay[x][y] = TILEY / 4 + 1;
8188 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8189 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8190 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8194 else if (element == EL_DC_MAGIC_WALL_FULL)
8196 if (IS_FREE(x, y + 1))
8198 InitMovingField(x, y, MV_DOWN);
8199 started_moving = TRUE;
8201 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8202 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8204 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8206 if (!MovDelay[x][y])
8207 MovDelay[x][y] = TILEY / 4 + 1;
8216 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8217 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8218 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8222 else if ((CAN_PASS_MAGIC_WALL(element) &&
8223 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8224 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8225 (CAN_PASS_DC_MAGIC_WALL(element) &&
8226 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8229 InitMovingField(x, y, MV_DOWN);
8230 started_moving = TRUE;
8233 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8234 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8235 EL_DC_MAGIC_WALL_FILLING);
8236 Store[x][y] = element;
8238 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8240 SplashAcid(x, y + 1);
8242 InitMovingField(x, y, MV_DOWN);
8243 started_moving = TRUE;
8245 Store[x][y] = EL_ACID;
8248 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8249 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8250 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8251 CAN_FALL(element) && WasJustFalling[x][y] &&
8252 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8254 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8255 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8256 (Tile[x][y + 1] == EL_BLOCKED)))
8258 /* this is needed for a special case not covered by calling "Impact()"
8259 from "ContinueMoving()": if an element moves to a tile directly below
8260 another element which was just falling on that tile (which was empty
8261 in the previous frame), the falling element above would just stop
8262 instead of smashing the element below (in previous version, the above
8263 element was just checked for "moving" instead of "falling", resulting
8264 in incorrect smashes caused by horizontal movement of the above
8265 element; also, the case of the player being the element to smash was
8266 simply not covered here... :-/ ) */
8268 CheckCollision[x][y] = 0;
8269 CheckImpact[x][y] = 0;
8273 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8275 if (MovDir[x][y] == MV_NONE)
8277 InitMovingField(x, y, MV_DOWN);
8278 started_moving = TRUE;
8281 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8283 if (WasJustFalling[x][y]) // prevent animation from being restarted
8284 MovDir[x][y] = MV_DOWN;
8286 InitMovingField(x, y, MV_DOWN);
8287 started_moving = TRUE;
8289 else if (element == EL_AMOEBA_DROP)
8291 Tile[x][y] = EL_AMOEBA_GROWING;
8292 Store[x][y] = EL_AMOEBA_WET;
8294 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8295 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8296 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8297 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8299 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8300 (IS_FREE(x - 1, y + 1) ||
8301 Tile[x - 1][y + 1] == EL_ACID));
8302 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8303 (IS_FREE(x + 1, y + 1) ||
8304 Tile[x + 1][y + 1] == EL_ACID));
8305 boolean can_fall_any = (can_fall_left || can_fall_right);
8306 boolean can_fall_both = (can_fall_left && can_fall_right);
8307 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8309 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8311 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8312 can_fall_right = FALSE;
8313 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8314 can_fall_left = FALSE;
8315 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8316 can_fall_right = FALSE;
8317 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8318 can_fall_left = FALSE;
8320 can_fall_any = (can_fall_left || can_fall_right);
8321 can_fall_both = FALSE;
8326 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8327 can_fall_right = FALSE; // slip down on left side
8329 can_fall_left = !(can_fall_right = RND(2));
8331 can_fall_both = FALSE;
8336 // if not determined otherwise, prefer left side for slipping down
8337 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8338 started_moving = TRUE;
8341 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8343 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8344 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8345 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8346 int belt_dir = game.belt_dir[belt_nr];
8348 if ((belt_dir == MV_LEFT && left_is_free) ||
8349 (belt_dir == MV_RIGHT && right_is_free))
8351 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8353 InitMovingField(x, y, belt_dir);
8354 started_moving = TRUE;
8356 Pushed[x][y] = TRUE;
8357 Pushed[nextx][y] = TRUE;
8359 GfxAction[x][y] = ACTION_DEFAULT;
8363 MovDir[x][y] = 0; // if element was moving, stop it
8368 // not "else if" because of elements that can fall and move (EL_SPRING)
8369 if (CAN_MOVE(element) && !started_moving)
8371 int move_pattern = element_info[element].move_pattern;
8374 Moving2Blocked(x, y, &newx, &newy);
8376 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8379 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8380 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8382 WasJustMoving[x][y] = 0;
8383 CheckCollision[x][y] = 0;
8385 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8387 if (Tile[x][y] != element) // element has changed
8391 if (!MovDelay[x][y]) // start new movement phase
8393 // all objects that can change their move direction after each step
8394 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8396 if (element != EL_YAMYAM &&
8397 element != EL_DARK_YAMYAM &&
8398 element != EL_PACMAN &&
8399 !(move_pattern & MV_ANY_DIRECTION) &&
8400 move_pattern != MV_TURNING_LEFT &&
8401 move_pattern != MV_TURNING_RIGHT &&
8402 move_pattern != MV_TURNING_LEFT_RIGHT &&
8403 move_pattern != MV_TURNING_RIGHT_LEFT &&
8404 move_pattern != MV_TURNING_RANDOM)
8408 if (MovDelay[x][y] && (element == EL_BUG ||
8409 element == EL_SPACESHIP ||
8410 element == EL_SP_SNIKSNAK ||
8411 element == EL_SP_ELECTRON ||
8412 element == EL_MOLE))
8413 TEST_DrawLevelField(x, y);
8417 if (MovDelay[x][y]) // wait some time before next movement
8421 if (element == EL_ROBOT ||
8422 element == EL_YAMYAM ||
8423 element == EL_DARK_YAMYAM)
8425 DrawLevelElementAnimationIfNeeded(x, y, element);
8426 PlayLevelSoundAction(x, y, ACTION_WAITING);
8428 else if (element == EL_SP_ELECTRON)
8429 DrawLevelElementAnimationIfNeeded(x, y, element);
8430 else if (element == EL_DRAGON)
8433 int dir = MovDir[x][y];
8434 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8435 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8436 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8437 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8438 dir == MV_UP ? IMG_FLAMES_1_UP :
8439 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8440 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8442 GfxAction[x][y] = ACTION_ATTACKING;
8444 if (IS_PLAYER(x, y))
8445 DrawPlayerField(x, y);
8447 TEST_DrawLevelField(x, y);
8449 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8451 for (i = 1; i <= 3; i++)
8453 int xx = x + i * dx;
8454 int yy = y + i * dy;
8455 int sx = SCREENX(xx);
8456 int sy = SCREENY(yy);
8457 int flame_graphic = graphic + (i - 1);
8459 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8464 int flamed = MovingOrBlocked2Element(xx, yy);
8466 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8469 RemoveMovingField(xx, yy);
8471 ChangeDelay[xx][yy] = 0;
8473 Tile[xx][yy] = EL_FLAMES;
8475 if (IN_SCR_FIELD(sx, sy))
8477 TEST_DrawLevelFieldCrumbled(xx, yy);
8478 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8483 if (Tile[xx][yy] == EL_FLAMES)
8484 Tile[xx][yy] = EL_EMPTY;
8485 TEST_DrawLevelField(xx, yy);
8490 if (MovDelay[x][y]) // element still has to wait some time
8492 PlayLevelSoundAction(x, y, ACTION_WAITING);
8498 // now make next step
8500 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8502 if (DONT_COLLIDE_WITH(element) &&
8503 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8504 !PLAYER_ENEMY_PROTECTED(newx, newy))
8506 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8511 else if (CAN_MOVE_INTO_ACID(element) &&
8512 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8513 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8514 (MovDir[x][y] == MV_DOWN ||
8515 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8517 SplashAcid(newx, newy);
8518 Store[x][y] = EL_ACID;
8520 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8522 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8523 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8524 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8525 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8528 TEST_DrawLevelField(x, y);
8530 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8531 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8532 DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0);
8534 game.friends_still_needed--;
8535 if (!game.friends_still_needed &&
8537 game.all_players_gone)
8542 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8544 if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING)
8545 TEST_DrawLevelField(newx, newy);
8547 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8549 else if (!IS_FREE(newx, newy))
8551 GfxAction[x][y] = ACTION_WAITING;
8553 if (IS_PLAYER(x, y))
8554 DrawPlayerField(x, y);
8556 TEST_DrawLevelField(x, y);
8561 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8563 if (IS_FOOD_PIG(Tile[newx][newy]))
8565 if (IS_MOVING(newx, newy))
8566 RemoveMovingField(newx, newy);
8569 Tile[newx][newy] = EL_EMPTY;
8570 TEST_DrawLevelField(newx, newy);
8573 PlayLevelSound(x, y, SND_PIG_DIGGING);
8575 else if (!IS_FREE(newx, newy))
8577 if (IS_PLAYER(x, y))
8578 DrawPlayerField(x, y);
8580 TEST_DrawLevelField(x, y);
8585 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8587 if (Store[x][y] != EL_EMPTY)
8589 boolean can_clone = FALSE;
8592 // check if element to clone is still there
8593 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8595 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8603 // cannot clone or target field not free anymore -- do not clone
8604 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8605 Store[x][y] = EL_EMPTY;
8608 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8610 if (IS_MV_DIAGONAL(MovDir[x][y]))
8612 int diagonal_move_dir = MovDir[x][y];
8613 int stored = Store[x][y];
8614 int change_delay = 8;
8617 // android is moving diagonally
8619 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8621 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8622 GfxElement[x][y] = EL_EMC_ANDROID;
8623 GfxAction[x][y] = ACTION_SHRINKING;
8624 GfxDir[x][y] = diagonal_move_dir;
8625 ChangeDelay[x][y] = change_delay;
8627 if (Store[x][y] == EL_EMPTY)
8628 Store[x][y] = GfxElementEmpty[x][y];
8630 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8633 DrawLevelGraphicAnimation(x, y, graphic);
8634 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8636 if (Tile[newx][newy] == EL_ACID)
8638 SplashAcid(newx, newy);
8643 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8645 Store[newx][newy] = EL_EMC_ANDROID;
8646 GfxElement[newx][newy] = EL_EMC_ANDROID;
8647 GfxAction[newx][newy] = ACTION_GROWING;
8648 GfxDir[newx][newy] = diagonal_move_dir;
8649 ChangeDelay[newx][newy] = change_delay;
8651 graphic = el_act_dir2img(GfxElement[newx][newy],
8652 GfxAction[newx][newy], GfxDir[newx][newy]);
8654 DrawLevelGraphicAnimation(newx, newy, graphic);
8655 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8661 Tile[newx][newy] = EL_EMPTY;
8662 TEST_DrawLevelField(newx, newy);
8664 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8667 else if (!IS_FREE(newx, newy))
8672 else if (IS_CUSTOM_ELEMENT(element) &&
8673 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8675 if (!DigFieldByCE(newx, newy, element))
8678 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8680 RunnerVisit[x][y] = FrameCounter;
8681 PlayerVisit[x][y] /= 8; // expire player visit path
8684 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8686 if (!IS_FREE(newx, newy))
8688 if (IS_PLAYER(x, y))
8689 DrawPlayerField(x, y);
8691 TEST_DrawLevelField(x, y);
8697 boolean wanna_flame = !RND(10);
8698 int dx = newx - x, dy = newy - y;
8699 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8700 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8701 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8702 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8703 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8704 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8707 IS_CLASSIC_ENEMY(element1) ||
8708 IS_CLASSIC_ENEMY(element2)) &&
8709 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8710 element1 != EL_FLAMES && element2 != EL_FLAMES)
8712 ResetGfxAnimation(x, y);
8713 GfxAction[x][y] = ACTION_ATTACKING;
8715 if (IS_PLAYER(x, y))
8716 DrawPlayerField(x, y);
8718 TEST_DrawLevelField(x, y);
8720 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8722 MovDelay[x][y] = 50;
8724 Tile[newx][newy] = EL_FLAMES;
8725 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8726 Tile[newx1][newy1] = EL_FLAMES;
8727 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8728 Tile[newx2][newy2] = EL_FLAMES;
8734 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8735 Tile[newx][newy] == EL_DIAMOND)
8737 if (IS_MOVING(newx, newy))
8738 RemoveMovingField(newx, newy);
8741 Tile[newx][newy] = EL_EMPTY;
8742 TEST_DrawLevelField(newx, newy);
8745 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8747 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8748 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8750 if (AmoebaNr[newx][newy])
8752 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8753 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8754 Tile[newx][newy] == EL_BD_AMOEBA)
8755 AmoebaCnt[AmoebaNr[newx][newy]]--;
8758 if (IS_MOVING(newx, newy))
8760 RemoveMovingField(newx, newy);
8764 Tile[newx][newy] = EL_EMPTY;
8765 TEST_DrawLevelField(newx, newy);
8768 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8770 else if ((element == EL_PACMAN || element == EL_MOLE)
8771 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8773 if (AmoebaNr[newx][newy])
8775 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8776 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8777 Tile[newx][newy] == EL_BD_AMOEBA)
8778 AmoebaCnt[AmoebaNr[newx][newy]]--;
8781 if (element == EL_MOLE)
8783 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8784 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8786 ResetGfxAnimation(x, y);
8787 GfxAction[x][y] = ACTION_DIGGING;
8788 TEST_DrawLevelField(x, y);
8790 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8792 return; // wait for shrinking amoeba
8794 else // element == EL_PACMAN
8796 Tile[newx][newy] = EL_EMPTY;
8797 TEST_DrawLevelField(newx, newy);
8798 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8801 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8802 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8803 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8805 // wait for shrinking amoeba to completely disappear
8808 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8810 // object was running against a wall
8814 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8815 DrawLevelElementAnimation(x, y, element);
8817 if (DONT_TOUCH(element))
8818 TestIfBadThingTouchesPlayer(x, y);
8823 InitMovingField(x, y, MovDir[x][y]);
8825 PlayLevelSoundAction(x, y, ACTION_MOVING);
8829 ContinueMoving(x, y);
8832 void ContinueMoving(int x, int y)
8834 int element = Tile[x][y];
8835 struct ElementInfo *ei = &element_info[element];
8836 int direction = MovDir[x][y];
8837 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8838 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8839 int newx = x + dx, newy = y + dy;
8840 int stored = Store[x][y];
8841 int stored_new = Store[newx][newy];
8842 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8843 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8844 boolean last_line = (newy == lev_fieldy - 1);
8845 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8847 if (pushed_by_player) // special case: moving object pushed by player
8849 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos));
8851 else if (use_step_delay) // special case: moving object has step delay
8853 if (!MovDelay[x][y])
8854 MovPos[x][y] += getElementMoveStepsize(x, y);
8859 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8863 TEST_DrawLevelField(x, y);
8865 return; // element is still waiting
8868 else // normal case: generically moving object
8870 MovPos[x][y] += getElementMoveStepsize(x, y);
8873 if (ABS(MovPos[x][y]) < TILEX)
8875 TEST_DrawLevelField(x, y);
8877 return; // element is still moving
8880 // element reached destination field
8882 Tile[x][y] = EL_EMPTY;
8883 Tile[newx][newy] = element;
8884 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8886 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8888 element = Tile[newx][newy] = EL_ACID;
8890 else if (element == EL_MOLE)
8892 Tile[x][y] = EL_SAND;
8894 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8896 else if (element == EL_QUICKSAND_FILLING)
8898 element = Tile[newx][newy] = get_next_element(element);
8899 Store[newx][newy] = Store[x][y];
8901 else if (element == EL_QUICKSAND_EMPTYING)
8903 Tile[x][y] = get_next_element(element);
8904 element = Tile[newx][newy] = Store[x][y];
8906 else if (element == EL_QUICKSAND_FAST_FILLING)
8908 element = Tile[newx][newy] = get_next_element(element);
8909 Store[newx][newy] = Store[x][y];
8911 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8913 Tile[x][y] = get_next_element(element);
8914 element = Tile[newx][newy] = Store[x][y];
8916 else if (element == EL_MAGIC_WALL_FILLING)
8918 element = Tile[newx][newy] = get_next_element(element);
8919 if (!game.magic_wall_active)
8920 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8921 Store[newx][newy] = Store[x][y];
8923 else if (element == EL_MAGIC_WALL_EMPTYING)
8925 Tile[x][y] = get_next_element(element);
8926 if (!game.magic_wall_active)
8927 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8928 element = Tile[newx][newy] = Store[x][y];
8930 InitField(newx, newy, FALSE);
8932 else if (element == EL_BD_MAGIC_WALL_FILLING)
8934 element = Tile[newx][newy] = get_next_element(element);
8935 if (!game.magic_wall_active)
8936 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8937 Store[newx][newy] = Store[x][y];
8939 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8941 Tile[x][y] = get_next_element(element);
8942 if (!game.magic_wall_active)
8943 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8944 element = Tile[newx][newy] = Store[x][y];
8946 InitField(newx, newy, FALSE);
8948 else if (element == EL_DC_MAGIC_WALL_FILLING)
8950 element = Tile[newx][newy] = get_next_element(element);
8951 if (!game.magic_wall_active)
8952 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8953 Store[newx][newy] = Store[x][y];
8955 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8957 Tile[x][y] = get_next_element(element);
8958 if (!game.magic_wall_active)
8959 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8960 element = Tile[newx][newy] = Store[x][y];
8962 InitField(newx, newy, FALSE);
8964 else if (element == EL_AMOEBA_DROPPING)
8966 Tile[x][y] = get_next_element(element);
8967 element = Tile[newx][newy] = Store[x][y];
8969 else if (element == EL_SOKOBAN_OBJECT)
8972 Tile[x][y] = Back[x][y];
8974 if (Back[newx][newy])
8975 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8977 Back[x][y] = Back[newx][newy] = 0;
8980 Store[x][y] = EL_EMPTY;
8985 MovDelay[newx][newy] = 0;
8987 if (CAN_CHANGE_OR_HAS_ACTION(element))
8989 // copy element change control values to new field
8990 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8991 ChangePage[newx][newy] = ChangePage[x][y];
8992 ChangeCount[newx][newy] = ChangeCount[x][y];
8993 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8996 CustomValue[newx][newy] = CustomValue[x][y];
8998 ChangeDelay[x][y] = 0;
8999 ChangePage[x][y] = -1;
9000 ChangeCount[x][y] = 0;
9001 ChangeEvent[x][y] = -1;
9003 CustomValue[x][y] = 0;
9005 // copy animation control values to new field
9006 GfxFrame[newx][newy] = GfxFrame[x][y];
9007 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
9008 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
9009 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
9011 Pushed[x][y] = Pushed[newx][newy] = FALSE;
9013 // some elements can leave other elements behind after moving
9014 if (ei->move_leave_element != EL_EMPTY &&
9015 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
9016 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
9018 int move_leave_element = ei->move_leave_element;
9020 // this makes it possible to leave the removed element again
9021 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
9022 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
9024 Tile[x][y] = move_leave_element;
9026 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
9027 MovDir[x][y] = direction;
9029 InitField(x, y, FALSE);
9031 if (GFX_CRUMBLED(Tile[x][y]))
9032 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
9034 if (IS_PLAYER_ELEMENT(move_leave_element))
9035 RelocatePlayer(x, y, move_leave_element);
9038 // do this after checking for left-behind element
9039 ResetGfxAnimation(x, y); // reset animation values for old field
9041 if (!CAN_MOVE(element) ||
9042 (CAN_FALL(element) && direction == MV_DOWN &&
9043 (element == EL_SPRING ||
9044 element_info[element].move_pattern == MV_WHEN_PUSHED ||
9045 element_info[element].move_pattern == MV_WHEN_DROPPED)))
9046 GfxDir[x][y] = MovDir[newx][newy] = 0;
9048 TEST_DrawLevelField(x, y);
9049 TEST_DrawLevelField(newx, newy);
9051 Stop[newx][newy] = TRUE; // ignore this element until the next frame
9053 // prevent pushed element from moving on in pushed direction
9054 if (pushed_by_player && CAN_MOVE(element) &&
9055 element_info[element].move_pattern & MV_ANY_DIRECTION &&
9056 !(element_info[element].move_pattern & direction))
9057 TurnRound(newx, newy);
9059 // prevent elements on conveyor belt from moving on in last direction
9060 if (pushed_by_conveyor && CAN_FALL(element) &&
9061 direction & MV_HORIZONTAL)
9062 MovDir[newx][newy] = 0;
9064 if (!pushed_by_player)
9066 int nextx = newx + dx, nexty = newy + dy;
9067 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
9069 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
9071 if (CAN_FALL(element) && direction == MV_DOWN)
9072 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
9074 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
9075 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
9077 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
9078 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
9081 if (DONT_TOUCH(element)) // object may be nasty to player or others
9083 TestIfBadThingTouchesPlayer(newx, newy);
9084 TestIfBadThingTouchesFriend(newx, newy);
9086 if (!IS_CUSTOM_ELEMENT(element))
9087 TestIfBadThingTouchesOtherBadThing(newx, newy);
9089 else if (element == EL_PENGUIN)
9090 TestIfFriendTouchesBadThing(newx, newy);
9092 if (DONT_GET_HIT_BY(element))
9094 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
9097 // give the player one last chance (one more frame) to move away
9098 if (CAN_FALL(element) && direction == MV_DOWN &&
9099 (last_line || (!IS_FREE(x, newy + 1) &&
9100 (!IS_PLAYER(x, newy + 1) ||
9101 game.engine_version < VERSION_IDENT(3,1,1,0)))))
9104 if (pushed_by_player && !game.use_change_when_pushing_bug)
9106 int push_side = MV_DIR_OPPOSITE(direction);
9107 struct PlayerInfo *player = PLAYERINFO(x, y);
9109 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
9110 player->index_bit, push_side);
9111 CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X,
9112 player->index_bit, push_side);
9115 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
9116 MovDelay[newx][newy] = 1;
9118 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
9120 TestIfElementTouchesCustomElement(x, y); // empty or new element
9121 TestIfElementHitsCustomElement(newx, newy, direction);
9122 TestIfPlayerTouchesCustomElement(newx, newy);
9123 TestIfElementTouchesCustomElement(newx, newy);
9125 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
9126 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
9127 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
9128 MV_DIR_OPPOSITE(direction));
9131 int AmoebaNeighbourNr(int ax, int ay)
9134 int element = Tile[ax][ay];
9136 struct XY *xy = xy_topdown;
9138 for (i = 0; i < NUM_DIRECTIONS; i++)
9140 int x = ax + xy[i].x;
9141 int y = ay + xy[i].y;
9143 if (!IN_LEV_FIELD(x, y))
9146 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
9147 group_nr = AmoebaNr[x][y];
9153 static void AmoebaMerge(int ax, int ay)
9155 int i, x, y, xx, yy;
9156 int new_group_nr = AmoebaNr[ax][ay];
9157 struct XY *xy = xy_topdown;
9159 if (new_group_nr == 0)
9162 for (i = 0; i < NUM_DIRECTIONS; i++)
9167 if (!IN_LEV_FIELD(x, y))
9170 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9171 Tile[x][y] == EL_BD_AMOEBA ||
9172 Tile[x][y] == EL_AMOEBA_DEAD) &&
9173 AmoebaNr[x][y] != new_group_nr)
9175 int old_group_nr = AmoebaNr[x][y];
9177 if (old_group_nr == 0)
9180 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9181 AmoebaCnt[old_group_nr] = 0;
9182 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9183 AmoebaCnt2[old_group_nr] = 0;
9185 SCAN_PLAYFIELD(xx, yy)
9187 if (AmoebaNr[xx][yy] == old_group_nr)
9188 AmoebaNr[xx][yy] = new_group_nr;
9194 void AmoebaToDiamond(int ax, int ay)
9198 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9200 int group_nr = AmoebaNr[ax][ay];
9205 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9206 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9212 SCAN_PLAYFIELD(x, y)
9214 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9217 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9221 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9222 SND_AMOEBA_TURNING_TO_GEM :
9223 SND_AMOEBA_TURNING_TO_ROCK));
9228 struct XY *xy = xy_topdown;
9230 for (i = 0; i < NUM_DIRECTIONS; i++)
9235 if (!IN_LEV_FIELD(x, y))
9238 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9240 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9241 SND_AMOEBA_TURNING_TO_GEM :
9242 SND_AMOEBA_TURNING_TO_ROCK));
9249 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9252 int group_nr = AmoebaNr[ax][ay];
9253 boolean done = FALSE;
9258 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9259 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9265 SCAN_PLAYFIELD(x, y)
9267 if (AmoebaNr[x][y] == group_nr &&
9268 (Tile[x][y] == EL_AMOEBA_DEAD ||
9269 Tile[x][y] == EL_BD_AMOEBA ||
9270 Tile[x][y] == EL_AMOEBA_GROWING))
9273 Tile[x][y] = new_element;
9274 InitField(x, y, FALSE);
9275 TEST_DrawLevelField(x, y);
9281 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9282 SND_BD_AMOEBA_TURNING_TO_ROCK :
9283 SND_BD_AMOEBA_TURNING_TO_GEM));
9286 static void AmoebaGrowing(int x, int y)
9288 static DelayCounter sound_delay = { 0 };
9290 if (!MovDelay[x][y]) // start new growing cycle
9294 if (DelayReached(&sound_delay))
9296 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9297 sound_delay.value = 30;
9301 if (MovDelay[x][y]) // wait some time before growing bigger
9304 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9306 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9307 6 - MovDelay[x][y]);
9309 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9312 if (!MovDelay[x][y])
9314 Tile[x][y] = Store[x][y];
9316 TEST_DrawLevelField(x, y);
9321 static void AmoebaShrinking(int x, int y)
9323 static DelayCounter sound_delay = { 0 };
9325 if (!MovDelay[x][y]) // start new shrinking cycle
9329 if (DelayReached(&sound_delay))
9330 sound_delay.value = 30;
9333 if (MovDelay[x][y]) // wait some time before shrinking
9336 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9338 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9339 6 - MovDelay[x][y]);
9341 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9344 if (!MovDelay[x][y])
9346 Tile[x][y] = EL_EMPTY;
9347 TEST_DrawLevelField(x, y);
9349 // don't let mole enter this field in this cycle;
9350 // (give priority to objects falling to this field from above)
9356 static void AmoebaReproduce(int ax, int ay)
9359 int element = Tile[ax][ay];
9360 int graphic = el2img(element);
9361 int newax = ax, neway = ay;
9362 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9363 struct XY *xy = xy_topdown;
9365 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9367 Tile[ax][ay] = EL_AMOEBA_DEAD;
9368 TEST_DrawLevelField(ax, ay);
9372 if (IS_ANIMATED(graphic))
9373 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9375 if (!MovDelay[ax][ay]) // start making new amoeba field
9376 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9378 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9381 if (MovDelay[ax][ay])
9385 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9388 int x = ax + xy[start].x;
9389 int y = ay + xy[start].y;
9391 if (!IN_LEV_FIELD(x, y))
9394 if (IS_FREE(x, y) ||
9395 CAN_GROW_INTO(Tile[x][y]) ||
9396 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9397 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9403 if (newax == ax && neway == ay)
9406 else // normal or "filled" (BD style) amoeba
9409 boolean waiting_for_player = FALSE;
9411 for (i = 0; i < NUM_DIRECTIONS; i++)
9413 int j = (start + i) % 4;
9414 int x = ax + xy[j].x;
9415 int y = ay + xy[j].y;
9417 if (!IN_LEV_FIELD(x, y))
9420 if (IS_FREE(x, y) ||
9421 CAN_GROW_INTO(Tile[x][y]) ||
9422 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9423 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9429 else if (IS_PLAYER(x, y))
9430 waiting_for_player = TRUE;
9433 if (newax == ax && neway == ay) // amoeba cannot grow
9435 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9437 Tile[ax][ay] = EL_AMOEBA_DEAD;
9438 TEST_DrawLevelField(ax, ay);
9439 AmoebaCnt[AmoebaNr[ax][ay]]--;
9441 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9443 if (element == EL_AMOEBA_FULL)
9444 AmoebaToDiamond(ax, ay);
9445 else if (element == EL_BD_AMOEBA)
9446 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9451 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9453 // amoeba gets larger by growing in some direction
9455 int new_group_nr = AmoebaNr[ax][ay];
9458 if (new_group_nr == 0)
9460 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9462 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9468 AmoebaNr[newax][neway] = new_group_nr;
9469 AmoebaCnt[new_group_nr]++;
9470 AmoebaCnt2[new_group_nr]++;
9472 // if amoeba touches other amoeba(s) after growing, unify them
9473 AmoebaMerge(newax, neway);
9475 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9477 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9483 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9484 (neway == lev_fieldy - 1 && newax != ax))
9486 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9487 Store[newax][neway] = element;
9489 else if (neway == ay || element == EL_EMC_DRIPPER)
9491 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9493 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9497 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9498 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9499 Store[ax][ay] = EL_AMOEBA_DROP;
9500 ContinueMoving(ax, ay);
9504 TEST_DrawLevelField(newax, neway);
9507 static void Life(int ax, int ay)
9511 int element = Tile[ax][ay];
9512 int graphic = el2img(element);
9513 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9515 boolean changed = FALSE;
9517 if (IS_ANIMATED(graphic))
9518 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9523 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9524 MovDelay[ax][ay] = life_time;
9526 if (MovDelay[ax][ay]) // wait some time before next cycle
9529 if (MovDelay[ax][ay])
9533 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9535 int xx = ax + x1, yy = ay + y1;
9536 int old_element = Tile[xx][yy];
9537 int num_neighbours = 0;
9539 if (!IN_LEV_FIELD(xx, yy))
9542 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9544 int x = xx + x2, y = yy + y2;
9546 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9549 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9550 boolean is_neighbour = FALSE;
9552 if (level.use_life_bugs)
9554 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9555 (IS_FREE(x, y) && Stop[x][y]));
9558 (Last[x][y] == element || is_player_cell);
9564 boolean is_free = FALSE;
9566 if (level.use_life_bugs)
9567 is_free = (IS_FREE(xx, yy));
9569 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9571 if (xx == ax && yy == ay) // field in the middle
9573 if (num_neighbours < life_parameter[0] ||
9574 num_neighbours > life_parameter[1])
9576 Tile[xx][yy] = EL_EMPTY;
9577 if (Tile[xx][yy] != old_element)
9578 TEST_DrawLevelField(xx, yy);
9579 Stop[xx][yy] = TRUE;
9583 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9584 { // free border field
9585 if (num_neighbours >= life_parameter[2] &&
9586 num_neighbours <= life_parameter[3])
9588 Tile[xx][yy] = element;
9589 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9590 if (Tile[xx][yy] != old_element)
9591 TEST_DrawLevelField(xx, yy);
9592 Stop[xx][yy] = TRUE;
9599 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9600 SND_GAME_OF_LIFE_GROWING);
9603 static void InitRobotWheel(int x, int y)
9605 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9608 static void RunRobotWheel(int x, int y)
9610 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9613 static void StopRobotWheel(int x, int y)
9615 if (game.robot_wheel_x == x &&
9616 game.robot_wheel_y == y)
9618 game.robot_wheel_x = -1;
9619 game.robot_wheel_y = -1;
9620 game.robot_wheel_active = FALSE;
9624 static void InitTimegateWheel(int x, int y)
9626 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9629 static void RunTimegateWheel(int x, int y)
9631 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9634 static void InitMagicBallDelay(int x, int y)
9636 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9639 static void ActivateMagicBall(int bx, int by)
9643 if (level.ball_random)
9645 int pos_border = RND(8); // select one of the eight border elements
9646 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9647 int xx = pos_content % 3;
9648 int yy = pos_content / 3;
9653 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9654 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9658 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9660 int xx = x - bx + 1;
9661 int yy = y - by + 1;
9663 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9664 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9668 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9671 static void CheckExit(int x, int y)
9673 if (game.gems_still_needed > 0 ||
9674 game.sokoban_fields_still_needed > 0 ||
9675 game.sokoban_objects_still_needed > 0 ||
9676 game.lights_still_needed > 0)
9678 int element = Tile[x][y];
9679 int graphic = el2img(element);
9681 if (IS_ANIMATED(graphic))
9682 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9687 // do not re-open exit door closed after last player
9688 if (game.all_players_gone)
9691 Tile[x][y] = EL_EXIT_OPENING;
9693 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9696 static void CheckExitEM(int x, int y)
9698 if (game.gems_still_needed > 0 ||
9699 game.sokoban_fields_still_needed > 0 ||
9700 game.sokoban_objects_still_needed > 0 ||
9701 game.lights_still_needed > 0)
9703 int element = Tile[x][y];
9704 int graphic = el2img(element);
9706 if (IS_ANIMATED(graphic))
9707 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9712 // do not re-open exit door closed after last player
9713 if (game.all_players_gone)
9716 Tile[x][y] = EL_EM_EXIT_OPENING;
9718 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9721 static void CheckExitSteel(int x, int y)
9723 if (game.gems_still_needed > 0 ||
9724 game.sokoban_fields_still_needed > 0 ||
9725 game.sokoban_objects_still_needed > 0 ||
9726 game.lights_still_needed > 0)
9728 int element = Tile[x][y];
9729 int graphic = el2img(element);
9731 if (IS_ANIMATED(graphic))
9732 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9737 // do not re-open exit door closed after last player
9738 if (game.all_players_gone)
9741 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9743 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9746 static void CheckExitSteelEM(int x, int y)
9748 if (game.gems_still_needed > 0 ||
9749 game.sokoban_fields_still_needed > 0 ||
9750 game.sokoban_objects_still_needed > 0 ||
9751 game.lights_still_needed > 0)
9753 int element = Tile[x][y];
9754 int graphic = el2img(element);
9756 if (IS_ANIMATED(graphic))
9757 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9762 // do not re-open exit door closed after last player
9763 if (game.all_players_gone)
9766 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9768 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9771 static void CheckExitSP(int x, int y)
9773 if (game.gems_still_needed > 0)
9775 int element = Tile[x][y];
9776 int graphic = el2img(element);
9778 if (IS_ANIMATED(graphic))
9779 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9784 // do not re-open exit door closed after last player
9785 if (game.all_players_gone)
9788 Tile[x][y] = EL_SP_EXIT_OPENING;
9790 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9793 static void CloseAllOpenTimegates(void)
9797 SCAN_PLAYFIELD(x, y)
9799 int element = Tile[x][y];
9801 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9803 Tile[x][y] = EL_TIMEGATE_CLOSING;
9805 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9810 static void DrawTwinkleOnField(int x, int y)
9812 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9815 if (Tile[x][y] == EL_BD_DIAMOND)
9818 if (MovDelay[x][y] == 0) // next animation frame
9819 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9821 if (MovDelay[x][y] != 0) // wait some time before next frame
9825 DrawLevelElementAnimation(x, y, Tile[x][y]);
9827 if (MovDelay[x][y] != 0)
9829 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9830 10 - MovDelay[x][y]);
9832 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9837 static void WallGrowing(int x, int y)
9841 if (!MovDelay[x][y]) // next animation frame
9842 MovDelay[x][y] = 3 * delay;
9844 if (MovDelay[x][y]) // wait some time before next frame
9848 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9850 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9851 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9853 DrawLevelGraphic(x, y, graphic, frame);
9856 if (!MovDelay[x][y])
9858 if (MovDir[x][y] == MV_LEFT)
9860 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9861 TEST_DrawLevelField(x - 1, y);
9863 else if (MovDir[x][y] == MV_RIGHT)
9865 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9866 TEST_DrawLevelField(x + 1, y);
9868 else if (MovDir[x][y] == MV_UP)
9870 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9871 TEST_DrawLevelField(x, y - 1);
9875 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9876 TEST_DrawLevelField(x, y + 1);
9879 Tile[x][y] = Store[x][y];
9881 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9882 TEST_DrawLevelField(x, y);
9887 static void CheckWallGrowing(int ax, int ay)
9889 int element = Tile[ax][ay];
9890 int graphic = el2img(element);
9891 boolean free_top = FALSE;
9892 boolean free_bottom = FALSE;
9893 boolean free_left = FALSE;
9894 boolean free_right = FALSE;
9895 boolean stop_top = FALSE;
9896 boolean stop_bottom = FALSE;
9897 boolean stop_left = FALSE;
9898 boolean stop_right = FALSE;
9899 boolean new_wall = FALSE;
9901 boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9902 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9903 element == EL_EXPANDABLE_STEELWALL_ANY);
9905 boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9906 element == EL_EXPANDABLE_WALL_ANY ||
9907 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9908 element == EL_EXPANDABLE_STEELWALL_ANY);
9910 boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9911 element == EL_EXPANDABLE_WALL_ANY ||
9912 element == EL_EXPANDABLE_WALL ||
9913 element == EL_BD_EXPANDABLE_WALL ||
9914 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9915 element == EL_EXPANDABLE_STEELWALL_ANY);
9917 boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9918 element == EL_EXPANDABLE_STEELWALL_VERTICAL);
9920 boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9921 element == EL_EXPANDABLE_WALL ||
9922 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL);
9924 int wall_growing = (is_steelwall ?
9925 EL_EXPANDABLE_STEELWALL_GROWING :
9926 EL_EXPANDABLE_WALL_GROWING);
9928 int gfx_wall_growing_up = (is_steelwall ?
9929 IMG_EXPANDABLE_STEELWALL_GROWING_UP :
9930 IMG_EXPANDABLE_WALL_GROWING_UP);
9931 int gfx_wall_growing_down = (is_steelwall ?
9932 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN :
9933 IMG_EXPANDABLE_WALL_GROWING_DOWN);
9934 int gfx_wall_growing_left = (is_steelwall ?
9935 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT :
9936 IMG_EXPANDABLE_WALL_GROWING_LEFT);
9937 int gfx_wall_growing_right = (is_steelwall ?
9938 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT :
9939 IMG_EXPANDABLE_WALL_GROWING_RIGHT);
9941 if (IS_ANIMATED(graphic))
9942 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9944 if (!MovDelay[ax][ay]) // start building new wall
9945 MovDelay[ax][ay] = 6;
9947 if (MovDelay[ax][ay]) // wait some time before building new wall
9950 if (MovDelay[ax][ay])
9954 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9956 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9958 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9960 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9967 Tile[ax][ay - 1] = wall_growing;
9968 Store[ax][ay - 1] = element;
9969 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9971 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9972 DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0);
9979 Tile[ax][ay + 1] = wall_growing;
9980 Store[ax][ay + 1] = element;
9981 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9983 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9984 DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0);
9990 if (grow_horizontal)
9994 Tile[ax - 1][ay] = wall_growing;
9995 Store[ax - 1][ay] = element;
9996 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9998 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9999 DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0);
10006 Tile[ax + 1][ay] = wall_growing;
10007 Store[ax + 1][ay] = element;
10008 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
10010 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
10011 DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0);
10017 if (element == EL_EXPANDABLE_WALL && (free_left || free_right))
10018 TEST_DrawLevelField(ax, ay);
10020 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
10022 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
10023 stop_bottom = TRUE;
10024 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
10026 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
10029 if (((stop_top && stop_bottom) || stop_horizontal) &&
10030 ((stop_left && stop_right) || stop_vertical))
10031 Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL);
10034 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
10037 static void CheckForDragon(int x, int y)
10040 boolean dragon_found = FALSE;
10041 struct XY *xy = xy_topdown;
10043 for (i = 0; i < NUM_DIRECTIONS; i++)
10045 for (j = 0; j < 4; j++)
10047 int xx = x + j * xy[i].x;
10048 int yy = y + j * xy[i].y;
10050 if (IN_LEV_FIELD(xx, yy) &&
10051 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
10053 if (Tile[xx][yy] == EL_DRAGON)
10054 dragon_found = TRUE;
10063 for (i = 0; i < NUM_DIRECTIONS; i++)
10065 for (j = 0; j < 3; j++)
10067 int xx = x + j * xy[i].x;
10068 int yy = y + j * xy[i].y;
10070 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
10072 Tile[xx][yy] = EL_EMPTY;
10073 TEST_DrawLevelField(xx, yy);
10082 static void InitBuggyBase(int x, int y)
10084 int element = Tile[x][y];
10085 int activating_delay = FRAMES_PER_SECOND / 4;
10087 ChangeDelay[x][y] =
10088 (element == EL_SP_BUGGY_BASE ?
10089 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10090 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10092 element == EL_SP_BUGGY_BASE_ACTIVE ?
10093 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10096 static void WarnBuggyBase(int x, int y)
10099 struct XY *xy = xy_topdown;
10101 for (i = 0; i < NUM_DIRECTIONS; i++)
10103 int xx = x + xy[i].x;
10104 int yy = y + xy[i].y;
10106 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10108 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10115 static void InitTrap(int x, int y)
10117 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10120 static void ActivateTrap(int x, int y)
10122 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10125 static void ChangeActiveTrap(int x, int y)
10127 int graphic = IMG_TRAP_ACTIVE;
10129 // if new animation frame was drawn, correct crumbled sand border
10130 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10131 TEST_DrawLevelFieldCrumbled(x, y);
10134 static int getSpecialActionElement(int element, int number, int base_element)
10136 return (element != EL_EMPTY ? element :
10137 number != -1 ? base_element + number - 1 :
10141 static int getModifiedActionNumber(int value_old, int operator, int operand,
10142 int value_min, int value_max)
10144 int value_new = (operator == CA_MODE_SET ? operand :
10145 operator == CA_MODE_ADD ? value_old + operand :
10146 operator == CA_MODE_SUBTRACT ? value_old - operand :
10147 operator == CA_MODE_MULTIPLY ? value_old * operand :
10148 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10149 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10152 return (value_new < value_min ? value_min :
10153 value_new > value_max ? value_max :
10157 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10159 struct ElementInfo *ei = &element_info[element];
10160 struct ElementChangeInfo *change = &ei->change_page[page];
10161 int target_element = change->target_element;
10162 int action_type = change->action_type;
10163 int action_mode = change->action_mode;
10164 int action_arg = change->action_arg;
10165 int action_element = change->action_element;
10168 if (!change->has_action)
10171 // ---------- determine action paramater values -----------------------------
10173 int level_time_value =
10174 (level.time > 0 ? TimeLeft :
10177 int action_arg_element_raw =
10178 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10179 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10180 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10181 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10182 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10183 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10184 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10186 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10188 int action_arg_direction =
10189 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10190 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10191 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10192 change->actual_trigger_side :
10193 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10194 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10197 int action_arg_number_min =
10198 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10201 int action_arg_number_max =
10202 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10203 action_type == CA_SET_LEVEL_GEMS ? 999 :
10204 action_type == CA_SET_LEVEL_TIME ? 9999 :
10205 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10206 action_type == CA_SET_CE_VALUE ? 9999 :
10207 action_type == CA_SET_CE_SCORE ? 9999 :
10210 int action_arg_number_reset =
10211 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10212 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10213 action_type == CA_SET_LEVEL_TIME ? level.time :
10214 action_type == CA_SET_LEVEL_SCORE ? 0 :
10215 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10216 action_type == CA_SET_CE_SCORE ? 0 :
10219 int action_arg_number =
10220 (action_arg <= CA_ARG_MAX ? action_arg :
10221 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10222 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10223 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10224 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10225 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10226 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10227 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10228 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10229 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10230 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10231 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10232 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10233 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10234 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10235 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10236 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10237 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10238 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10239 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10240 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10241 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10244 int action_arg_number_old =
10245 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10246 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10247 action_type == CA_SET_LEVEL_SCORE ? game.score :
10248 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10249 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10252 int action_arg_number_new =
10253 getModifiedActionNumber(action_arg_number_old,
10254 action_mode, action_arg_number,
10255 action_arg_number_min, action_arg_number_max);
10257 int trigger_player_bits =
10258 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10259 change->actual_trigger_player_bits : change->trigger_player);
10261 int action_arg_player_bits =
10262 (action_arg >= CA_ARG_PLAYER_1 &&
10263 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10264 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10265 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10268 // ---------- execute action -----------------------------------------------
10270 switch (action_type)
10277 // ---------- level actions ----------------------------------------------
10279 case CA_RESTART_LEVEL:
10281 game.restart_level = TRUE;
10286 case CA_SHOW_ENVELOPE:
10288 int element = getSpecialActionElement(action_arg_element,
10289 action_arg_number, EL_ENVELOPE_1);
10291 if (IS_ENVELOPE(element))
10292 local_player->show_envelope = element;
10297 case CA_SET_LEVEL_TIME:
10299 if (level.time > 0) // only modify limited time value
10301 TimeLeft = action_arg_number_new;
10303 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10305 DisplayGameControlValues();
10307 if (!TimeLeft && game.time_limit)
10308 for (i = 0; i < MAX_PLAYERS; i++)
10309 KillPlayer(&stored_player[i]);
10315 case CA_SET_LEVEL_SCORE:
10317 game.score = action_arg_number_new;
10319 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10321 DisplayGameControlValues();
10326 case CA_SET_LEVEL_GEMS:
10328 game.gems_still_needed = action_arg_number_new;
10330 game.snapshot.collected_item = TRUE;
10332 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10334 DisplayGameControlValues();
10339 case CA_SET_LEVEL_WIND:
10341 game.wind_direction = action_arg_direction;
10346 case CA_SET_LEVEL_RANDOM_SEED:
10348 // ensure that setting a new random seed while playing is predictable
10349 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10354 // ---------- player actions ---------------------------------------------
10356 case CA_MOVE_PLAYER:
10357 case CA_MOVE_PLAYER_NEW:
10359 // automatically move to the next field in specified direction
10360 for (i = 0; i < MAX_PLAYERS; i++)
10361 if (trigger_player_bits & (1 << i))
10362 if (action_type == CA_MOVE_PLAYER ||
10363 stored_player[i].MovPos == 0)
10364 stored_player[i].programmed_action = action_arg_direction;
10369 case CA_EXIT_PLAYER:
10371 for (i = 0; i < MAX_PLAYERS; i++)
10372 if (action_arg_player_bits & (1 << i))
10373 ExitPlayer(&stored_player[i]);
10375 if (game.players_still_needed == 0)
10381 case CA_KILL_PLAYER:
10383 for (i = 0; i < MAX_PLAYERS; i++)
10384 if (action_arg_player_bits & (1 << i))
10385 KillPlayer(&stored_player[i]);
10390 case CA_SET_PLAYER_KEYS:
10392 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10393 int element = getSpecialActionElement(action_arg_element,
10394 action_arg_number, EL_KEY_1);
10396 if (IS_KEY(element))
10398 for (i = 0; i < MAX_PLAYERS; i++)
10400 if (trigger_player_bits & (1 << i))
10402 stored_player[i].key[KEY_NR(element)] = key_state;
10404 DrawGameDoorValues();
10412 case CA_SET_PLAYER_SPEED:
10414 for (i = 0; i < MAX_PLAYERS; i++)
10416 if (trigger_player_bits & (1 << i))
10418 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10420 if (action_arg == CA_ARG_SPEED_FASTER &&
10421 stored_player[i].cannot_move)
10423 action_arg_number = STEPSIZE_VERY_SLOW;
10425 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10426 action_arg == CA_ARG_SPEED_FASTER)
10428 action_arg_number = 2;
10429 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10432 else if (action_arg == CA_ARG_NUMBER_RESET)
10434 action_arg_number = level.initial_player_stepsize[i];
10438 getModifiedActionNumber(move_stepsize,
10441 action_arg_number_min,
10442 action_arg_number_max);
10444 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10451 case CA_SET_PLAYER_SHIELD:
10453 for (i = 0; i < MAX_PLAYERS; i++)
10455 if (trigger_player_bits & (1 << i))
10457 if (action_arg == CA_ARG_SHIELD_OFF)
10459 stored_player[i].shield_normal_time_left = 0;
10460 stored_player[i].shield_deadly_time_left = 0;
10462 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10464 stored_player[i].shield_normal_time_left = 999999;
10466 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10468 stored_player[i].shield_normal_time_left = 999999;
10469 stored_player[i].shield_deadly_time_left = 999999;
10477 case CA_SET_PLAYER_GRAVITY:
10479 for (i = 0; i < MAX_PLAYERS; i++)
10481 if (trigger_player_bits & (1 << i))
10483 stored_player[i].gravity =
10484 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10485 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10486 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10487 stored_player[i].gravity);
10494 case CA_SET_PLAYER_ARTWORK:
10496 for (i = 0; i < MAX_PLAYERS; i++)
10498 if (trigger_player_bits & (1 << i))
10500 int artwork_element = action_arg_element;
10502 if (action_arg == CA_ARG_ELEMENT_RESET)
10504 (level.use_artwork_element[i] ? level.artwork_element[i] :
10505 stored_player[i].element_nr);
10507 if (stored_player[i].artwork_element != artwork_element)
10508 stored_player[i].Frame = 0;
10510 stored_player[i].artwork_element = artwork_element;
10512 SetPlayerWaiting(&stored_player[i], FALSE);
10514 // set number of special actions for bored and sleeping animation
10515 stored_player[i].num_special_action_bored =
10516 get_num_special_action(artwork_element,
10517 ACTION_BORING_1, ACTION_BORING_LAST);
10518 stored_player[i].num_special_action_sleeping =
10519 get_num_special_action(artwork_element,
10520 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10527 case CA_SET_PLAYER_INVENTORY:
10529 for (i = 0; i < MAX_PLAYERS; i++)
10531 struct PlayerInfo *player = &stored_player[i];
10534 if (trigger_player_bits & (1 << i))
10536 int inventory_element = action_arg_element;
10538 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10539 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10540 action_arg == CA_ARG_ELEMENT_ACTION)
10542 int element = inventory_element;
10543 int collect_count = element_info[element].collect_count_initial;
10545 if (!IS_CUSTOM_ELEMENT(element))
10548 if (collect_count == 0)
10549 player->inventory_infinite_element = element;
10551 for (k = 0; k < collect_count; k++)
10552 if (player->inventory_size < MAX_INVENTORY_SIZE)
10553 player->inventory_element[player->inventory_size++] =
10556 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10557 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10558 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10560 if (player->inventory_infinite_element != EL_UNDEFINED &&
10561 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10562 action_arg_element_raw))
10563 player->inventory_infinite_element = EL_UNDEFINED;
10565 for (k = 0, j = 0; j < player->inventory_size; j++)
10567 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10568 action_arg_element_raw))
10569 player->inventory_element[k++] = player->inventory_element[j];
10572 player->inventory_size = k;
10574 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10576 if (player->inventory_size > 0)
10578 for (j = 0; j < player->inventory_size - 1; j++)
10579 player->inventory_element[j] = player->inventory_element[j + 1];
10581 player->inventory_size--;
10584 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10586 if (player->inventory_size > 0)
10587 player->inventory_size--;
10589 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10591 player->inventory_infinite_element = EL_UNDEFINED;
10592 player->inventory_size = 0;
10594 else if (action_arg == CA_ARG_INVENTORY_RESET)
10596 player->inventory_infinite_element = EL_UNDEFINED;
10597 player->inventory_size = 0;
10599 if (level.use_initial_inventory[i])
10601 for (j = 0; j < level.initial_inventory_size[i]; j++)
10603 int element = level.initial_inventory_content[i][j];
10604 int collect_count = element_info[element].collect_count_initial;
10606 if (!IS_CUSTOM_ELEMENT(element))
10609 if (collect_count == 0)
10610 player->inventory_infinite_element = element;
10612 for (k = 0; k < collect_count; k++)
10613 if (player->inventory_size < MAX_INVENTORY_SIZE)
10614 player->inventory_element[player->inventory_size++] =
10625 // ---------- CE actions -------------------------------------------------
10627 case CA_SET_CE_VALUE:
10629 int last_ce_value = CustomValue[x][y];
10631 CustomValue[x][y] = action_arg_number_new;
10633 if (CustomValue[x][y] != last_ce_value)
10635 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10636 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10638 if (CustomValue[x][y] == 0)
10640 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10641 ChangeCount[x][y] = 0; // allow at least one more change
10643 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10644 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10651 case CA_SET_CE_SCORE:
10653 int last_ce_score = ei->collect_score;
10655 ei->collect_score = action_arg_number_new;
10657 if (ei->collect_score != last_ce_score)
10659 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10660 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10662 if (ei->collect_score == 0)
10666 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10667 ChangeCount[x][y] = 0; // allow at least one more change
10669 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10670 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10673 This is a very special case that seems to be a mixture between
10674 CheckElementChange() and CheckTriggeredElementChange(): while
10675 the first one only affects single elements that are triggered
10676 directly, the second one affects multiple elements in the playfield
10677 that are triggered indirectly by another element. This is a third
10678 case: Changing the CE score always affects multiple identical CEs,
10679 so every affected CE must be checked, not only the single CE for
10680 which the CE score was changed in the first place (as every instance
10681 of that CE shares the same CE score, and therefore also can change)!
10683 SCAN_PLAYFIELD(xx, yy)
10685 if (Tile[xx][yy] == element)
10686 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10687 CE_SCORE_GETS_ZERO);
10695 case CA_SET_CE_ARTWORK:
10697 int artwork_element = action_arg_element;
10698 boolean reset_frame = FALSE;
10701 if (action_arg == CA_ARG_ELEMENT_RESET)
10702 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10705 if (ei->gfx_element != artwork_element)
10706 reset_frame = TRUE;
10708 ei->gfx_element = artwork_element;
10710 SCAN_PLAYFIELD(xx, yy)
10712 if (Tile[xx][yy] == element)
10716 ResetGfxAnimation(xx, yy);
10717 ResetRandomAnimationValue(xx, yy);
10720 TEST_DrawLevelField(xx, yy);
10727 // ---------- engine actions ---------------------------------------------
10729 case CA_SET_ENGINE_SCAN_MODE:
10731 InitPlayfieldScanMode(action_arg);
10741 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10743 int old_element = Tile[x][y];
10744 int new_element = GetElementFromGroupElement(element);
10745 int previous_move_direction = MovDir[x][y];
10746 int last_ce_value = CustomValue[x][y];
10747 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10748 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10749 boolean add_player_onto_element = (new_element_is_player &&
10750 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10751 IS_WALKABLE(old_element));
10753 if (!add_player_onto_element)
10755 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10756 RemoveMovingField(x, y);
10760 Tile[x][y] = new_element;
10762 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10763 MovDir[x][y] = previous_move_direction;
10765 if (element_info[new_element].use_last_ce_value)
10766 CustomValue[x][y] = last_ce_value;
10768 InitField_WithBug1(x, y, FALSE);
10770 new_element = Tile[x][y]; // element may have changed
10772 ResetGfxAnimation(x, y);
10773 ResetRandomAnimationValue(x, y);
10775 TEST_DrawLevelField(x, y);
10777 if (GFX_CRUMBLED(new_element))
10778 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10780 if (old_element == EL_EXPLOSION)
10782 Store[x][y] = Store2[x][y] = 0;
10784 // check if new element replaces an exploding player, requiring cleanup
10785 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
10786 StorePlayer[x][y] = 0;
10789 // check if element under the player changes from accessible to unaccessible
10790 // (needed for special case of dropping element which then changes)
10791 // (must be checked after creating new element for walkable group elements)
10792 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10793 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10795 KillPlayer(PLAYERINFO(x, y));
10801 // "ChangeCount" not set yet to allow "entered by player" change one time
10802 if (new_element_is_player)
10803 RelocatePlayer(x, y, new_element);
10806 ChangeCount[x][y]++; // count number of changes in the same frame
10808 TestIfBadThingTouchesPlayer(x, y);
10809 TestIfPlayerTouchesCustomElement(x, y);
10810 TestIfElementTouchesCustomElement(x, y);
10813 static void CreateField(int x, int y, int element)
10815 CreateFieldExt(x, y, element, FALSE);
10818 static void CreateElementFromChange(int x, int y, int element)
10820 element = GET_VALID_RUNTIME_ELEMENT(element);
10822 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10824 int old_element = Tile[x][y];
10826 // prevent changed element from moving in same engine frame
10827 // unless both old and new element can either fall or move
10828 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10829 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10833 CreateFieldExt(x, y, element, TRUE);
10836 static boolean ChangeElement(int x, int y, int element, int page)
10838 struct ElementInfo *ei = &element_info[element];
10839 struct ElementChangeInfo *change = &ei->change_page[page];
10840 int ce_value = CustomValue[x][y];
10841 int ce_score = ei->collect_score;
10842 int target_element;
10843 int old_element = Tile[x][y];
10845 // always use default change event to prevent running into a loop
10846 if (ChangeEvent[x][y] == -1)
10847 ChangeEvent[x][y] = CE_DELAY;
10849 if (ChangeEvent[x][y] == CE_DELAY)
10851 // reset actual trigger element, trigger player and action element
10852 change->actual_trigger_element = EL_EMPTY;
10853 change->actual_trigger_player = EL_EMPTY;
10854 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10855 change->actual_trigger_side = CH_SIDE_NONE;
10856 change->actual_trigger_ce_value = 0;
10857 change->actual_trigger_ce_score = 0;
10858 change->actual_trigger_x = -1;
10859 change->actual_trigger_y = -1;
10862 // do not change elements more than a specified maximum number of changes
10863 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10866 ChangeCount[x][y]++; // count number of changes in the same frame
10868 if (ei->has_anim_event)
10869 HandleGlobalAnimEventByElementChange(element, page, x, y,
10870 change->actual_trigger_x,
10871 change->actual_trigger_y);
10873 if (change->explode)
10880 if (change->use_target_content)
10882 boolean complete_replace = TRUE;
10883 boolean can_replace[3][3];
10886 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10889 boolean is_walkable;
10890 boolean is_diggable;
10891 boolean is_collectible;
10892 boolean is_removable;
10893 boolean is_destructible;
10894 int ex = x + xx - 1;
10895 int ey = y + yy - 1;
10896 int content_element = change->target_content.e[xx][yy];
10899 can_replace[xx][yy] = TRUE;
10901 if (ex == x && ey == y) // do not check changing element itself
10904 if (content_element == EL_EMPTY_SPACE)
10906 can_replace[xx][yy] = FALSE; // do not replace border with space
10911 if (!IN_LEV_FIELD(ex, ey))
10913 can_replace[xx][yy] = FALSE;
10914 complete_replace = FALSE;
10921 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10922 e = MovingOrBlocked2Element(ex, ey);
10924 is_empty = (IS_FREE(ex, ey) ||
10925 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10927 is_walkable = (is_empty || IS_WALKABLE(e));
10928 is_diggable = (is_empty || IS_DIGGABLE(e));
10929 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10930 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10931 is_removable = (is_diggable || is_collectible);
10933 can_replace[xx][yy] =
10934 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10935 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10936 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10937 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10938 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10939 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10940 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10942 if (!can_replace[xx][yy])
10943 complete_replace = FALSE;
10946 if (!change->only_if_complete || complete_replace)
10948 boolean something_has_changed = FALSE;
10950 if (change->only_if_complete && change->use_random_replace &&
10951 RND(100) < change->random_percentage)
10954 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10956 int ex = x + xx - 1;
10957 int ey = y + yy - 1;
10958 int content_element;
10960 if (can_replace[xx][yy] && (!change->use_random_replace ||
10961 RND(100) < change->random_percentage))
10963 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10964 RemoveMovingField(ex, ey);
10966 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10968 content_element = change->target_content.e[xx][yy];
10969 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10970 ce_value, ce_score);
10972 CreateElementFromChange(ex, ey, target_element);
10974 something_has_changed = TRUE;
10976 // for symmetry reasons, freeze newly created border elements
10977 if (ex != x || ey != y)
10978 Stop[ex][ey] = TRUE; // no more moving in this frame
10982 if (something_has_changed)
10984 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10985 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10991 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10992 ce_value, ce_score);
10994 if (element == EL_DIAGONAL_GROWING ||
10995 element == EL_DIAGONAL_SHRINKING)
10997 target_element = Store[x][y];
10999 Store[x][y] = EL_EMPTY;
11002 // special case: element changes to player (and may be kept if walkable)
11003 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
11004 CreateElementFromChange(x, y, EL_EMPTY);
11006 CreateElementFromChange(x, y, target_element);
11008 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
11009 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
11012 // this uses direct change before indirect change
11013 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
11018 static void HandleElementChange(int x, int y, int page)
11020 int element = MovingOrBlocked2Element(x, y);
11021 struct ElementInfo *ei = &element_info[element];
11022 struct ElementChangeInfo *change = &ei->change_page[page];
11023 boolean handle_action_before_change = FALSE;
11026 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
11027 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
11029 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
11030 x, y, element, element_info[element].token_name);
11031 Debug("game:playing:HandleElementChange", "This should never happen!");
11035 // this can happen with classic bombs on walkable, changing elements
11036 if (!CAN_CHANGE_OR_HAS_ACTION(element))
11041 if (ChangeDelay[x][y] == 0) // initialize element change
11043 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
11045 if (change->can_change)
11047 // !!! not clear why graphic animation should be reset at all here !!!
11048 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
11049 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
11052 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
11054 When using an animation frame delay of 1 (this only happens with
11055 "sp_zonk.moving.left/right" in the classic graphics), the default
11056 (non-moving) animation shows wrong animation frames (while the
11057 moving animation, like "sp_zonk.moving.left/right", is correct,
11058 so this graphical bug never shows up with the classic graphics).
11059 For an animation with 4 frames, this causes wrong frames 0,0,1,2
11060 be drawn instead of the correct frames 0,1,2,3. This is caused by
11061 "GfxFrame[][]" being reset *twice* (in two successive frames) after
11062 an element change: First when the change delay ("ChangeDelay[][]")
11063 counter has reached zero after decrementing, then a second time in
11064 the next frame (after "GfxFrame[][]" was already incremented) when
11065 "ChangeDelay[][]" is reset to the initial delay value again.
11067 This causes frame 0 to be drawn twice, while the last frame won't
11068 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
11070 As some animations may already be cleverly designed around this bug
11071 (at least the "Snake Bite" snake tail animation does this), it cannot
11072 simply be fixed here without breaking such existing animations.
11073 Unfortunately, it cannot easily be detected if a graphics set was
11074 designed "before" or "after" the bug was fixed. As a workaround,
11075 a new graphics set option "game.graphics_engine_version" was added
11076 to be able to specify the game's major release version for which the
11077 graphics set was designed, which can then be used to decide if the
11078 bugfix should be used (version 4 and above) or not (version 3 or
11079 below, or if no version was specified at all, as with old sets).
11081 (The wrong/fixed animation frames can be tested with the test level set
11082 "test_gfxframe" and level "000", which contains a specially prepared
11083 custom element at level position (x/y) == (11/9) which uses the zonk
11084 animation mentioned above. Using "game.graphics_engine_version: 4"
11085 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
11086 This can also be seen from the debug output for this test element.)
11089 // when a custom element is about to change (for example by change delay),
11090 // do not reset graphic animation when the custom element is moving
11091 if (game.graphics_engine_version < 4 &&
11094 ResetGfxAnimation(x, y);
11095 ResetRandomAnimationValue(x, y);
11098 if (change->pre_change_function)
11099 change->pre_change_function(x, y);
11103 ChangeDelay[x][y]--;
11105 if (ChangeDelay[x][y] != 0) // continue element change
11107 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11109 // also needed if CE can not change, but has CE delay with CE action
11110 if (IS_ANIMATED(graphic))
11111 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11113 if (change->can_change)
11115 if (change->change_function)
11116 change->change_function(x, y);
11119 else // finish element change
11121 if (ChangePage[x][y] != -1) // remember page from delayed change
11123 page = ChangePage[x][y];
11124 ChangePage[x][y] = -1;
11126 change = &ei->change_page[page];
11129 if (IS_MOVING(x, y)) // never change a running system ;-)
11131 ChangeDelay[x][y] = 1; // try change after next move step
11132 ChangePage[x][y] = page; // remember page to use for change
11137 // special case: set new level random seed before changing element
11138 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11139 handle_action_before_change = TRUE;
11141 if (change->has_action && handle_action_before_change)
11142 ExecuteCustomElementAction(x, y, element, page);
11144 if (change->can_change)
11146 if (ChangeElement(x, y, element, page))
11148 if (change->post_change_function)
11149 change->post_change_function(x, y);
11153 if (change->has_action && !handle_action_before_change)
11154 ExecuteCustomElementAction(x, y, element, page);
11158 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11159 int trigger_element,
11161 int trigger_player,
11165 boolean change_done_any = FALSE;
11166 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11169 if (!(trigger_events[trigger_element][trigger_event]))
11172 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11174 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11176 int element = EL_CUSTOM_START + i;
11177 boolean change_done = FALSE;
11180 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11181 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11184 for (p = 0; p < element_info[element].num_change_pages; p++)
11186 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11188 if (change->can_change_or_has_action &&
11189 change->has_event[trigger_event] &&
11190 change->trigger_side & trigger_side &&
11191 change->trigger_player & trigger_player &&
11192 change->trigger_page & trigger_page_bits &&
11193 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11195 change->actual_trigger_element = trigger_element;
11196 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11197 change->actual_trigger_player_bits = trigger_player;
11198 change->actual_trigger_side = trigger_side;
11199 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11200 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11201 change->actual_trigger_x = trigger_x;
11202 change->actual_trigger_y = trigger_y;
11204 if ((change->can_change && !change_done) || change->has_action)
11208 SCAN_PLAYFIELD(x, y)
11210 if (Tile[x][y] == element)
11212 if (change->can_change && !change_done)
11214 // if element already changed in this frame, not only prevent
11215 // another element change (checked in ChangeElement()), but
11216 // also prevent additional element actions for this element
11218 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11219 !level.use_action_after_change_bug)
11222 ChangeDelay[x][y] = 1;
11223 ChangeEvent[x][y] = trigger_event;
11225 HandleElementChange(x, y, p);
11227 else if (change->has_action)
11229 // if element already changed in this frame, not only prevent
11230 // another element change (checked in ChangeElement()), but
11231 // also prevent additional element actions for this element
11233 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11234 !level.use_action_after_change_bug)
11237 ExecuteCustomElementAction(x, y, element, p);
11238 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11243 if (change->can_change)
11245 change_done = TRUE;
11246 change_done_any = TRUE;
11253 RECURSION_LOOP_DETECTION_END();
11255 return change_done_any;
11258 static boolean CheckElementChangeExt(int x, int y,
11260 int trigger_element,
11262 int trigger_player,
11265 boolean change_done = FALSE;
11268 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11269 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11272 if (Tile[x][y] == EL_BLOCKED)
11274 Blocked2Moving(x, y, &x, &y);
11275 element = Tile[x][y];
11278 // check if element has already changed or is about to change after moving
11279 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11280 Tile[x][y] != element) ||
11282 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11283 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11284 ChangePage[x][y] != -1)))
11287 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11289 for (p = 0; p < element_info[element].num_change_pages; p++)
11291 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11293 /* check trigger element for all events where the element that is checked
11294 for changing interacts with a directly adjacent element -- this is
11295 different to element changes that affect other elements to change on the
11296 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11297 boolean check_trigger_element =
11298 (trigger_event == CE_NEXT_TO_X ||
11299 trigger_event == CE_TOUCHING_X ||
11300 trigger_event == CE_HITTING_X ||
11301 trigger_event == CE_HIT_BY_X ||
11302 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11304 if (change->can_change_or_has_action &&
11305 change->has_event[trigger_event] &&
11306 change->trigger_side & trigger_side &&
11307 change->trigger_player & trigger_player &&
11308 (!check_trigger_element ||
11309 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11311 change->actual_trigger_element = trigger_element;
11312 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11313 change->actual_trigger_player_bits = trigger_player;
11314 change->actual_trigger_side = trigger_side;
11315 change->actual_trigger_ce_value = CustomValue[x][y];
11316 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11317 change->actual_trigger_x = x;
11318 change->actual_trigger_y = y;
11320 // special case: trigger element not at (x,y) position for some events
11321 if (check_trigger_element)
11333 { 0, 0 }, { 0, 0 }, { 0, 0 },
11337 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11338 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11340 change->actual_trigger_ce_value = CustomValue[xx][yy];
11341 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11342 change->actual_trigger_x = xx;
11343 change->actual_trigger_y = yy;
11346 if (change->can_change && !change_done)
11348 ChangeDelay[x][y] = 1;
11349 ChangeEvent[x][y] = trigger_event;
11351 HandleElementChange(x, y, p);
11353 change_done = TRUE;
11355 else if (change->has_action)
11357 ExecuteCustomElementAction(x, y, element, p);
11358 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11363 RECURSION_LOOP_DETECTION_END();
11365 return change_done;
11368 static void PlayPlayerSound(struct PlayerInfo *player)
11370 int jx = player->jx, jy = player->jy;
11371 int sound_element = player->artwork_element;
11372 int last_action = player->last_action_waiting;
11373 int action = player->action_waiting;
11375 if (player->is_waiting)
11377 if (action != last_action)
11378 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11380 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11384 if (action != last_action)
11385 StopSound(element_info[sound_element].sound[last_action]);
11387 if (last_action == ACTION_SLEEPING)
11388 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11392 static void PlayAllPlayersSound(void)
11396 for (i = 0; i < MAX_PLAYERS; i++)
11397 if (stored_player[i].active)
11398 PlayPlayerSound(&stored_player[i]);
11401 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11403 boolean last_waiting = player->is_waiting;
11404 int move_dir = player->MovDir;
11406 player->dir_waiting = move_dir;
11407 player->last_action_waiting = player->action_waiting;
11411 if (!last_waiting) // not waiting -> waiting
11413 player->is_waiting = TRUE;
11415 player->frame_counter_bored =
11417 game.player_boring_delay_fixed +
11418 GetSimpleRandom(game.player_boring_delay_random);
11419 player->frame_counter_sleeping =
11421 game.player_sleeping_delay_fixed +
11422 GetSimpleRandom(game.player_sleeping_delay_random);
11424 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11427 if (game.player_sleeping_delay_fixed +
11428 game.player_sleeping_delay_random > 0 &&
11429 player->anim_delay_counter == 0 &&
11430 player->post_delay_counter == 0 &&
11431 FrameCounter >= player->frame_counter_sleeping)
11432 player->is_sleeping = TRUE;
11433 else if (game.player_boring_delay_fixed +
11434 game.player_boring_delay_random > 0 &&
11435 FrameCounter >= player->frame_counter_bored)
11436 player->is_bored = TRUE;
11438 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11439 player->is_bored ? ACTION_BORING :
11442 if (player->is_sleeping && player->use_murphy)
11444 // special case for sleeping Murphy when leaning against non-free tile
11446 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11447 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11448 !IS_MOVING(player->jx - 1, player->jy)))
11449 move_dir = MV_LEFT;
11450 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11451 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11452 !IS_MOVING(player->jx + 1, player->jy)))
11453 move_dir = MV_RIGHT;
11455 player->is_sleeping = FALSE;
11457 player->dir_waiting = move_dir;
11460 if (player->is_sleeping)
11462 if (player->num_special_action_sleeping > 0)
11464 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11466 int last_special_action = player->special_action_sleeping;
11467 int num_special_action = player->num_special_action_sleeping;
11468 int special_action =
11469 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11470 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11471 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11472 last_special_action + 1 : ACTION_SLEEPING);
11473 int special_graphic =
11474 el_act_dir2img(player->artwork_element, special_action, move_dir);
11476 player->anim_delay_counter =
11477 graphic_info[special_graphic].anim_delay_fixed +
11478 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11479 player->post_delay_counter =
11480 graphic_info[special_graphic].post_delay_fixed +
11481 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11483 player->special_action_sleeping = special_action;
11486 if (player->anim_delay_counter > 0)
11488 player->action_waiting = player->special_action_sleeping;
11489 player->anim_delay_counter--;
11491 else if (player->post_delay_counter > 0)
11493 player->post_delay_counter--;
11497 else if (player->is_bored)
11499 if (player->num_special_action_bored > 0)
11501 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11503 int special_action =
11504 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11505 int special_graphic =
11506 el_act_dir2img(player->artwork_element, special_action, move_dir);
11508 player->anim_delay_counter =
11509 graphic_info[special_graphic].anim_delay_fixed +
11510 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11511 player->post_delay_counter =
11512 graphic_info[special_graphic].post_delay_fixed +
11513 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11515 player->special_action_bored = special_action;
11518 if (player->anim_delay_counter > 0)
11520 player->action_waiting = player->special_action_bored;
11521 player->anim_delay_counter--;
11523 else if (player->post_delay_counter > 0)
11525 player->post_delay_counter--;
11530 else if (last_waiting) // waiting -> not waiting
11532 player->is_waiting = FALSE;
11533 player->is_bored = FALSE;
11534 player->is_sleeping = FALSE;
11536 player->frame_counter_bored = -1;
11537 player->frame_counter_sleeping = -1;
11539 player->anim_delay_counter = 0;
11540 player->post_delay_counter = 0;
11542 player->dir_waiting = player->MovDir;
11543 player->action_waiting = ACTION_DEFAULT;
11545 player->special_action_bored = ACTION_DEFAULT;
11546 player->special_action_sleeping = ACTION_DEFAULT;
11550 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11552 if ((!player->is_moving && player->was_moving) ||
11553 (player->MovPos == 0 && player->was_moving) ||
11554 (player->is_snapping && !player->was_snapping) ||
11555 (player->is_dropping && !player->was_dropping))
11557 if (!CheckSaveEngineSnapshotToList())
11560 player->was_moving = FALSE;
11561 player->was_snapping = TRUE;
11562 player->was_dropping = TRUE;
11566 if (player->is_moving)
11567 player->was_moving = TRUE;
11569 if (!player->is_snapping)
11570 player->was_snapping = FALSE;
11572 if (!player->is_dropping)
11573 player->was_dropping = FALSE;
11576 static struct MouseActionInfo mouse_action_last = { 0 };
11577 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11578 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11581 CheckSaveEngineSnapshotToList();
11583 mouse_action_last = mouse_action;
11586 static void CheckSingleStepMode(struct PlayerInfo *player)
11588 if (tape.single_step && tape.recording && !tape.pausing)
11590 // as it is called "single step mode", just return to pause mode when the
11591 // player stopped moving after one tile (or never starts moving at all)
11592 // (reverse logic needed here in case single step mode used in team mode)
11593 if (player->is_moving ||
11594 player->is_pushing ||
11595 player->is_dropping_pressed ||
11596 player->effective_mouse_action.button)
11597 game.enter_single_step_mode = FALSE;
11600 CheckSaveEngineSnapshot(player);
11603 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11605 int left = player_action & JOY_LEFT;
11606 int right = player_action & JOY_RIGHT;
11607 int up = player_action & JOY_UP;
11608 int down = player_action & JOY_DOWN;
11609 int button1 = player_action & JOY_BUTTON_1;
11610 int button2 = player_action & JOY_BUTTON_2;
11611 int dx = (left ? -1 : right ? 1 : 0);
11612 int dy = (up ? -1 : down ? 1 : 0);
11614 if (!player->active || tape.pausing)
11620 SnapField(player, dx, dy);
11624 DropElement(player);
11626 MovePlayer(player, dx, dy);
11629 CheckSingleStepMode(player);
11631 SetPlayerWaiting(player, FALSE);
11633 return player_action;
11637 // no actions for this player (no input at player's configured device)
11639 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11640 SnapField(player, 0, 0);
11641 CheckGravityMovementWhenNotMoving(player);
11643 if (player->MovPos == 0)
11644 SetPlayerWaiting(player, TRUE);
11646 if (player->MovPos == 0) // needed for tape.playing
11647 player->is_moving = FALSE;
11649 player->is_dropping = FALSE;
11650 player->is_dropping_pressed = FALSE;
11651 player->drop_pressed_delay = 0;
11653 CheckSingleStepMode(player);
11659 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11662 if (!tape.use_mouse_actions)
11665 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11666 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11667 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11670 static void SetTapeActionFromMouseAction(byte *tape_action,
11671 struct MouseActionInfo *mouse_action)
11673 if (!tape.use_mouse_actions)
11676 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11677 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11678 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11681 static void CheckLevelSolved(void)
11683 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11685 if (game_bd.level_solved &&
11686 !game_bd.game_over) // game won
11690 game_bd.game_over = TRUE;
11692 game.all_players_gone = TRUE;
11695 if (game_bd.game_over) // game lost
11696 game.all_players_gone = TRUE;
11698 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11700 if (game_em.level_solved &&
11701 !game_em.game_over) // game won
11705 game_em.game_over = TRUE;
11707 game.all_players_gone = TRUE;
11710 if (game_em.game_over) // game lost
11711 game.all_players_gone = TRUE;
11713 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11715 if (game_sp.level_solved &&
11716 !game_sp.game_over) // game won
11720 game_sp.game_over = TRUE;
11722 game.all_players_gone = TRUE;
11725 if (game_sp.game_over) // game lost
11726 game.all_players_gone = TRUE;
11728 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11730 if (game_mm.level_solved &&
11731 !game_mm.game_over) // game won
11735 game_mm.game_over = TRUE;
11737 game.all_players_gone = TRUE;
11740 if (game_mm.game_over) // game lost
11741 game.all_players_gone = TRUE;
11745 static void PlayTimeoutSound(int seconds_left)
11747 // will be played directly by BD engine (for classic bonus time sounds)
11748 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && checkBonusTime_BD())
11751 // try to use individual "running out of time" sound for each second left
11752 int sound = SND_GAME_RUNNING_OUT_OF_TIME_0 - seconds_left;
11754 // if special sound per second not defined, use default sound
11755 if (getSoundInfoEntryFilename(sound) == NULL)
11756 sound = SND_GAME_RUNNING_OUT_OF_TIME;
11758 // if out of time, but player still alive, play special "timeout" sound, if defined
11759 if (seconds_left == 0 && !checkGameFailed())
11760 if (getSoundInfoEntryFilename(SND_GAME_TIMEOUT) != NULL)
11761 sound = SND_GAME_TIMEOUT;
11766 static void CheckLevelTime_StepCounter(void)
11776 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11777 PlayTimeoutSound(TimeLeft);
11779 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11781 DisplayGameControlValues();
11783 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11784 for (i = 0; i < MAX_PLAYERS; i++)
11785 KillPlayer(&stored_player[i]);
11787 else if (game.no_level_time_limit && !game.all_players_gone)
11789 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11791 DisplayGameControlValues();
11795 static void CheckLevelTime(void)
11797 int frames_per_second = FRAMES_PER_SECOND;
11800 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11802 // level time may be running slower in native BD engine
11803 frames_per_second = getFramesPerSecond_BD();
11805 // if native engine time changed, force main engine time change
11806 if (getTimeLeft_BD() < TimeLeft)
11807 TimeFrames = frames_per_second;
11809 // if last second running, wait for native engine time to exactly reach zero
11810 if (getTimeLeft_BD() == 1 && TimeLeft == 1)
11811 TimeFrames = frames_per_second - 1;
11814 if (TimeFrames >= frames_per_second)
11818 for (i = 0; i < MAX_PLAYERS; i++)
11820 struct PlayerInfo *player = &stored_player[i];
11822 if (SHIELD_ON(player))
11824 player->shield_normal_time_left--;
11826 if (player->shield_deadly_time_left > 0)
11827 player->shield_deadly_time_left--;
11831 if (!game.LevelSolved && !level.use_step_counter)
11839 if (TimeLeft <= 10 && game.time_limit)
11840 PlayTimeoutSound(TimeLeft);
11842 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11843 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11845 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11847 if (!TimeLeft && game.time_limit)
11849 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11851 if (game_bd.game->cave->player_state == GD_PL_LIVING)
11852 game_bd.game->cave->player_state = GD_PL_TIMEOUT;
11854 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11856 game_em.lev->killed_out_of_time = TRUE;
11860 for (i = 0; i < MAX_PLAYERS; i++)
11861 KillPlayer(&stored_player[i]);
11865 else if (game.no_level_time_limit && !game.all_players_gone)
11867 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11870 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11874 if (TapeTimeFrames >= FRAMES_PER_SECOND)
11876 TapeTimeFrames = 0;
11879 if (tape.recording || tape.playing)
11880 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11883 if (tape.recording || tape.playing)
11884 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11886 UpdateAndDisplayGameControlValues();
11889 void AdvanceFrameAndPlayerCounters(int player_nr)
11893 // handle game and tape time differently for native BD game engine
11895 // tape time is running in native BD engine even if player is not hatched yet
11896 if (!checkGameRunning())
11899 // advance frame counters (global frame counter and tape time frame counter)
11903 // level time is running in native BD engine after player is being hatched
11904 if (!checkGamePlaying())
11907 // advance time frame counter (used to control available time to solve level)
11910 // advance player counters (counters for move delay, move animation etc.)
11911 for (i = 0; i < MAX_PLAYERS; i++)
11913 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11914 int move_delay_value = stored_player[i].move_delay_value;
11915 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11917 if (!advance_player_counters) // not all players may be affected
11920 if (move_frames == 0) // less than one move per game frame
11922 int stepsize = TILEX / move_delay_value;
11923 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11924 int count = (stored_player[i].is_moving ?
11925 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11927 if (count % delay == 0)
11931 stored_player[i].Frame += move_frames;
11933 if (stored_player[i].MovPos != 0)
11934 stored_player[i].StepFrame += move_frames;
11936 if (stored_player[i].move_delay > 0)
11937 stored_player[i].move_delay--;
11939 // due to bugs in previous versions, counter must count up, not down
11940 if (stored_player[i].push_delay != -1)
11941 stored_player[i].push_delay++;
11943 if (stored_player[i].drop_delay > 0)
11944 stored_player[i].drop_delay--;
11946 if (stored_player[i].is_dropping_pressed)
11947 stored_player[i].drop_pressed_delay++;
11951 void AdvanceFrameCounter(void)
11956 void AdvanceGfxFrame(void)
11960 SCAN_PLAYFIELD(x, y)
11966 static void HandleMouseAction(struct MouseActionInfo *mouse_action,
11967 struct MouseActionInfo *mouse_action_last)
11969 if (mouse_action->button)
11971 int new_button = (mouse_action->button && mouse_action_last->button == 0);
11972 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button);
11973 int x = mouse_action->lx;
11974 int y = mouse_action->ly;
11975 int element = Tile[x][y];
11979 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
11980 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
11984 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
11985 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
11988 if (level.use_step_counter)
11990 boolean counted_click = FALSE;
11992 // element clicked that can change when clicked/pressed
11993 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
11994 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
11995 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
11996 counted_click = TRUE;
11998 // element clicked that can trigger change when clicked/pressed
11999 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
12000 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
12001 counted_click = TRUE;
12003 if (new_button && counted_click)
12004 CheckLevelTime_StepCounter();
12009 void StartGameActions(boolean init_network_game, boolean record_tape,
12012 unsigned int new_random_seed = InitRND(random_seed);
12015 TapeStartRecording(new_random_seed);
12017 if (setup.auto_pause_on_start && !tape.pausing)
12018 TapeTogglePause(TAPE_TOGGLE_MANUAL);
12020 if (init_network_game)
12022 SendToServer_LevelFile();
12023 SendToServer_StartPlaying();
12031 static void GameActionsExt(void)
12034 static unsigned int game_frame_delay = 0;
12036 unsigned int game_frame_delay_value;
12037 byte *recorded_player_action;
12038 byte summarized_player_action = 0;
12039 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
12042 // detect endless loops, caused by custom element programming
12043 if (recursion_loop_detected && recursion_loop_depth == 0)
12045 char *message = getStringCat3("Internal Error! Element ",
12046 EL_NAME(recursion_loop_element),
12047 " caused endless loop! Quit the game?");
12049 Warn("element '%s' caused endless loop in game engine",
12050 EL_NAME(recursion_loop_element));
12052 RequestQuitGameExt(program.headless, level_editor_test_game, message);
12054 recursion_loop_detected = FALSE; // if game should be continued
12061 if (game.restart_level)
12062 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
12064 CheckLevelSolved();
12066 if (game.LevelSolved && !game.LevelSolved_GameEnd)
12069 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
12072 if (game_status != GAME_MODE_PLAYING) // status might have changed
12075 game_frame_delay_value =
12076 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
12078 if (tape.playing && tape.warp_forward && !tape.pausing)
12079 game_frame_delay_value = 0;
12081 SetVideoFrameDelay(game_frame_delay_value);
12083 // (de)activate virtual buttons depending on current game status
12084 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
12086 if (game.all_players_gone) // if no players there to be controlled anymore
12087 SetOverlayActive(FALSE);
12088 else if (!tape.playing) // if game continues after tape stopped playing
12089 SetOverlayActive(TRUE);
12094 // ---------- main game synchronization point ----------
12096 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12098 Debug("game:playing:skip", "skip == %d", skip);
12101 // ---------- main game synchronization point ----------
12103 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12107 if (network_playing && !network_player_action_received)
12109 // try to get network player actions in time
12111 // last chance to get network player actions without main loop delay
12112 HandleNetworking();
12114 // game was quit by network peer
12115 if (game_status != GAME_MODE_PLAYING)
12118 // check if network player actions still missing and game still running
12119 if (!network_player_action_received && !checkGameEnded())
12120 return; // failed to get network player actions in time
12122 // do not yet reset "network_player_action_received" (for tape.pausing)
12128 // at this point we know that we really continue executing the game
12130 network_player_action_received = FALSE;
12132 // when playing tape, read previously recorded player input from tape data
12133 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
12135 local_player->effective_mouse_action = local_player->mouse_action;
12137 if (recorded_player_action != NULL)
12138 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
12139 recorded_player_action);
12141 // TapePlayAction() may return NULL when toggling to "pause before death"
12145 if (tape.set_centered_player)
12147 game.centered_player_nr_next = tape.centered_player_nr_next;
12148 game.set_centered_player = TRUE;
12151 for (i = 0; i < MAX_PLAYERS; i++)
12153 summarized_player_action |= stored_player[i].action;
12155 if (!network_playing && (game.team_mode || tape.playing))
12156 stored_player[i].effective_action = stored_player[i].action;
12159 if (network_playing && !checkGameEnded())
12160 SendToServer_MovePlayer(summarized_player_action);
12162 // summarize all actions at local players mapped input device position
12163 // (this allows using different input devices in single player mode)
12164 if (!network.enabled && !game.team_mode)
12165 stored_player[map_player_action[local_player->index_nr]].effective_action =
12166 summarized_player_action;
12168 // summarize all actions at centered player in local team mode
12169 if (tape.recording &&
12170 setup.team_mode && !network.enabled &&
12171 setup.input_on_focus &&
12172 game.centered_player_nr != -1)
12174 for (i = 0; i < MAX_PLAYERS; i++)
12175 stored_player[map_player_action[i]].effective_action =
12176 (i == game.centered_player_nr ? summarized_player_action : 0);
12179 if (recorded_player_action != NULL)
12180 for (i = 0; i < MAX_PLAYERS; i++)
12181 stored_player[i].effective_action = recorded_player_action[i];
12183 for (i = 0; i < MAX_PLAYERS; i++)
12185 tape_action[i] = stored_player[i].effective_action;
12187 /* (this may happen in the RND game engine if a player was not present on
12188 the playfield on level start, but appeared later from a custom element */
12189 if (setup.team_mode &&
12192 !tape.player_participates[i])
12193 tape.player_participates[i] = TRUE;
12196 SetTapeActionFromMouseAction(tape_action,
12197 &local_player->effective_mouse_action);
12199 // only record actions from input devices, but not programmed actions
12200 if (tape.recording)
12201 TapeRecordAction(tape_action);
12203 // remember if game was played (especially after tape stopped playing)
12204 if (!tape.playing && summarized_player_action && !checkGameFailed())
12205 game.GamePlayed = TRUE;
12207 #if USE_NEW_PLAYER_ASSIGNMENTS
12208 // !!! also map player actions in single player mode !!!
12209 // if (game.team_mode)
12212 byte mapped_action[MAX_PLAYERS];
12214 #if DEBUG_PLAYER_ACTIONS
12215 for (i = 0; i < MAX_PLAYERS; i++)
12216 DebugContinued("", "%d, ", stored_player[i].effective_action);
12219 for (i = 0; i < MAX_PLAYERS; i++)
12220 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
12222 for (i = 0; i < MAX_PLAYERS; i++)
12223 stored_player[i].effective_action = mapped_action[i];
12225 #if DEBUG_PLAYER_ACTIONS
12226 DebugContinued("", "=> ");
12227 for (i = 0; i < MAX_PLAYERS; i++)
12228 DebugContinued("", "%d, ", stored_player[i].effective_action);
12229 DebugContinued("game:playing:player", "\n");
12232 #if DEBUG_PLAYER_ACTIONS
12235 for (i = 0; i < MAX_PLAYERS; i++)
12236 DebugContinued("", "%d, ", stored_player[i].effective_action);
12237 DebugContinued("game:playing:player", "\n");
12242 for (i = 0; i < MAX_PLAYERS; i++)
12244 // allow engine snapshot in case of changed movement attempt
12245 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12246 (stored_player[i].effective_action & KEY_MOTION))
12247 game.snapshot.changed_action = TRUE;
12249 // allow engine snapshot in case of snapping/dropping attempt
12250 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12251 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12252 game.snapshot.changed_action = TRUE;
12254 game.snapshot.last_action[i] = stored_player[i].effective_action;
12257 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
12259 GameActions_BD_Main();
12261 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12263 GameActions_EM_Main();
12265 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12267 GameActions_SP_Main();
12269 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12271 GameActions_MM_Main();
12275 GameActions_RND_Main();
12278 BlitScreenToBitmap(backbuffer);
12280 CheckLevelSolved();
12283 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12285 if (global.show_frames_per_second)
12287 static unsigned int fps_counter = 0;
12288 static int fps_frames = 0;
12289 unsigned int fps_delay_ms = Counter() - fps_counter;
12293 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12295 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12298 fps_counter = Counter();
12300 // always draw FPS to screen after FPS value was updated
12301 redraw_mask |= REDRAW_FPS;
12304 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12305 if (GetDrawDeactivationMask() == REDRAW_NONE)
12306 redraw_mask |= REDRAW_FPS;
12310 static void GameActions_CheckSaveEngineSnapshot(void)
12312 if (!game.snapshot.save_snapshot)
12315 // clear flag for saving snapshot _before_ saving snapshot
12316 game.snapshot.save_snapshot = FALSE;
12318 SaveEngineSnapshotToList();
12321 void GameActions(void)
12325 GameActions_CheckSaveEngineSnapshot();
12328 void GameActions_BD_Main(void)
12330 byte effective_action[MAX_PLAYERS];
12333 for (i = 0; i < MAX_PLAYERS; i++)
12334 effective_action[i] = stored_player[i].effective_action;
12336 GameActions_BD(effective_action);
12339 void GameActions_EM_Main(void)
12341 byte effective_action[MAX_PLAYERS];
12344 for (i = 0; i < MAX_PLAYERS; i++)
12345 effective_action[i] = stored_player[i].effective_action;
12347 GameActions_EM(effective_action);
12350 void GameActions_SP_Main(void)
12352 byte effective_action[MAX_PLAYERS];
12355 for (i = 0; i < MAX_PLAYERS; i++)
12356 effective_action[i] = stored_player[i].effective_action;
12358 GameActions_SP(effective_action);
12360 for (i = 0; i < MAX_PLAYERS; i++)
12362 if (stored_player[i].force_dropping)
12363 stored_player[i].action |= KEY_BUTTON_DROP;
12365 stored_player[i].force_dropping = FALSE;
12369 void GameActions_MM_Main(void)
12373 GameActions_MM(local_player->effective_mouse_action);
12376 void GameActions_RND_Main(void)
12381 void GameActions_RND(void)
12383 static struct MouseActionInfo mouse_action_last = { 0 };
12384 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12385 int magic_wall_x = 0, magic_wall_y = 0;
12386 int i, x, y, element, graphic, last_gfx_frame;
12388 InitPlayfieldScanModeVars();
12390 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12392 SCAN_PLAYFIELD(x, y)
12394 ChangeCount[x][y] = 0;
12395 ChangeEvent[x][y] = -1;
12399 if (game.set_centered_player)
12401 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12403 // switching to "all players" only possible if all players fit to screen
12404 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12406 game.centered_player_nr_next = game.centered_player_nr;
12407 game.set_centered_player = FALSE;
12410 // do not switch focus to non-existing (or non-active) player
12411 if (game.centered_player_nr_next >= 0 &&
12412 !stored_player[game.centered_player_nr_next].active)
12414 game.centered_player_nr_next = game.centered_player_nr;
12415 game.set_centered_player = FALSE;
12419 if (game.set_centered_player &&
12420 ScreenMovPos == 0) // screen currently aligned at tile position
12424 if (game.centered_player_nr_next == -1)
12426 setScreenCenteredToAllPlayers(&sx, &sy);
12430 sx = stored_player[game.centered_player_nr_next].jx;
12431 sy = stored_player[game.centered_player_nr_next].jy;
12434 game.centered_player_nr = game.centered_player_nr_next;
12435 game.set_centered_player = FALSE;
12437 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12438 DrawGameDoorValues();
12441 // check single step mode (set flag and clear again if any player is active)
12442 game.enter_single_step_mode =
12443 (tape.single_step && tape.recording && !tape.pausing);
12445 for (i = 0; i < MAX_PLAYERS; i++)
12447 int actual_player_action = stored_player[i].effective_action;
12450 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12451 - rnd_equinox_tetrachloride 048
12452 - rnd_equinox_tetrachloride_ii 096
12453 - rnd_emanuel_schmieg 002
12454 - doctor_sloan_ww 001, 020
12456 if (stored_player[i].MovPos == 0)
12457 CheckGravityMovement(&stored_player[i]);
12460 // overwrite programmed action with tape action
12461 if (stored_player[i].programmed_action)
12462 actual_player_action = stored_player[i].programmed_action;
12464 PlayerActions(&stored_player[i], actual_player_action);
12466 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12469 // single step pause mode may already have been toggled by "ScrollPlayer()"
12470 if (game.enter_single_step_mode && !tape.pausing)
12471 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12473 ScrollScreen(NULL, SCROLL_GO_ON);
12475 /* for backwards compatibility, the following code emulates a fixed bug that
12476 occured when pushing elements (causing elements that just made their last
12477 pushing step to already (if possible) make their first falling step in the
12478 same game frame, which is bad); this code is also needed to use the famous
12479 "spring push bug" which is used in older levels and might be wanted to be
12480 used also in newer levels, but in this case the buggy pushing code is only
12481 affecting the "spring" element and no other elements */
12483 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12485 for (i = 0; i < MAX_PLAYERS; i++)
12487 struct PlayerInfo *player = &stored_player[i];
12488 int x = player->jx;
12489 int y = player->jy;
12491 if (player->active && player->is_pushing && player->is_moving &&
12493 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12494 Tile[x][y] == EL_SPRING))
12496 ContinueMoving(x, y);
12498 // continue moving after pushing (this is actually a bug)
12499 if (!IS_MOVING(x, y))
12500 Stop[x][y] = FALSE;
12505 SCAN_PLAYFIELD(x, y)
12507 Last[x][y] = Tile[x][y];
12509 ChangeCount[x][y] = 0;
12510 ChangeEvent[x][y] = -1;
12512 // this must be handled before main playfield loop
12513 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12516 if (MovDelay[x][y] <= 0)
12520 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12523 if (MovDelay[x][y] <= 0)
12525 int element = Store[x][y];
12526 int move_direction = MovDir[x][y];
12527 int player_index_bit = Store2[x][y];
12533 TEST_DrawLevelField(x, y);
12535 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12537 if (IS_ENVELOPE(element))
12538 local_player->show_envelope = element;
12543 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12545 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12547 Debug("game:playing:GameActions_RND", "This should never happen!");
12549 ChangePage[x][y] = -1;
12553 Stop[x][y] = FALSE;
12554 if (WasJustMoving[x][y] > 0)
12555 WasJustMoving[x][y]--;
12556 if (WasJustFalling[x][y] > 0)
12557 WasJustFalling[x][y]--;
12558 if (CheckCollision[x][y] > 0)
12559 CheckCollision[x][y]--;
12560 if (CheckImpact[x][y] > 0)
12561 CheckImpact[x][y]--;
12565 /* reset finished pushing action (not done in ContinueMoving() to allow
12566 continuous pushing animation for elements with zero push delay) */
12567 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12569 ResetGfxAnimation(x, y);
12570 TEST_DrawLevelField(x, y);
12574 if (IS_BLOCKED(x, y))
12578 Blocked2Moving(x, y, &oldx, &oldy);
12579 if (!IS_MOVING(oldx, oldy))
12581 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12582 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12583 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12584 Debug("game:playing:GameActions_RND", "This should never happen!");
12590 HandleMouseAction(&mouse_action, &mouse_action_last);
12592 SCAN_PLAYFIELD(x, y)
12594 element = Tile[x][y];
12595 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12596 last_gfx_frame = GfxFrame[x][y];
12598 if (element == EL_EMPTY)
12599 graphic = el2img(GfxElementEmpty[x][y]);
12601 ResetGfxFrame(x, y);
12603 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12604 DrawLevelGraphicAnimation(x, y, graphic);
12606 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12607 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12608 ResetRandomAnimationValue(x, y);
12610 SetRandomAnimationValue(x, y);
12612 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12614 if (IS_INACTIVE(element))
12616 if (IS_ANIMATED(graphic))
12617 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12622 // this may take place after moving, so 'element' may have changed
12623 if (IS_CHANGING(x, y) &&
12624 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12626 int page = element_info[element].event_page_nr[CE_DELAY];
12628 HandleElementChange(x, y, page);
12630 element = Tile[x][y];
12631 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12634 CheckNextToConditions(x, y);
12636 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12640 element = Tile[x][y];
12641 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12643 if (IS_ANIMATED(graphic) &&
12644 !IS_MOVING(x, y) &&
12646 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12648 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12649 TEST_DrawTwinkleOnField(x, y);
12651 else if (element == EL_ACID)
12654 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12656 else if ((element == EL_EXIT_OPEN ||
12657 element == EL_EM_EXIT_OPEN ||
12658 element == EL_SP_EXIT_OPEN ||
12659 element == EL_STEEL_EXIT_OPEN ||
12660 element == EL_EM_STEEL_EXIT_OPEN ||
12661 element == EL_SP_TERMINAL ||
12662 element == EL_SP_TERMINAL_ACTIVE ||
12663 element == EL_EXTRA_TIME ||
12664 element == EL_SHIELD_NORMAL ||
12665 element == EL_SHIELD_DEADLY) &&
12666 IS_ANIMATED(graphic))
12667 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12668 else if (IS_MOVING(x, y))
12669 ContinueMoving(x, y);
12670 else if (IS_ACTIVE_BOMB(element))
12671 CheckDynamite(x, y);
12672 else if (element == EL_AMOEBA_GROWING)
12673 AmoebaGrowing(x, y);
12674 else if (element == EL_AMOEBA_SHRINKING)
12675 AmoebaShrinking(x, y);
12677 #if !USE_NEW_AMOEBA_CODE
12678 else if (IS_AMOEBALIVE(element))
12679 AmoebaReproduce(x, y);
12682 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12684 else if (element == EL_EXIT_CLOSED)
12686 else if (element == EL_EM_EXIT_CLOSED)
12688 else if (element == EL_STEEL_EXIT_CLOSED)
12689 CheckExitSteel(x, y);
12690 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12691 CheckExitSteelEM(x, y);
12692 else if (element == EL_SP_EXIT_CLOSED)
12694 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12695 element == EL_EXPANDABLE_STEELWALL_GROWING)
12697 else if (element == EL_EXPANDABLE_WALL ||
12698 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12699 element == EL_EXPANDABLE_WALL_VERTICAL ||
12700 element == EL_EXPANDABLE_WALL_ANY ||
12701 element == EL_BD_EXPANDABLE_WALL ||
12702 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12703 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12704 element == EL_EXPANDABLE_STEELWALL_ANY)
12705 CheckWallGrowing(x, y);
12706 else if (element == EL_FLAMES)
12707 CheckForDragon(x, y);
12708 else if (element == EL_EXPLOSION)
12709 ; // drawing of correct explosion animation is handled separately
12710 else if (element == EL_ELEMENT_SNAPPING ||
12711 element == EL_DIAGONAL_SHRINKING ||
12712 element == EL_DIAGONAL_GROWING)
12714 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]);
12716 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12718 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12719 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12721 if (IS_BELT_ACTIVE(element))
12722 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12724 if (game.magic_wall_active)
12726 int jx = local_player->jx, jy = local_player->jy;
12728 // play the element sound at the position nearest to the player
12729 if ((element == EL_MAGIC_WALL_FULL ||
12730 element == EL_MAGIC_WALL_ACTIVE ||
12731 element == EL_MAGIC_WALL_EMPTYING ||
12732 element == EL_BD_MAGIC_WALL_FULL ||
12733 element == EL_BD_MAGIC_WALL_ACTIVE ||
12734 element == EL_BD_MAGIC_WALL_EMPTYING ||
12735 element == EL_DC_MAGIC_WALL_FULL ||
12736 element == EL_DC_MAGIC_WALL_ACTIVE ||
12737 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12738 ABS(x - jx) + ABS(y - jy) <
12739 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12747 #if USE_NEW_AMOEBA_CODE
12748 // new experimental amoeba growth stuff
12749 if (!(FrameCounter % 8))
12751 static unsigned int random = 1684108901;
12753 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12755 x = RND(lev_fieldx);
12756 y = RND(lev_fieldy);
12757 element = Tile[x][y];
12759 if (!IS_PLAYER(x, y) &&
12760 (element == EL_EMPTY ||
12761 CAN_GROW_INTO(element) ||
12762 element == EL_QUICKSAND_EMPTY ||
12763 element == EL_QUICKSAND_FAST_EMPTY ||
12764 element == EL_ACID_SPLASH_LEFT ||
12765 element == EL_ACID_SPLASH_RIGHT))
12767 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12768 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12769 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12770 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12771 Tile[x][y] = EL_AMOEBA_DROP;
12774 random = random * 129 + 1;
12779 game.explosions_delayed = FALSE;
12781 SCAN_PLAYFIELD(x, y)
12783 element = Tile[x][y];
12785 if (ExplodeField[x][y])
12786 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12787 else if (element == EL_EXPLOSION)
12788 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12790 ExplodeField[x][y] = EX_TYPE_NONE;
12793 game.explosions_delayed = TRUE;
12795 if (game.magic_wall_active)
12797 if (!(game.magic_wall_time_left % 4))
12799 int element = Tile[magic_wall_x][magic_wall_y];
12801 if (element == EL_BD_MAGIC_WALL_FULL ||
12802 element == EL_BD_MAGIC_WALL_ACTIVE ||
12803 element == EL_BD_MAGIC_WALL_EMPTYING)
12804 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12805 else if (element == EL_DC_MAGIC_WALL_FULL ||
12806 element == EL_DC_MAGIC_WALL_ACTIVE ||
12807 element == EL_DC_MAGIC_WALL_EMPTYING)
12808 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12810 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12813 if (game.magic_wall_time_left > 0)
12815 game.magic_wall_time_left--;
12817 if (!game.magic_wall_time_left)
12819 SCAN_PLAYFIELD(x, y)
12821 element = Tile[x][y];
12823 if (element == EL_MAGIC_WALL_ACTIVE ||
12824 element == EL_MAGIC_WALL_FULL)
12826 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12827 TEST_DrawLevelField(x, y);
12829 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12830 element == EL_BD_MAGIC_WALL_FULL)
12832 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12833 TEST_DrawLevelField(x, y);
12835 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12836 element == EL_DC_MAGIC_WALL_FULL)
12838 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12839 TEST_DrawLevelField(x, y);
12843 game.magic_wall_active = FALSE;
12848 if (game.light_time_left > 0)
12850 game.light_time_left--;
12852 if (game.light_time_left == 0)
12853 RedrawAllLightSwitchesAndInvisibleElements();
12856 if (game.timegate_time_left > 0)
12858 game.timegate_time_left--;
12860 if (game.timegate_time_left == 0)
12861 CloseAllOpenTimegates();
12864 if (game.lenses_time_left > 0)
12866 game.lenses_time_left--;
12868 if (game.lenses_time_left == 0)
12869 RedrawAllInvisibleElementsForLenses();
12872 if (game.magnify_time_left > 0)
12874 game.magnify_time_left--;
12876 if (game.magnify_time_left == 0)
12877 RedrawAllInvisibleElementsForMagnifier();
12880 for (i = 0; i < MAX_PLAYERS; i++)
12882 struct PlayerInfo *player = &stored_player[i];
12884 if (SHIELD_ON(player))
12886 if (player->shield_deadly_time_left)
12887 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12888 else if (player->shield_normal_time_left)
12889 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12893 #if USE_DELAYED_GFX_REDRAW
12894 SCAN_PLAYFIELD(x, y)
12896 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12898 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12899 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12901 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12902 DrawLevelField(x, y);
12904 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12905 DrawLevelFieldCrumbled(x, y);
12907 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12908 DrawLevelFieldCrumbledNeighbours(x, y);
12910 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12911 DrawTwinkleOnField(x, y);
12914 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12919 PlayAllPlayersSound();
12921 for (i = 0; i < MAX_PLAYERS; i++)
12923 struct PlayerInfo *player = &stored_player[i];
12925 if (player->show_envelope != 0 && (!player->active ||
12926 player->MovPos == 0))
12928 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12930 player->show_envelope = 0;
12934 // use random number generator in every frame to make it less predictable
12935 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12938 mouse_action_last = mouse_action;
12941 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12943 int min_x = x, min_y = y, max_x = x, max_y = y;
12944 int scr_fieldx = getScreenFieldSizeX();
12945 int scr_fieldy = getScreenFieldSizeY();
12948 for (i = 0; i < MAX_PLAYERS; i++)
12950 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12952 if (!stored_player[i].active || &stored_player[i] == player)
12955 min_x = MIN(min_x, jx);
12956 min_y = MIN(min_y, jy);
12957 max_x = MAX(max_x, jx);
12958 max_y = MAX(max_y, jy);
12961 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12964 static boolean AllPlayersInVisibleScreen(void)
12968 for (i = 0; i < MAX_PLAYERS; i++)
12970 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12972 if (!stored_player[i].active)
12975 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12982 void ScrollLevel(int dx, int dy)
12984 int scroll_offset = 2 * TILEX_VAR;
12987 BlitBitmap(drawto_field, drawto_field,
12988 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12989 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12990 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12991 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12992 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12993 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12997 x = (dx == 1 ? BX1 : BX2);
12998 for (y = BY1; y <= BY2; y++)
12999 DrawScreenField(x, y);
13004 y = (dy == 1 ? BY1 : BY2);
13005 for (x = BX1; x <= BX2; x++)
13006 DrawScreenField(x, y);
13009 redraw_mask |= REDRAW_FIELD;
13012 static boolean canFallDown(struct PlayerInfo *player)
13014 int jx = player->jx, jy = player->jy;
13016 return (IN_LEV_FIELD(jx, jy + 1) &&
13017 (IS_FREE(jx, jy + 1) ||
13018 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
13019 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
13020 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
13023 static boolean canPassField(int x, int y, int move_dir)
13025 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13026 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13027 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13028 int nextx = x + dx;
13029 int nexty = y + dy;
13030 int element = Tile[x][y];
13032 return (IS_PASSABLE_FROM(element, opposite_dir) &&
13033 !CAN_MOVE(element) &&
13034 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
13035 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
13036 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
13039 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
13041 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13042 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13043 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13047 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
13048 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
13049 (IS_DIGGABLE(Tile[newx][newy]) ||
13050 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
13051 canPassField(newx, newy, move_dir)));
13054 static void CheckGravityMovement(struct PlayerInfo *player)
13056 if (player->gravity && !player->programmed_action)
13058 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
13059 int move_dir_vertical = player->effective_action & MV_VERTICAL;
13060 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
13061 int jx = player->jx, jy = player->jy;
13062 boolean player_is_moving_to_valid_field =
13063 (!player_is_snapping &&
13064 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
13065 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
13066 boolean player_can_fall_down = canFallDown(player);
13068 if (player_can_fall_down &&
13069 !player_is_moving_to_valid_field)
13070 player->programmed_action = MV_DOWN;
13074 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
13076 return CheckGravityMovement(player);
13078 if (player->gravity && !player->programmed_action)
13080 int jx = player->jx, jy = player->jy;
13081 boolean field_under_player_is_free =
13082 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
13083 boolean player_is_standing_on_valid_field =
13084 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
13085 (IS_WALKABLE(Tile[jx][jy]) &&
13086 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
13088 if (field_under_player_is_free && !player_is_standing_on_valid_field)
13089 player->programmed_action = MV_DOWN;
13094 MovePlayerOneStep()
13095 -----------------------------------------------------------------------------
13096 dx, dy: direction (non-diagonal) to try to move the player to
13097 real_dx, real_dy: direction as read from input device (can be diagonal)
13100 boolean MovePlayerOneStep(struct PlayerInfo *player,
13101 int dx, int dy, int real_dx, int real_dy)
13103 int jx = player->jx, jy = player->jy;
13104 int new_jx = jx + dx, new_jy = jy + dy;
13106 boolean player_can_move = !player->cannot_move;
13108 if (!player->active || (!dx && !dy))
13109 return MP_NO_ACTION;
13111 player->MovDir = (dx < 0 ? MV_LEFT :
13112 dx > 0 ? MV_RIGHT :
13114 dy > 0 ? MV_DOWN : MV_NONE);
13116 if (!IN_LEV_FIELD(new_jx, new_jy))
13117 return MP_NO_ACTION;
13119 if (!player_can_move)
13121 if (player->MovPos == 0)
13123 player->is_moving = FALSE;
13124 player->is_digging = FALSE;
13125 player->is_collecting = FALSE;
13126 player->is_snapping = FALSE;
13127 player->is_pushing = FALSE;
13131 if (!network.enabled && game.centered_player_nr == -1 &&
13132 !AllPlayersInSight(player, new_jx, new_jy))
13133 return MP_NO_ACTION;
13135 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG);
13136 if (can_move != MP_MOVING)
13139 // check if DigField() has caused relocation of the player
13140 if (player->jx != jx || player->jy != jy)
13141 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
13143 StorePlayer[jx][jy] = 0;
13144 player->last_jx = jx;
13145 player->last_jy = jy;
13146 player->jx = new_jx;
13147 player->jy = new_jy;
13148 StorePlayer[new_jx][new_jy] = player->element_nr;
13150 if (player->move_delay_value_next != -1)
13152 player->move_delay_value = player->move_delay_value_next;
13153 player->move_delay_value_next = -1;
13157 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
13159 player->step_counter++;
13161 PlayerVisit[jx][jy] = FrameCounter;
13163 player->is_moving = TRUE;
13166 // should better be called in MovePlayer(), but this breaks some tapes
13167 ScrollPlayer(player, SCROLL_INIT);
13173 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
13175 int jx = player->jx, jy = player->jy;
13176 int old_jx = jx, old_jy = jy;
13177 int moved = MP_NO_ACTION;
13179 if (!player->active)
13184 if (player->MovPos == 0)
13186 player->is_moving = FALSE;
13187 player->is_digging = FALSE;
13188 player->is_collecting = FALSE;
13189 player->is_snapping = FALSE;
13190 player->is_pushing = FALSE;
13196 if (player->move_delay > 0)
13199 player->move_delay = -1; // set to "uninitialized" value
13201 // store if player is automatically moved to next field
13202 player->is_auto_moving = (player->programmed_action != MV_NONE);
13204 // remove the last programmed player action
13205 player->programmed_action = 0;
13207 if (player->MovPos)
13209 // should only happen if pre-1.2 tape recordings are played
13210 // this is only for backward compatibility
13212 int original_move_delay_value = player->move_delay_value;
13215 Debug("game:playing:MovePlayer",
13216 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13220 // scroll remaining steps with finest movement resolution
13221 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13223 while (player->MovPos)
13225 ScrollPlayer(player, SCROLL_GO_ON);
13226 ScrollScreen(NULL, SCROLL_GO_ON);
13228 AdvanceFrameAndPlayerCounters(player->index_nr);
13231 BackToFront_WithFrameDelay(0);
13234 player->move_delay_value = original_move_delay_value;
13237 player->is_active = FALSE;
13239 if (player->last_move_dir & MV_HORIZONTAL)
13241 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13242 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13246 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13247 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13250 if (!moved && !player->is_active)
13252 player->is_moving = FALSE;
13253 player->is_digging = FALSE;
13254 player->is_collecting = FALSE;
13255 player->is_snapping = FALSE;
13256 player->is_pushing = FALSE;
13262 if (moved & MP_MOVING && !ScreenMovPos &&
13263 (player->index_nr == game.centered_player_nr ||
13264 game.centered_player_nr == -1))
13266 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13268 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13270 // actual player has left the screen -- scroll in that direction
13271 if (jx != old_jx) // player has moved horizontally
13272 scroll_x += (jx - old_jx);
13273 else // player has moved vertically
13274 scroll_y += (jy - old_jy);
13278 int offset_raw = game.scroll_delay_value;
13280 if (jx != old_jx) // player has moved horizontally
13282 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13283 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13284 int new_scroll_x = jx - MIDPOSX + offset_x;
13286 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13287 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13288 scroll_x = new_scroll_x;
13290 // don't scroll over playfield boundaries
13291 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13293 // don't scroll more than one field at a time
13294 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13296 // don't scroll against the player's moving direction
13297 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13298 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13299 scroll_x = old_scroll_x;
13301 else // player has moved vertically
13303 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13304 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13305 int new_scroll_y = jy - MIDPOSY + offset_y;
13307 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13308 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13309 scroll_y = new_scroll_y;
13311 // don't scroll over playfield boundaries
13312 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13314 // don't scroll more than one field at a time
13315 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13317 // don't scroll against the player's moving direction
13318 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13319 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13320 scroll_y = old_scroll_y;
13324 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13326 if (!network.enabled && game.centered_player_nr == -1 &&
13327 !AllPlayersInVisibleScreen())
13329 scroll_x = old_scroll_x;
13330 scroll_y = old_scroll_y;
13334 ScrollScreen(player, SCROLL_INIT);
13335 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13340 player->StepFrame = 0;
13342 if (moved & MP_MOVING)
13344 if (old_jx != jx && old_jy == jy)
13345 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13346 else if (old_jx == jx && old_jy != jy)
13347 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13349 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13351 player->last_move_dir = player->MovDir;
13352 player->is_moving = TRUE;
13353 player->is_snapping = FALSE;
13354 player->is_switching = FALSE;
13355 player->is_dropping = FALSE;
13356 player->is_dropping_pressed = FALSE;
13357 player->drop_pressed_delay = 0;
13360 // should better be called here than above, but this breaks some tapes
13361 ScrollPlayer(player, SCROLL_INIT);
13366 CheckGravityMovementWhenNotMoving(player);
13368 player->is_moving = FALSE;
13370 /* at this point, the player is allowed to move, but cannot move right now
13371 (e.g. because of something blocking the way) -- ensure that the player
13372 is also allowed to move in the next frame (in old versions before 3.1.1,
13373 the player was forced to wait again for eight frames before next try) */
13375 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13376 player->move_delay = 0; // allow direct movement in the next frame
13379 if (player->move_delay == -1) // not yet initialized by DigField()
13380 player->move_delay = player->move_delay_value;
13382 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13384 TestIfPlayerTouchesBadThing(jx, jy);
13385 TestIfPlayerTouchesCustomElement(jx, jy);
13388 if (!player->active)
13389 RemovePlayer(player);
13394 void ScrollPlayer(struct PlayerInfo *player, int mode)
13396 int jx = player->jx, jy = player->jy;
13397 int last_jx = player->last_jx, last_jy = player->last_jy;
13398 int move_stepsize = TILEX / player->move_delay_value;
13400 if (!player->active)
13403 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13406 if (mode == SCROLL_INIT)
13408 player->actual_frame_counter.count = FrameCounter;
13409 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13411 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13412 Tile[last_jx][last_jy] == EL_EMPTY)
13414 int last_field_block_delay = 0; // start with no blocking at all
13415 int block_delay_adjustment = player->block_delay_adjustment;
13417 // if player blocks last field, add delay for exactly one move
13418 if (player->block_last_field)
13420 last_field_block_delay += player->move_delay_value;
13422 // when blocking enabled, prevent moving up despite gravity
13423 if (player->gravity && player->MovDir == MV_UP)
13424 block_delay_adjustment = -1;
13427 // add block delay adjustment (also possible when not blocking)
13428 last_field_block_delay += block_delay_adjustment;
13430 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13431 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13434 if (player->MovPos != 0) // player has not yet reached destination
13437 else if (!FrameReached(&player->actual_frame_counter))
13440 if (player->MovPos != 0)
13442 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13443 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13445 // before DrawPlayer() to draw correct player graphic for this case
13446 if (player->MovPos == 0)
13447 CheckGravityMovement(player);
13450 if (player->MovPos == 0) // player reached destination field
13452 if (player->move_delay_reset_counter > 0)
13454 player->move_delay_reset_counter--;
13456 if (player->move_delay_reset_counter == 0)
13458 // continue with normal speed after quickly moving through gate
13459 HALVE_PLAYER_SPEED(player);
13461 // be able to make the next move without delay
13462 player->move_delay = 0;
13466 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13467 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13468 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13469 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13470 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13471 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13472 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13473 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13475 ExitPlayer(player);
13477 if (game.players_still_needed == 0 &&
13478 (game.friends_still_needed == 0 ||
13479 IS_SP_ELEMENT(Tile[jx][jy])))
13483 player->last_jx = jx;
13484 player->last_jy = jy;
13486 // this breaks one level: "machine", level 000
13488 int move_direction = player->MovDir;
13489 int enter_side = MV_DIR_OPPOSITE(move_direction);
13490 int leave_side = move_direction;
13491 int old_jx = last_jx;
13492 int old_jy = last_jy;
13493 int old_element = Tile[old_jx][old_jy];
13494 int new_element = Tile[jx][jy];
13496 if (IS_CUSTOM_ELEMENT(old_element))
13497 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13499 player->index_bit, leave_side);
13501 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13502 CE_PLAYER_LEAVES_X,
13503 player->index_bit, leave_side);
13505 // needed because pushed element has not yet reached its destination,
13506 // so it would trigger a change event at its previous field location
13507 if (!player->is_pushing)
13509 if (IS_CUSTOM_ELEMENT(new_element))
13510 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13511 player->index_bit, enter_side);
13513 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13514 CE_PLAYER_ENTERS_X,
13515 player->index_bit, enter_side);
13518 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13519 CE_MOVE_OF_X, move_direction);
13522 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13524 TestIfPlayerTouchesBadThing(jx, jy);
13525 TestIfPlayerTouchesCustomElement(jx, jy);
13527 // needed because pushed element has not yet reached its destination,
13528 // so it would trigger a change event at its previous field location
13529 if (!player->is_pushing)
13530 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13532 if (level.finish_dig_collect &&
13533 (player->is_digging || player->is_collecting))
13535 int last_element = player->last_removed_element;
13536 int move_direction = player->MovDir;
13537 int enter_side = MV_DIR_OPPOSITE(move_direction);
13538 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13539 CE_PLAYER_COLLECTS_X);
13541 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13542 player->index_bit, enter_side);
13544 player->last_removed_element = EL_UNDEFINED;
13547 if (!player->active)
13548 RemovePlayer(player);
13551 if (level.use_step_counter)
13552 CheckLevelTime_StepCounter();
13554 if (tape.single_step && tape.recording && !tape.pausing &&
13555 !player->programmed_action)
13556 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13558 if (!player->programmed_action)
13559 CheckSaveEngineSnapshot(player);
13563 void ScrollScreen(struct PlayerInfo *player, int mode)
13565 static DelayCounter screen_frame_counter = { 0 };
13567 if (mode == SCROLL_INIT)
13569 // set scrolling step size according to actual player's moving speed
13570 ScrollStepSize = TILEX / player->move_delay_value;
13572 screen_frame_counter.count = FrameCounter;
13573 screen_frame_counter.value = 1;
13575 ScreenMovDir = player->MovDir;
13576 ScreenMovPos = player->MovPos;
13577 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13580 else if (!FrameReached(&screen_frame_counter))
13585 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13586 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13587 redraw_mask |= REDRAW_FIELD;
13590 ScreenMovDir = MV_NONE;
13593 void CheckNextToConditions(int x, int y)
13595 int element = Tile[x][y];
13597 if (IS_PLAYER(x, y))
13598 TestIfPlayerNextToCustomElement(x, y);
13600 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13601 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13602 TestIfElementNextToCustomElement(x, y);
13605 void TestIfPlayerNextToCustomElement(int x, int y)
13607 struct XY *xy = xy_topdown;
13608 static int trigger_sides[4][2] =
13610 // center side border side
13611 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13612 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13613 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13614 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13618 if (!IS_PLAYER(x, y))
13621 struct PlayerInfo *player = PLAYERINFO(x, y);
13623 if (player->is_moving)
13626 for (i = 0; i < NUM_DIRECTIONS; i++)
13628 int xx = x + xy[i].x;
13629 int yy = y + xy[i].y;
13630 int border_side = trigger_sides[i][1];
13631 int border_element;
13633 if (!IN_LEV_FIELD(xx, yy))
13636 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13637 continue; // center and border element not connected
13639 border_element = Tile[xx][yy];
13641 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13642 player->index_bit, border_side);
13643 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13644 CE_PLAYER_NEXT_TO_X,
13645 player->index_bit, border_side);
13647 /* use player element that is initially defined in the level playfield,
13648 not the player element that corresponds to the runtime player number
13649 (example: a level that contains EL_PLAYER_3 as the only player would
13650 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13652 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13653 CE_NEXT_TO_X, border_side);
13657 void TestIfPlayerTouchesCustomElement(int x, int y)
13659 struct XY *xy = xy_topdown;
13660 static int trigger_sides[4][2] =
13662 // center side border side
13663 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13664 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13665 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13666 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13668 static int touch_dir[4] =
13670 MV_LEFT | MV_RIGHT,
13675 int center_element = Tile[x][y]; // should always be non-moving!
13678 for (i = 0; i < NUM_DIRECTIONS; i++)
13680 int xx = x + xy[i].x;
13681 int yy = y + xy[i].y;
13682 int center_side = trigger_sides[i][0];
13683 int border_side = trigger_sides[i][1];
13684 int border_element;
13686 if (!IN_LEV_FIELD(xx, yy))
13689 if (IS_PLAYER(x, y)) // player found at center element
13691 struct PlayerInfo *player = PLAYERINFO(x, y);
13693 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13694 border_element = Tile[xx][yy]; // may be moving!
13695 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13696 border_element = Tile[xx][yy];
13697 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13698 border_element = MovingOrBlocked2Element(xx, yy);
13700 continue; // center and border element do not touch
13702 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13703 player->index_bit, border_side);
13704 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13705 CE_PLAYER_TOUCHES_X,
13706 player->index_bit, border_side);
13709 /* use player element that is initially defined in the level playfield,
13710 not the player element that corresponds to the runtime player number
13711 (example: a level that contains EL_PLAYER_3 as the only player would
13712 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13713 int player_element = PLAYERINFO(x, y)->initial_element;
13715 // as element "X" is the player here, check opposite (center) side
13716 CheckElementChangeBySide(xx, yy, border_element, player_element,
13717 CE_TOUCHING_X, center_side);
13720 else if (IS_PLAYER(xx, yy)) // player found at border element
13722 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13724 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13726 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13727 continue; // center and border element do not touch
13730 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13731 player->index_bit, center_side);
13732 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13733 CE_PLAYER_TOUCHES_X,
13734 player->index_bit, center_side);
13737 /* use player element that is initially defined in the level playfield,
13738 not the player element that corresponds to the runtime player number
13739 (example: a level that contains EL_PLAYER_3 as the only player would
13740 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13741 int player_element = PLAYERINFO(xx, yy)->initial_element;
13743 // as element "X" is the player here, check opposite (border) side
13744 CheckElementChangeBySide(x, y, center_element, player_element,
13745 CE_TOUCHING_X, border_side);
13753 void TestIfElementNextToCustomElement(int x, int y)
13755 struct XY *xy = xy_topdown;
13756 static int trigger_sides[4][2] =
13758 // center side border side
13759 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13760 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13761 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13762 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13764 int center_element = Tile[x][y]; // should always be non-moving!
13767 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13770 for (i = 0; i < NUM_DIRECTIONS; i++)
13772 int xx = x + xy[i].x;
13773 int yy = y + xy[i].y;
13774 int border_side = trigger_sides[i][1];
13775 int border_element;
13777 if (!IN_LEV_FIELD(xx, yy))
13780 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13781 continue; // center and border element not connected
13783 border_element = Tile[xx][yy];
13785 // check for change of center element (but change it only once)
13786 if (CheckElementChangeBySide(x, y, center_element, border_element,
13787 CE_NEXT_TO_X, border_side))
13792 void TestIfElementTouchesCustomElement(int x, int y)
13794 struct XY *xy = xy_topdown;
13795 static int trigger_sides[4][2] =
13797 // center side border side
13798 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13799 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13800 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13801 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13803 static int touch_dir[4] =
13805 MV_LEFT | MV_RIGHT,
13810 boolean change_center_element = FALSE;
13811 int center_element = Tile[x][y]; // should always be non-moving!
13812 int border_element_old[NUM_DIRECTIONS];
13815 for (i = 0; i < NUM_DIRECTIONS; i++)
13817 int xx = x + xy[i].x;
13818 int yy = y + xy[i].y;
13819 int border_element;
13821 border_element_old[i] = -1;
13823 if (!IN_LEV_FIELD(xx, yy))
13826 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13827 border_element = Tile[xx][yy]; // may be moving!
13828 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13829 border_element = Tile[xx][yy];
13830 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13831 border_element = MovingOrBlocked2Element(xx, yy);
13833 continue; // center and border element do not touch
13835 border_element_old[i] = border_element;
13838 for (i = 0; i < NUM_DIRECTIONS; i++)
13840 int xx = x + xy[i].x;
13841 int yy = y + xy[i].y;
13842 int center_side = trigger_sides[i][0];
13843 int border_element = border_element_old[i];
13845 if (border_element == -1)
13848 // check for change of border element
13849 CheckElementChangeBySide(xx, yy, border_element, center_element,
13850 CE_TOUCHING_X, center_side);
13852 // (center element cannot be player, so we don't have to check this here)
13855 for (i = 0; i < NUM_DIRECTIONS; i++)
13857 int xx = x + xy[i].x;
13858 int yy = y + xy[i].y;
13859 int border_side = trigger_sides[i][1];
13860 int border_element = border_element_old[i];
13862 if (border_element == -1)
13865 // check for change of center element (but change it only once)
13866 if (!change_center_element)
13867 change_center_element =
13868 CheckElementChangeBySide(x, y, center_element, border_element,
13869 CE_TOUCHING_X, border_side);
13871 if (IS_PLAYER(xx, yy))
13873 /* use player element that is initially defined in the level playfield,
13874 not the player element that corresponds to the runtime player number
13875 (example: a level that contains EL_PLAYER_3 as the only player would
13876 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13877 int player_element = PLAYERINFO(xx, yy)->initial_element;
13879 // as element "X" is the player here, check opposite (border) side
13880 CheckElementChangeBySide(x, y, center_element, player_element,
13881 CE_TOUCHING_X, border_side);
13886 void TestIfElementHitsCustomElement(int x, int y, int direction)
13888 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13889 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13890 int hitx = x + dx, hity = y + dy;
13891 int hitting_element = Tile[x][y];
13892 int touched_element;
13894 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13897 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13898 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13900 if (IN_LEV_FIELD(hitx, hity))
13902 int opposite_direction = MV_DIR_OPPOSITE(direction);
13903 int hitting_side = direction;
13904 int touched_side = opposite_direction;
13905 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13906 MovDir[hitx][hity] != direction ||
13907 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13913 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13914 CE_HITTING_X, touched_side);
13916 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13917 CE_HIT_BY_X, hitting_side);
13919 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13920 CE_HIT_BY_SOMETHING, opposite_direction);
13922 if (IS_PLAYER(hitx, hity))
13924 /* use player element that is initially defined in the level playfield,
13925 not the player element that corresponds to the runtime player number
13926 (example: a level that contains EL_PLAYER_3 as the only player would
13927 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13928 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13930 CheckElementChangeBySide(x, y, hitting_element, player_element,
13931 CE_HITTING_X, touched_side);
13936 // "hitting something" is also true when hitting the playfield border
13937 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13938 CE_HITTING_SOMETHING, direction);
13941 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13943 int i, kill_x = -1, kill_y = -1;
13945 int bad_element = -1;
13946 struct XY *test_xy = xy_topdown;
13947 static int test_dir[4] =
13955 for (i = 0; i < NUM_DIRECTIONS; i++)
13957 int test_x, test_y, test_move_dir, test_element;
13959 test_x = good_x + test_xy[i].x;
13960 test_y = good_y + test_xy[i].y;
13962 if (!IN_LEV_FIELD(test_x, test_y))
13966 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13968 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13970 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13971 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13973 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13974 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13978 bad_element = test_element;
13984 if (kill_x != -1 || kill_y != -1)
13986 if (IS_PLAYER(good_x, good_y))
13988 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13990 if (player->shield_deadly_time_left > 0 &&
13991 !IS_INDESTRUCTIBLE(bad_element))
13992 Bang(kill_x, kill_y);
13993 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13994 KillPlayer(player);
13997 Bang(good_x, good_y);
14001 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
14003 int i, kill_x = -1, kill_y = -1;
14004 int bad_element = Tile[bad_x][bad_y];
14005 struct XY *test_xy = xy_topdown;
14006 static int touch_dir[4] =
14008 MV_LEFT | MV_RIGHT,
14013 static int test_dir[4] =
14021 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
14024 for (i = 0; i < NUM_DIRECTIONS; i++)
14026 int test_x, test_y, test_move_dir, test_element;
14028 test_x = bad_x + test_xy[i].x;
14029 test_y = bad_y + test_xy[i].y;
14031 if (!IN_LEV_FIELD(test_x, test_y))
14035 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14037 test_element = Tile[test_x][test_y];
14039 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
14040 2nd case: DONT_TOUCH style bad thing does not move away from good thing
14042 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
14043 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
14045 // good thing is player or penguin that does not move away
14046 if (IS_PLAYER(test_x, test_y))
14048 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14050 if (bad_element == EL_ROBOT && player->is_moving)
14051 continue; // robot does not kill player if he is moving
14053 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
14055 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
14056 continue; // center and border element do not touch
14064 else if (test_element == EL_PENGUIN)
14074 if (kill_x != -1 || kill_y != -1)
14076 if (IS_PLAYER(kill_x, kill_y))
14078 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14080 if (player->shield_deadly_time_left > 0 &&
14081 !IS_INDESTRUCTIBLE(bad_element))
14082 Bang(bad_x, bad_y);
14083 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14084 KillPlayer(player);
14087 Bang(kill_x, kill_y);
14091 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
14093 int bad_element = Tile[bad_x][bad_y];
14094 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
14095 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
14096 int test_x = bad_x + dx, test_y = bad_y + dy;
14097 int test_move_dir, test_element;
14098 int kill_x = -1, kill_y = -1;
14100 if (!IN_LEV_FIELD(test_x, test_y))
14104 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14106 test_element = Tile[test_x][test_y];
14108 if (test_move_dir != bad_move_dir)
14110 // good thing can be player or penguin that does not move away
14111 if (IS_PLAYER(test_x, test_y))
14113 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14115 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
14116 player as being hit when he is moving towards the bad thing, because
14117 the "get hit by" condition would be lost after the player stops) */
14118 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
14119 return; // player moves away from bad thing
14124 else if (test_element == EL_PENGUIN)
14131 if (kill_x != -1 || kill_y != -1)
14133 if (IS_PLAYER(kill_x, kill_y))
14135 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14137 if (player->shield_deadly_time_left > 0 &&
14138 !IS_INDESTRUCTIBLE(bad_element))
14139 Bang(bad_x, bad_y);
14140 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14141 KillPlayer(player);
14144 Bang(kill_x, kill_y);
14148 void TestIfPlayerTouchesBadThing(int x, int y)
14150 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14153 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
14155 TestIfGoodThingHitsBadThing(x, y, move_dir);
14158 void TestIfBadThingTouchesPlayer(int x, int y)
14160 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14163 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
14165 TestIfBadThingHitsGoodThing(x, y, move_dir);
14168 void TestIfFriendTouchesBadThing(int x, int y)
14170 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14173 void TestIfBadThingTouchesFriend(int x, int y)
14175 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14178 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
14180 int i, kill_x = bad_x, kill_y = bad_y;
14181 struct XY *xy = xy_topdown;
14183 for (i = 0; i < NUM_DIRECTIONS; i++)
14187 x = bad_x + xy[i].x;
14188 y = bad_y + xy[i].y;
14189 if (!IN_LEV_FIELD(x, y))
14192 element = Tile[x][y];
14193 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14194 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14202 if (kill_x != bad_x || kill_y != bad_y)
14203 Bang(bad_x, bad_y);
14206 void KillPlayer(struct PlayerInfo *player)
14208 int jx = player->jx, jy = player->jy;
14210 if (!player->active)
14214 Debug("game:playing:KillPlayer",
14215 "0: killed == %d, active == %d, reanimated == %d",
14216 player->killed, player->active, player->reanimated);
14219 /* the following code was introduced to prevent an infinite loop when calling
14221 -> CheckTriggeredElementChangeExt()
14222 -> ExecuteCustomElementAction()
14224 -> (infinitely repeating the above sequence of function calls)
14225 which occurs when killing the player while having a CE with the setting
14226 "kill player X when explosion of <player X>"; the solution using a new
14227 field "player->killed" was chosen for backwards compatibility, although
14228 clever use of the fields "player->active" etc. would probably also work */
14230 if (player->killed)
14234 player->killed = TRUE;
14236 // remove accessible field at the player's position
14237 RemoveField(jx, jy);
14239 // deactivate shield (else Bang()/Explode() would not work right)
14240 player->shield_normal_time_left = 0;
14241 player->shield_deadly_time_left = 0;
14244 Debug("game:playing:KillPlayer",
14245 "1: killed == %d, active == %d, reanimated == %d",
14246 player->killed, player->active, player->reanimated);
14252 Debug("game:playing:KillPlayer",
14253 "2: killed == %d, active == %d, reanimated == %d",
14254 player->killed, player->active, player->reanimated);
14257 if (player->reanimated) // killed player may have been reanimated
14258 player->killed = player->reanimated = FALSE;
14260 BuryPlayer(player);
14263 static void KillPlayerUnlessEnemyProtected(int x, int y)
14265 if (!PLAYER_ENEMY_PROTECTED(x, y))
14266 KillPlayer(PLAYERINFO(x, y));
14269 static void KillPlayerUnlessExplosionProtected(int x, int y)
14271 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14272 KillPlayer(PLAYERINFO(x, y));
14275 void BuryPlayer(struct PlayerInfo *player)
14277 int jx = player->jx, jy = player->jy;
14279 if (!player->active)
14282 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14284 RemovePlayer(player);
14286 player->buried = TRUE;
14288 if (game.all_players_gone)
14289 game.GameOver = TRUE;
14292 void RemovePlayer(struct PlayerInfo *player)
14294 int jx = player->jx, jy = player->jy;
14295 int i, found = FALSE;
14297 player->present = FALSE;
14298 player->active = FALSE;
14300 // required for some CE actions (even if the player is not active anymore)
14301 player->MovPos = 0;
14303 if (!ExplodeField[jx][jy])
14304 StorePlayer[jx][jy] = 0;
14306 if (player->is_moving)
14307 TEST_DrawLevelField(player->last_jx, player->last_jy);
14309 for (i = 0; i < MAX_PLAYERS; i++)
14310 if (stored_player[i].active)
14315 game.all_players_gone = TRUE;
14316 game.GameOver = TRUE;
14319 game.exit_x = game.robot_wheel_x = jx;
14320 game.exit_y = game.robot_wheel_y = jy;
14323 void ExitPlayer(struct PlayerInfo *player)
14325 DrawPlayer(player); // needed here only to cleanup last field
14326 RemovePlayer(player);
14328 if (game.players_still_needed > 0)
14329 game.players_still_needed--;
14332 static void SetFieldForSnapping(int x, int y, int element, int direction,
14333 int player_index_bit)
14335 struct ElementInfo *ei = &element_info[element];
14336 int direction_bit = MV_DIR_TO_BIT(direction);
14337 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14338 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14339 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14341 Tile[x][y] = EL_ELEMENT_SNAPPING;
14342 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14343 MovDir[x][y] = direction;
14344 Store[x][y] = element;
14345 Store2[x][y] = player_index_bit;
14347 ResetGfxAnimation(x, y);
14349 GfxElement[x][y] = element;
14350 GfxAction[x][y] = action;
14351 GfxDir[x][y] = direction;
14352 GfxFrame[x][y] = -1;
14355 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14356 int player_index_bit)
14358 TestIfElementTouchesCustomElement(x, y); // for empty space
14360 if (level.finish_dig_collect)
14362 int dig_side = MV_DIR_OPPOSITE(direction);
14363 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14364 CE_PLAYER_COLLECTS_X);
14366 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14367 player_index_bit, dig_side);
14368 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14369 player_index_bit, dig_side);
14374 =============================================================================
14375 checkDiagonalPushing()
14376 -----------------------------------------------------------------------------
14377 check if diagonal input device direction results in pushing of object
14378 (by checking if the alternative direction is walkable, diggable, ...)
14379 =============================================================================
14382 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14383 int x, int y, int real_dx, int real_dy)
14385 int jx, jy, dx, dy, xx, yy;
14387 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14390 // diagonal direction: check alternative direction
14395 xx = jx + (dx == 0 ? real_dx : 0);
14396 yy = jy + (dy == 0 ? real_dy : 0);
14398 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14402 =============================================================================
14404 -----------------------------------------------------------------------------
14405 x, y: field next to player (non-diagonal) to try to dig to
14406 real_dx, real_dy: direction as read from input device (can be diagonal)
14407 =============================================================================
14410 static int DigField(struct PlayerInfo *player,
14411 int oldx, int oldy, int x, int y,
14412 int real_dx, int real_dy, int mode)
14414 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14415 boolean player_was_pushing = player->is_pushing;
14416 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14417 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14418 int jx = oldx, jy = oldy;
14419 int dx = x - jx, dy = y - jy;
14420 int nextx = x + dx, nexty = y + dy;
14421 int move_direction = (dx == -1 ? MV_LEFT :
14422 dx == +1 ? MV_RIGHT :
14424 dy == +1 ? MV_DOWN : MV_NONE);
14425 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14426 int dig_side = MV_DIR_OPPOSITE(move_direction);
14427 int old_element = Tile[jx][jy];
14428 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14431 if (is_player) // function can also be called by EL_PENGUIN
14433 if (player->MovPos == 0)
14435 player->is_digging = FALSE;
14436 player->is_collecting = FALSE;
14439 if (player->MovPos == 0) // last pushing move finished
14440 player->is_pushing = FALSE;
14442 if (mode == DF_NO_PUSH) // player just stopped pushing
14444 player->is_switching = FALSE;
14445 player->push_delay = -1;
14447 return MP_NO_ACTION;
14450 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14451 old_element = Back[jx][jy];
14453 // in case of element dropped at player position, check background
14454 else if (Back[jx][jy] != EL_EMPTY &&
14455 game.engine_version >= VERSION_IDENT(2,2,0,0))
14456 old_element = Back[jx][jy];
14458 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14459 return MP_NO_ACTION; // field has no opening in this direction
14461 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction))
14462 return MP_NO_ACTION; // field has no opening in this direction
14464 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14468 Tile[jx][jy] = player->artwork_element;
14469 InitMovingField(jx, jy, MV_DOWN);
14470 Store[jx][jy] = EL_ACID;
14471 ContinueMoving(jx, jy);
14472 BuryPlayer(player);
14474 return MP_DONT_RUN_INTO;
14477 if (player_can_move && DONT_RUN_INTO(element))
14479 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14481 return MP_DONT_RUN_INTO;
14484 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14485 return MP_NO_ACTION;
14487 collect_count = element_info[element].collect_count_initial;
14489 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14490 return MP_NO_ACTION;
14492 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14493 player_can_move = player_can_move_or_snap;
14495 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14496 game.engine_version >= VERSION_IDENT(2,2,0,0))
14498 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14499 player->index_bit, dig_side);
14500 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14501 player->index_bit, dig_side);
14503 if (element == EL_DC_LANDMINE)
14506 if (Tile[x][y] != element) // field changed by snapping
14509 return MP_NO_ACTION;
14512 if (player->gravity && is_player && !player->is_auto_moving &&
14513 canFallDown(player) && move_direction != MV_DOWN &&
14514 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14515 return MP_NO_ACTION; // player cannot walk here due to gravity
14517 if (player_can_move &&
14518 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14520 int sound_element = SND_ELEMENT(element);
14521 int sound_action = ACTION_WALKING;
14523 if (IS_RND_GATE(element))
14525 if (!player->key[RND_GATE_NR(element)])
14526 return MP_NO_ACTION;
14528 else if (IS_RND_GATE_GRAY(element))
14530 if (!player->key[RND_GATE_GRAY_NR(element)])
14531 return MP_NO_ACTION;
14533 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14535 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14536 return MP_NO_ACTION;
14538 else if (element == EL_EXIT_OPEN ||
14539 element == EL_EM_EXIT_OPEN ||
14540 element == EL_EM_EXIT_OPENING ||
14541 element == EL_STEEL_EXIT_OPEN ||
14542 element == EL_EM_STEEL_EXIT_OPEN ||
14543 element == EL_EM_STEEL_EXIT_OPENING ||
14544 element == EL_SP_EXIT_OPEN ||
14545 element == EL_SP_EXIT_OPENING)
14547 sound_action = ACTION_PASSING; // player is passing exit
14549 else if (element == EL_EMPTY)
14551 sound_action = ACTION_MOVING; // nothing to walk on
14554 // play sound from background or player, whatever is available
14555 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14556 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14558 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14560 else if (player_can_move &&
14561 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14563 if (!ACCESS_FROM(element, opposite_direction))
14564 return MP_NO_ACTION; // field not accessible from this direction
14566 if (CAN_MOVE(element)) // only fixed elements can be passed!
14567 return MP_NO_ACTION;
14569 if (IS_EM_GATE(element))
14571 if (!player->key[EM_GATE_NR(element)])
14572 return MP_NO_ACTION;
14574 else if (IS_EM_GATE_GRAY(element))
14576 if (!player->key[EM_GATE_GRAY_NR(element)])
14577 return MP_NO_ACTION;
14579 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14581 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14582 return MP_NO_ACTION;
14584 else if (IS_EMC_GATE(element))
14586 if (!player->key[EMC_GATE_NR(element)])
14587 return MP_NO_ACTION;
14589 else if (IS_EMC_GATE_GRAY(element))
14591 if (!player->key[EMC_GATE_GRAY_NR(element)])
14592 return MP_NO_ACTION;
14594 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14596 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14597 return MP_NO_ACTION;
14599 else if (element == EL_DC_GATE_WHITE ||
14600 element == EL_DC_GATE_WHITE_GRAY ||
14601 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14603 if (player->num_white_keys == 0)
14604 return MP_NO_ACTION;
14606 player->num_white_keys--;
14608 else if (IS_SP_PORT(element))
14610 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14611 element == EL_SP_GRAVITY_PORT_RIGHT ||
14612 element == EL_SP_GRAVITY_PORT_UP ||
14613 element == EL_SP_GRAVITY_PORT_DOWN)
14614 player->gravity = !player->gravity;
14615 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14616 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14617 element == EL_SP_GRAVITY_ON_PORT_UP ||
14618 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14619 player->gravity = TRUE;
14620 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14621 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14622 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14623 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14624 player->gravity = FALSE;
14627 // automatically move to the next field with double speed
14628 player->programmed_action = move_direction;
14630 if (player->move_delay_reset_counter == 0)
14632 player->move_delay_reset_counter = 2; // two double speed steps
14634 DOUBLE_PLAYER_SPEED(player);
14637 PlayLevelSoundAction(x, y, ACTION_PASSING);
14639 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14643 if (mode != DF_SNAP)
14645 GfxElement[x][y] = GFX_ELEMENT(element);
14646 player->is_digging = TRUE;
14649 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14651 // use old behaviour for old levels (digging)
14652 if (!level.finish_dig_collect)
14654 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14655 player->index_bit, dig_side);
14657 // if digging triggered player relocation, finish digging tile
14658 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14659 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14662 if (mode == DF_SNAP)
14664 if (level.block_snap_field)
14665 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14667 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14669 // use old behaviour for old levels (snapping)
14670 if (!level.finish_dig_collect)
14671 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14672 player->index_bit, dig_side);
14675 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14679 if (is_player && mode != DF_SNAP)
14681 GfxElement[x][y] = element;
14682 player->is_collecting = TRUE;
14685 if (element == EL_SPEED_PILL)
14687 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14689 else if (element == EL_EXTRA_TIME && level.time > 0)
14691 TimeLeft += level.extra_time;
14693 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14695 DisplayGameControlValues();
14697 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14699 int shield_time = (element == EL_SHIELD_DEADLY ?
14700 level.shield_deadly_time :
14701 level.shield_normal_time);
14703 player->shield_normal_time_left += shield_time;
14704 if (element == EL_SHIELD_DEADLY)
14705 player->shield_deadly_time_left += shield_time;
14707 else if (element == EL_DYNAMITE ||
14708 element == EL_EM_DYNAMITE ||
14709 element == EL_SP_DISK_RED)
14711 if (player->inventory_size < MAX_INVENTORY_SIZE)
14712 player->inventory_element[player->inventory_size++] = element;
14714 DrawGameDoorValues();
14716 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14718 player->dynabomb_count++;
14719 player->dynabombs_left++;
14721 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14723 player->dynabomb_size++;
14725 else if (element == EL_DYNABOMB_INCREASE_POWER)
14727 player->dynabomb_xl = TRUE;
14729 else if (IS_KEY(element))
14731 player->key[KEY_NR(element)] = TRUE;
14733 DrawGameDoorValues();
14735 else if (element == EL_DC_KEY_WHITE)
14737 player->num_white_keys++;
14739 // display white keys?
14740 // DrawGameDoorValues();
14742 else if (IS_ENVELOPE(element))
14744 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14746 if (!wait_for_snapping)
14747 player->show_envelope = element;
14749 else if (element == EL_EMC_LENSES)
14751 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14753 RedrawAllInvisibleElementsForLenses();
14755 else if (element == EL_EMC_MAGNIFIER)
14757 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14759 RedrawAllInvisibleElementsForMagnifier();
14761 else if (IS_DROPPABLE(element) ||
14762 IS_THROWABLE(element)) // can be collected and dropped
14766 if (collect_count == 0)
14767 player->inventory_infinite_element = element;
14769 for (i = 0; i < collect_count; i++)
14770 if (player->inventory_size < MAX_INVENTORY_SIZE)
14771 player->inventory_element[player->inventory_size++] = element;
14773 DrawGameDoorValues();
14775 else if (collect_count > 0)
14777 game.gems_still_needed -= collect_count;
14778 if (game.gems_still_needed < 0)
14779 game.gems_still_needed = 0;
14781 game.snapshot.collected_item = TRUE;
14783 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14785 DisplayGameControlValues();
14788 RaiseScoreElement(element);
14789 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14791 // use old behaviour for old levels (collecting)
14792 if (!level.finish_dig_collect && is_player)
14794 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14795 player->index_bit, dig_side);
14797 // if collecting triggered player relocation, finish collecting tile
14798 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14799 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14802 if (mode == DF_SNAP)
14804 if (level.block_snap_field)
14805 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14807 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14809 // use old behaviour for old levels (snapping)
14810 if (!level.finish_dig_collect)
14811 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14812 player->index_bit, dig_side);
14815 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14817 if (mode == DF_SNAP && element != EL_BD_ROCK)
14818 return MP_NO_ACTION;
14820 if (CAN_FALL(element) && dy)
14821 return MP_NO_ACTION;
14823 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14824 !(element == EL_SPRING && level.use_spring_bug))
14825 return MP_NO_ACTION;
14827 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14828 ((move_direction & MV_VERTICAL &&
14829 ((element_info[element].move_pattern & MV_LEFT &&
14830 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14831 (element_info[element].move_pattern & MV_RIGHT &&
14832 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14833 (move_direction & MV_HORIZONTAL &&
14834 ((element_info[element].move_pattern & MV_UP &&
14835 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14836 (element_info[element].move_pattern & MV_DOWN &&
14837 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14838 return MP_NO_ACTION;
14840 // do not push elements already moving away faster than player
14841 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14842 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14843 return MP_NO_ACTION;
14845 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14847 if (player->push_delay_value == -1 || !player_was_pushing)
14848 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14850 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14852 if (player->push_delay_value == -1)
14853 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14855 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14857 if (!player->is_pushing)
14858 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14861 player->is_pushing = TRUE;
14862 player->is_active = TRUE;
14864 if (!(IN_LEV_FIELD(nextx, nexty) &&
14865 (IS_FREE(nextx, nexty) ||
14866 (IS_SB_ELEMENT(element) &&
14867 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14868 (IS_CUSTOM_ELEMENT(element) &&
14869 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14870 return MP_NO_ACTION;
14872 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14873 return MP_NO_ACTION;
14875 if (player->push_delay == -1) // new pushing; restart delay
14876 player->push_delay = 0;
14878 if (player->push_delay < player->push_delay_value &&
14879 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14880 element != EL_SPRING && element != EL_BALLOON)
14882 // make sure that there is no move delay before next try to push
14883 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14884 player->move_delay = 0;
14886 return MP_NO_ACTION;
14889 if (IS_CUSTOM_ELEMENT(element) &&
14890 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14892 if (!DigFieldByCE(nextx, nexty, element))
14893 return MP_NO_ACTION;
14896 if (IS_SB_ELEMENT(element))
14898 boolean sokoban_task_solved = FALSE;
14900 if (element == EL_SOKOBAN_FIELD_FULL)
14902 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14904 IncrementSokobanFieldsNeeded();
14905 IncrementSokobanObjectsNeeded();
14908 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14910 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14912 DecrementSokobanFieldsNeeded();
14913 DecrementSokobanObjectsNeeded();
14915 // sokoban object was pushed from empty field to sokoban field
14916 if (Back[x][y] == EL_EMPTY)
14917 sokoban_task_solved = TRUE;
14920 Tile[x][y] = EL_SOKOBAN_OBJECT;
14922 if (Back[x][y] == Back[nextx][nexty])
14923 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14924 else if (Back[x][y] != 0)
14925 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14928 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14931 if (sokoban_task_solved &&
14932 game.sokoban_fields_still_needed == 0 &&
14933 game.sokoban_objects_still_needed == 0 &&
14934 level.auto_exit_sokoban)
14936 game.players_still_needed = 0;
14940 PlaySound(SND_GAME_SOKOBAN_SOLVING);
14944 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14946 InitMovingField(x, y, move_direction);
14947 GfxAction[x][y] = ACTION_PUSHING;
14949 if (mode == DF_SNAP)
14950 ContinueMoving(x, y);
14952 MovPos[x][y] = (dx != 0 ? dx : dy);
14954 Pushed[x][y] = TRUE;
14955 Pushed[nextx][nexty] = TRUE;
14957 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14958 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14960 player->push_delay_value = -1; // get new value later
14962 // check for element change _after_ element has been pushed
14963 if (game.use_change_when_pushing_bug)
14965 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14966 player->index_bit, dig_side);
14967 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14968 player->index_bit, dig_side);
14971 else if (IS_SWITCHABLE(element))
14973 if (PLAYER_SWITCHING(player, x, y))
14975 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14976 player->index_bit, dig_side);
14981 player->is_switching = TRUE;
14982 player->switch_x = x;
14983 player->switch_y = y;
14985 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14987 if (element == EL_ROBOT_WHEEL)
14989 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14991 game.robot_wheel_x = x;
14992 game.robot_wheel_y = y;
14993 game.robot_wheel_active = TRUE;
14995 TEST_DrawLevelField(x, y);
14997 else if (element == EL_SP_TERMINAL)
15001 SCAN_PLAYFIELD(xx, yy)
15003 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
15007 else if (Tile[xx][yy] == EL_SP_TERMINAL)
15009 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
15011 ResetGfxAnimation(xx, yy);
15012 TEST_DrawLevelField(xx, yy);
15016 else if (IS_BELT_SWITCH(element))
15018 ToggleBeltSwitch(x, y);
15020 else if (element == EL_SWITCHGATE_SWITCH_UP ||
15021 element == EL_SWITCHGATE_SWITCH_DOWN ||
15022 element == EL_DC_SWITCHGATE_SWITCH_UP ||
15023 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
15025 ToggleSwitchgateSwitch();
15027 else if (element == EL_LIGHT_SWITCH ||
15028 element == EL_LIGHT_SWITCH_ACTIVE)
15030 ToggleLightSwitch(x, y);
15032 else if (element == EL_TIMEGATE_SWITCH ||
15033 element == EL_DC_TIMEGATE_SWITCH)
15035 ActivateTimegateSwitch(x, y);
15037 else if (element == EL_BALLOON_SWITCH_LEFT ||
15038 element == EL_BALLOON_SWITCH_RIGHT ||
15039 element == EL_BALLOON_SWITCH_UP ||
15040 element == EL_BALLOON_SWITCH_DOWN ||
15041 element == EL_BALLOON_SWITCH_NONE ||
15042 element == EL_BALLOON_SWITCH_ANY)
15044 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
15045 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
15046 element == EL_BALLOON_SWITCH_UP ? MV_UP :
15047 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
15048 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
15051 else if (element == EL_LAMP)
15053 Tile[x][y] = EL_LAMP_ACTIVE;
15054 game.lights_still_needed--;
15056 ResetGfxAnimation(x, y);
15057 TEST_DrawLevelField(x, y);
15059 else if (element == EL_TIME_ORB_FULL)
15061 Tile[x][y] = EL_TIME_ORB_EMPTY;
15063 if (level.time > 0 || level.use_time_orb_bug)
15065 TimeLeft += level.time_orb_time;
15066 game.no_level_time_limit = FALSE;
15068 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
15070 DisplayGameControlValues();
15073 ResetGfxAnimation(x, y);
15074 TEST_DrawLevelField(x, y);
15076 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
15077 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15081 game.ball_active = !game.ball_active;
15083 SCAN_PLAYFIELD(xx, yy)
15085 int e = Tile[xx][yy];
15087 if (game.ball_active)
15089 if (e == EL_EMC_MAGIC_BALL)
15090 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
15091 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
15092 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
15096 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
15097 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
15098 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15099 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
15104 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15105 player->index_bit, dig_side);
15107 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15108 player->index_bit, dig_side);
15110 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15111 player->index_bit, dig_side);
15117 if (!PLAYER_SWITCHING(player, x, y))
15119 player->is_switching = TRUE;
15120 player->switch_x = x;
15121 player->switch_y = y;
15123 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
15124 player->index_bit, dig_side);
15125 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15126 player->index_bit, dig_side);
15128 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
15129 player->index_bit, dig_side);
15130 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15131 player->index_bit, dig_side);
15134 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
15135 player->index_bit, dig_side);
15136 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15137 player->index_bit, dig_side);
15139 return MP_NO_ACTION;
15142 player->push_delay = -1;
15144 if (is_player) // function can also be called by EL_PENGUIN
15146 if (Tile[x][y] != element) // really digged/collected something
15148 player->is_collecting = !player->is_digging;
15149 player->is_active = TRUE;
15151 player->last_removed_element = element;
15158 static boolean DigFieldByCE(int x, int y, int digging_element)
15160 int element = Tile[x][y];
15162 if (!IS_FREE(x, y))
15164 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
15165 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
15168 // no element can dig solid indestructible elements
15169 if (IS_INDESTRUCTIBLE(element) &&
15170 !IS_DIGGABLE(element) &&
15171 !IS_COLLECTIBLE(element))
15174 if (AmoebaNr[x][y] &&
15175 (element == EL_AMOEBA_FULL ||
15176 element == EL_BD_AMOEBA ||
15177 element == EL_AMOEBA_GROWING))
15179 AmoebaCnt[AmoebaNr[x][y]]--;
15180 AmoebaCnt2[AmoebaNr[x][y]]--;
15183 if (IS_MOVING(x, y))
15184 RemoveMovingField(x, y);
15188 TEST_DrawLevelField(x, y);
15191 // if digged element was about to explode, prevent the explosion
15192 ExplodeField[x][y] = EX_TYPE_NONE;
15194 PlayLevelSoundAction(x, y, action);
15197 Store[x][y] = EL_EMPTY;
15199 // this makes it possible to leave the removed element again
15200 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15201 Store[x][y] = element;
15206 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15208 int jx = player->jx, jy = player->jy;
15209 int x = jx + dx, y = jy + dy;
15210 int snap_direction = (dx == -1 ? MV_LEFT :
15211 dx == +1 ? MV_RIGHT :
15213 dy == +1 ? MV_DOWN : MV_NONE);
15214 boolean can_continue_snapping = (level.continuous_snapping &&
15215 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15217 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15220 if (!player->active || !IN_LEV_FIELD(x, y))
15228 if (player->MovPos == 0)
15229 player->is_pushing = FALSE;
15231 player->is_snapping = FALSE;
15233 if (player->MovPos == 0)
15235 player->is_moving = FALSE;
15236 player->is_digging = FALSE;
15237 player->is_collecting = FALSE;
15243 // prevent snapping with already pressed snap key when not allowed
15244 if (player->is_snapping && !can_continue_snapping)
15247 player->MovDir = snap_direction;
15249 if (player->MovPos == 0)
15251 player->is_moving = FALSE;
15252 player->is_digging = FALSE;
15253 player->is_collecting = FALSE;
15256 player->is_dropping = FALSE;
15257 player->is_dropping_pressed = FALSE;
15258 player->drop_pressed_delay = 0;
15260 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15263 player->is_snapping = TRUE;
15264 player->is_active = TRUE;
15266 if (player->MovPos == 0)
15268 player->is_moving = FALSE;
15269 player->is_digging = FALSE;
15270 player->is_collecting = FALSE;
15273 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15274 TEST_DrawLevelField(player->last_jx, player->last_jy);
15276 TEST_DrawLevelField(x, y);
15281 static boolean DropElement(struct PlayerInfo *player)
15283 int old_element, new_element;
15284 int dropx = player->jx, dropy = player->jy;
15285 int drop_direction = player->MovDir;
15286 int drop_side = drop_direction;
15287 int drop_element = get_next_dropped_element(player);
15289 /* do not drop an element on top of another element; when holding drop key
15290 pressed without moving, dropped element must move away before the next
15291 element can be dropped (this is especially important if the next element
15292 is dynamite, which can be placed on background for historical reasons) */
15293 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15296 if (IS_THROWABLE(drop_element))
15298 dropx += GET_DX_FROM_DIR(drop_direction);
15299 dropy += GET_DY_FROM_DIR(drop_direction);
15301 if (!IN_LEV_FIELD(dropx, dropy))
15305 old_element = Tile[dropx][dropy]; // old element at dropping position
15306 new_element = drop_element; // default: no change when dropping
15308 // check if player is active, not moving and ready to drop
15309 if (!player->active || player->MovPos || player->drop_delay > 0)
15312 // check if player has anything that can be dropped
15313 if (new_element == EL_UNDEFINED)
15316 // only set if player has anything that can be dropped
15317 player->is_dropping_pressed = TRUE;
15319 // check if drop key was pressed long enough for EM style dynamite
15320 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15323 // check if anything can be dropped at the current position
15324 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15327 // collected custom elements can only be dropped on empty fields
15328 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15331 if (old_element != EL_EMPTY)
15332 Back[dropx][dropy] = old_element; // store old element on this field
15334 ResetGfxAnimation(dropx, dropy);
15335 ResetRandomAnimationValue(dropx, dropy);
15337 if (player->inventory_size > 0 ||
15338 player->inventory_infinite_element != EL_UNDEFINED)
15340 if (player->inventory_size > 0)
15342 player->inventory_size--;
15344 DrawGameDoorValues();
15346 if (new_element == EL_DYNAMITE)
15347 new_element = EL_DYNAMITE_ACTIVE;
15348 else if (new_element == EL_EM_DYNAMITE)
15349 new_element = EL_EM_DYNAMITE_ACTIVE;
15350 else if (new_element == EL_SP_DISK_RED)
15351 new_element = EL_SP_DISK_RED_ACTIVE;
15354 Tile[dropx][dropy] = new_element;
15356 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15357 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15358 el2img(Tile[dropx][dropy]), 0);
15360 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15362 // needed if previous element just changed to "empty" in the last frame
15363 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15365 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15366 player->index_bit, drop_side);
15367 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15369 player->index_bit, drop_side);
15371 TestIfElementTouchesCustomElement(dropx, dropy);
15373 else // player is dropping a dyna bomb
15375 player->dynabombs_left--;
15377 Tile[dropx][dropy] = new_element;
15379 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15380 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15381 el2img(Tile[dropx][dropy]), 0);
15383 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15386 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15387 InitField_WithBug1(dropx, dropy, FALSE);
15389 new_element = Tile[dropx][dropy]; // element might have changed
15391 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15392 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15394 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15395 MovDir[dropx][dropy] = drop_direction;
15397 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15399 // do not cause impact style collision by dropping elements that can fall
15400 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15403 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15404 player->is_dropping = TRUE;
15406 player->drop_pressed_delay = 0;
15407 player->is_dropping_pressed = FALSE;
15409 player->drop_x = dropx;
15410 player->drop_y = dropy;
15415 // ----------------------------------------------------------------------------
15416 // game sound playing functions
15417 // ----------------------------------------------------------------------------
15419 static int *loop_sound_frame = NULL;
15420 static int *loop_sound_volume = NULL;
15422 void InitPlayLevelSound(void)
15424 int num_sounds = getSoundListSize();
15426 checked_free(loop_sound_frame);
15427 checked_free(loop_sound_volume);
15429 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15430 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15433 static void PlayLevelSoundExt(int x, int y, int nr, boolean is_loop_sound)
15435 int sx = SCREENX(x), sy = SCREENY(y);
15436 int volume, stereo_position;
15437 int max_distance = 8;
15438 int type = (is_loop_sound ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15440 if ((!setup.sound_simple && !is_loop_sound) ||
15441 (!setup.sound_loops && is_loop_sound))
15444 if (!IN_LEV_FIELD(x, y) ||
15445 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15446 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15449 volume = SOUND_MAX_VOLUME;
15451 if (!IN_SCR_FIELD(sx, sy))
15453 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15454 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15456 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15459 stereo_position = (SOUND_MAX_LEFT +
15460 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15461 (SCR_FIELDX + 2 * max_distance));
15465 /* This assures that quieter loop sounds do not overwrite louder ones,
15466 while restarting sound volume comparison with each new game frame. */
15468 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15471 loop_sound_volume[nr] = volume;
15472 loop_sound_frame[nr] = FrameCounter;
15475 PlaySoundExt(nr, volume, stereo_position, type);
15478 static void PlayLevelSound(int x, int y, int nr)
15480 PlayLevelSoundExt(x, y, nr, IS_LOOP_SOUND(nr));
15483 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15485 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15486 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15487 y < LEVELY(BY1) ? LEVELY(BY1) :
15488 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15492 static void PlayLevelSoundAction(int x, int y, int action)
15494 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15497 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15499 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15501 if (sound_effect != SND_UNDEFINED)
15502 PlayLevelSound(x, y, sound_effect);
15505 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15508 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15510 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15511 PlayLevelSound(x, y, sound_effect);
15514 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15516 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15518 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15519 PlayLevelSound(x, y, sound_effect);
15522 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15524 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15526 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15527 StopSound(sound_effect);
15530 static int getLevelMusicNr(void)
15532 int level_pos = level_nr - leveldir_current->first_level;
15534 if (levelset.music[level_nr] != MUS_UNDEFINED)
15535 return levelset.music[level_nr]; // from config file
15537 return MAP_NOCONF_MUSIC(level_pos); // from music dir
15540 static void FadeLevelSounds(void)
15545 static void FadeLevelMusic(void)
15547 int music_nr = getLevelMusicNr();
15548 char *curr_music = getCurrentlyPlayingMusicFilename();
15549 char *next_music = getMusicInfoEntryFilename(music_nr);
15551 if (!strEqual(curr_music, next_music))
15555 void FadeLevelSoundsAndMusic(void)
15561 static void PlayLevelMusic(void)
15563 int music_nr = getLevelMusicNr();
15564 char *curr_music = getCurrentlyPlayingMusicFilename();
15565 char *next_music = getMusicInfoEntryFilename(music_nr);
15567 if (!strEqual(curr_music, next_music))
15568 PlayMusicLoop(music_nr);
15571 static int getSoundAction_BD(int sample)
15575 case GD_S_STONE_PUSHING:
15576 case GD_S_MEGA_STONE_PUSHING:
15577 case GD_S_FLYING_STONE_PUSHING:
15578 case GD_S_WAITING_STONE_PUSHING:
15579 case GD_S_CHASING_STONE_PUSHING:
15580 case GD_S_NUT_PUSHING:
15581 case GD_S_NITRO_PACK_PUSHING:
15582 case GD_S_BLADDER_PUSHING:
15583 case GD_S_BOX_PUSHING:
15584 return ACTION_PUSHING;
15586 case GD_S_STONE_FALLING:
15587 case GD_S_MEGA_STONE_FALLING:
15588 case GD_S_FLYING_STONE_FALLING:
15589 case GD_S_NUT_FALLING:
15590 case GD_S_DIRT_BALL_FALLING:
15591 case GD_S_DIRT_LOOSE_FALLING:
15592 case GD_S_NITRO_PACK_FALLING:
15593 case GD_S_FALLING_WALL_FALLING:
15594 return ACTION_FALLING;
15596 case GD_S_STONE_IMPACT:
15597 case GD_S_MEGA_STONE_IMPACT:
15598 case GD_S_FLYING_STONE_IMPACT:
15599 case GD_S_NUT_IMPACT:
15600 case GD_S_DIRT_BALL_IMPACT:
15601 case GD_S_DIRT_LOOSE_IMPACT:
15602 case GD_S_NITRO_PACK_IMPACT:
15603 case GD_S_FALLING_WALL_IMPACT:
15604 return ACTION_IMPACT;
15606 case GD_S_NUT_CRACKING:
15607 return ACTION_BREAKING;
15609 case GD_S_EXPANDING_WALL:
15610 case GD_S_WALL_REAPPEARING:
15613 case GD_S_ACID_SPREADING:
15614 return ACTION_GROWING;
15616 case GD_S_DIAMOND_COLLECTING:
15617 case GD_S_FLYING_DIAMOND_COLLECTING:
15618 case GD_S_SKELETON_COLLECTING:
15619 case GD_S_PNEUMATIC_COLLECTING:
15620 case GD_S_BOMB_COLLECTING:
15621 case GD_S_CLOCK_COLLECTING:
15622 case GD_S_SWEET_COLLECTING:
15623 case GD_S_KEY_COLLECTING:
15624 case GD_S_DIAMOND_KEY_COLLECTING:
15625 return ACTION_COLLECTING;
15627 case GD_S_BOMB_PLACING:
15628 case GD_S_REPLICATOR:
15629 return ACTION_DROPPING;
15631 case GD_S_BLADDER_MOVING:
15632 return ACTION_MOVING;
15634 case GD_S_BLADDER_SPENDER:
15635 case GD_S_BLADDER_CONVERTING:
15636 case GD_S_GRAVITY_CHANGING:
15637 return ACTION_CHANGING;
15639 case GD_S_BITER_EATING:
15640 return ACTION_EATING;
15642 case GD_S_DOOR_OPENING:
15643 case GD_S_CRACKING:
15644 return ACTION_OPENING;
15646 case GD_S_DIRT_WALKING:
15647 return ACTION_DIGGING;
15649 case GD_S_EMPTY_WALKING:
15650 return ACTION_WALKING;
15652 case GD_S_SWITCH_BITER:
15653 case GD_S_SWITCH_CREATURES:
15654 case GD_S_SWITCH_GRAVITY:
15655 case GD_S_SWITCH_EXPANDING:
15656 case GD_S_SWITCH_CONVEYOR:
15657 case GD_S_SWITCH_REPLICATOR:
15658 case GD_S_STIRRING:
15659 return ACTION_ACTIVATING;
15661 case GD_S_TELEPORTER:
15662 return ACTION_PASSING;
15664 case GD_S_EXPLODING:
15665 case GD_S_BOMB_EXPLODING:
15666 case GD_S_GHOST_EXPLODING:
15667 case GD_S_VOODOO_EXPLODING:
15668 case GD_S_NITRO_PACK_EXPLODING:
15669 return ACTION_EXPLODING;
15671 case GD_S_COVERING:
15673 case GD_S_MAGIC_WALL:
15674 case GD_S_PNEUMATIC_HAMMER:
15676 return ACTION_ACTIVE;
15678 case GD_S_DIAMOND_FALLING_RANDOM:
15679 case GD_S_DIAMOND_FALLING_1:
15680 case GD_S_DIAMOND_FALLING_2:
15681 case GD_S_DIAMOND_FALLING_3:
15682 case GD_S_DIAMOND_FALLING_4:
15683 case GD_S_DIAMOND_FALLING_5:
15684 case GD_S_DIAMOND_FALLING_6:
15685 case GD_S_DIAMOND_FALLING_7:
15686 case GD_S_DIAMOND_FALLING_8:
15687 case GD_S_DIAMOND_IMPACT_RANDOM:
15688 case GD_S_DIAMOND_IMPACT_1:
15689 case GD_S_DIAMOND_IMPACT_2:
15690 case GD_S_DIAMOND_IMPACT_3:
15691 case GD_S_DIAMOND_IMPACT_4:
15692 case GD_S_DIAMOND_IMPACT_5:
15693 case GD_S_DIAMOND_IMPACT_6:
15694 case GD_S_DIAMOND_IMPACT_7:
15695 case GD_S_DIAMOND_IMPACT_8:
15696 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15697 case GD_S_FLYING_DIAMOND_FALLING_1:
15698 case GD_S_FLYING_DIAMOND_FALLING_2:
15699 case GD_S_FLYING_DIAMOND_FALLING_3:
15700 case GD_S_FLYING_DIAMOND_FALLING_4:
15701 case GD_S_FLYING_DIAMOND_FALLING_5:
15702 case GD_S_FLYING_DIAMOND_FALLING_6:
15703 case GD_S_FLYING_DIAMOND_FALLING_7:
15704 case GD_S_FLYING_DIAMOND_FALLING_8:
15705 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15706 case GD_S_FLYING_DIAMOND_IMPACT_1:
15707 case GD_S_FLYING_DIAMOND_IMPACT_2:
15708 case GD_S_FLYING_DIAMOND_IMPACT_3:
15709 case GD_S_FLYING_DIAMOND_IMPACT_4:
15710 case GD_S_FLYING_DIAMOND_IMPACT_5:
15711 case GD_S_FLYING_DIAMOND_IMPACT_6:
15712 case GD_S_FLYING_DIAMOND_IMPACT_7:
15713 case GD_S_FLYING_DIAMOND_IMPACT_8:
15714 case GD_S_TIMEOUT_0:
15715 case GD_S_TIMEOUT_1:
15716 case GD_S_TIMEOUT_2:
15717 case GD_S_TIMEOUT_3:
15718 case GD_S_TIMEOUT_4:
15719 case GD_S_TIMEOUT_5:
15720 case GD_S_TIMEOUT_6:
15721 case GD_S_TIMEOUT_7:
15722 case GD_S_TIMEOUT_8:
15723 case GD_S_TIMEOUT_9:
15724 case GD_S_TIMEOUT_10:
15725 case GD_S_BONUS_LIFE:
15726 // trigger special post-processing (and force sound to be non-looping)
15727 return ACTION_OTHER;
15729 case GD_S_AMOEBA_MAGIC:
15730 case GD_S_FINISHED:
15731 // trigger special post-processing (and force sound to be looping)
15732 return ACTION_DEFAULT;
15735 return ACTION_DEFAULT;
15739 static int getSoundEffect_BD(int element_bd, int sample)
15741 int sound_action = getSoundAction_BD(sample);
15742 int sound_effect = element_info[SND_ELEMENT(element_bd)].sound[sound_action];
15746 if (sound_action != ACTION_OTHER &&
15747 sound_action != ACTION_DEFAULT)
15748 return sound_effect;
15750 // special post-processing for some sounds
15753 case GD_S_DIAMOND_FALLING_RANDOM:
15754 case GD_S_DIAMOND_FALLING_1:
15755 case GD_S_DIAMOND_FALLING_2:
15756 case GD_S_DIAMOND_FALLING_3:
15757 case GD_S_DIAMOND_FALLING_4:
15758 case GD_S_DIAMOND_FALLING_5:
15759 case GD_S_DIAMOND_FALLING_6:
15760 case GD_S_DIAMOND_FALLING_7:
15761 case GD_S_DIAMOND_FALLING_8:
15762 nr = (sample == GD_S_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15763 sample - GD_S_DIAMOND_FALLING_1);
15764 sound_effect = SND_BD_DIAMOND_FALLING_RANDOM_1 + nr;
15766 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15767 sound_effect = SND_BD_DIAMOND_FALLING;
15770 case GD_S_DIAMOND_IMPACT_RANDOM:
15771 case GD_S_DIAMOND_IMPACT_1:
15772 case GD_S_DIAMOND_IMPACT_2:
15773 case GD_S_DIAMOND_IMPACT_3:
15774 case GD_S_DIAMOND_IMPACT_4:
15775 case GD_S_DIAMOND_IMPACT_5:
15776 case GD_S_DIAMOND_IMPACT_6:
15777 case GD_S_DIAMOND_IMPACT_7:
15778 case GD_S_DIAMOND_IMPACT_8:
15779 nr = (sample == GD_S_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15780 sample - GD_S_DIAMOND_IMPACT_1);
15781 sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr;
15783 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15784 sound_effect = SND_BD_DIAMOND_IMPACT;
15787 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15788 case GD_S_FLYING_DIAMOND_FALLING_1:
15789 case GD_S_FLYING_DIAMOND_FALLING_2:
15790 case GD_S_FLYING_DIAMOND_FALLING_3:
15791 case GD_S_FLYING_DIAMOND_FALLING_4:
15792 case GD_S_FLYING_DIAMOND_FALLING_5:
15793 case GD_S_FLYING_DIAMOND_FALLING_6:
15794 case GD_S_FLYING_DIAMOND_FALLING_7:
15795 case GD_S_FLYING_DIAMOND_FALLING_8:
15796 nr = (sample == GD_S_FLYING_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15797 sample - GD_S_FLYING_DIAMOND_FALLING_1);
15798 sound_effect = SND_BD_FLYING_DIAMOND_FALLING_RANDOM_1 + nr;
15800 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15801 sound_effect = SND_BD_FLYING_DIAMOND_FALLING;
15804 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15805 case GD_S_FLYING_DIAMOND_IMPACT_1:
15806 case GD_S_FLYING_DIAMOND_IMPACT_2:
15807 case GD_S_FLYING_DIAMOND_IMPACT_3:
15808 case GD_S_FLYING_DIAMOND_IMPACT_4:
15809 case GD_S_FLYING_DIAMOND_IMPACT_5:
15810 case GD_S_FLYING_DIAMOND_IMPACT_6:
15811 case GD_S_FLYING_DIAMOND_IMPACT_7:
15812 case GD_S_FLYING_DIAMOND_IMPACT_8:
15813 nr = (sample == GD_S_FLYING_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15814 sample - GD_S_FLYING_DIAMOND_IMPACT_1);
15815 sound_effect = SND_BD_FLYING_DIAMOND_IMPACT_RANDOM_1 + nr;
15817 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15818 sound_effect = SND_BD_FLYING_DIAMOND_IMPACT;
15821 case GD_S_TIMEOUT_0:
15822 case GD_S_TIMEOUT_1:
15823 case GD_S_TIMEOUT_2:
15824 case GD_S_TIMEOUT_3:
15825 case GD_S_TIMEOUT_4:
15826 case GD_S_TIMEOUT_5:
15827 case GD_S_TIMEOUT_6:
15828 case GD_S_TIMEOUT_7:
15829 case GD_S_TIMEOUT_8:
15830 case GD_S_TIMEOUT_9:
15831 case GD_S_TIMEOUT_10:
15832 nr = sample - GD_S_TIMEOUT_0;
15833 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME_0 + nr;
15835 if (getSoundInfoEntryFilename(sound_effect) == NULL && sample != GD_S_TIMEOUT_0)
15836 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME;
15839 case GD_S_BONUS_LIFE:
15840 sound_effect = SND_GAME_HEALTH_BONUS;
15843 case GD_S_AMOEBA_MAGIC:
15844 sound_effect = SND_BD_AMOEBA_OTHER;
15847 case GD_S_FINISHED:
15848 sound_effect = SND_GAME_LEVELTIME_BONUS;
15852 sound_effect = SND_UNDEFINED;
15856 return sound_effect;
15859 void PlayLevelSound_BD(int xx, int yy, int element_bd, int sample)
15861 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15862 int sound_effect = getSoundEffect_BD(element, sample);
15863 int sound_action = getSoundAction_BD(sample);
15864 boolean is_loop_sound = IS_LOOP_SOUND(sound_effect);
15866 int x = xx - offset;
15867 int y = yy - offset;
15869 // some sound actions are always looping in native BD game engine
15870 if (sound_action == ACTION_DEFAULT)
15871 is_loop_sound = TRUE;
15873 // some sound actions are always non-looping in native BD game engine
15874 if (sound_action == ACTION_FALLING ||
15875 sound_action == ACTION_MOVING ||
15876 sound_action == ACTION_OTHER)
15877 is_loop_sound = FALSE;
15879 if (sound_effect != SND_UNDEFINED)
15880 PlayLevelSoundExt(x, y, sound_effect, is_loop_sound);
15883 void StopSound_BD(int element_bd, int sample)
15885 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15886 int sound_effect = getSoundEffect_BD(element, sample);
15888 if (sound_effect != SND_UNDEFINED)
15889 StopSound(sound_effect);
15892 boolean isSoundPlaying_BD(int element_bd, int sample)
15894 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15895 int sound_effect = getSoundEffect_BD(element, sample);
15897 if (sound_effect != SND_UNDEFINED)
15898 return isSoundPlaying(sound_effect);
15903 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15905 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15907 int x = xx - offset;
15908 int y = yy - offset;
15913 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15917 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15921 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15925 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15929 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15933 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15937 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15940 case SOUND_android_clone:
15941 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15944 case SOUND_android_move:
15945 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15949 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15953 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15957 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15960 case SOUND_eater_eat:
15961 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15965 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15968 case SOUND_collect:
15969 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15972 case SOUND_diamond:
15973 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15977 // !!! CHECK THIS !!!
15979 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15981 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15985 case SOUND_wonderfall:
15986 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15990 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15994 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15998 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
16002 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
16006 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16010 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
16014 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16018 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16021 case SOUND_exit_open:
16022 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
16025 case SOUND_exit_leave:
16026 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16029 case SOUND_dynamite:
16030 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16034 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16038 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
16042 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16046 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
16050 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
16054 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
16058 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
16063 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
16065 int element = map_element_SP_to_RND(element_sp);
16066 int action = map_action_SP_to_RND(action_sp);
16067 int offset = (setup.sp_show_border_elements ? 0 : 1);
16068 int x = xx - offset;
16069 int y = yy - offset;
16071 PlayLevelSoundElementAction(x, y, element, action);
16074 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
16076 int element = map_element_MM_to_RND(element_mm);
16077 int action = map_action_MM_to_RND(action_mm);
16079 int x = xx - offset;
16080 int y = yy - offset;
16082 if (!IS_MM_ELEMENT(element))
16083 element = EL_MM_DEFAULT;
16085 PlayLevelSoundElementAction(x, y, element, action);
16088 void PlaySound_MM(int sound_mm)
16090 int sound = map_sound_MM_to_RND(sound_mm);
16092 if (sound == SND_UNDEFINED)
16098 void PlaySoundLoop_MM(int sound_mm)
16100 int sound = map_sound_MM_to_RND(sound_mm);
16102 if (sound == SND_UNDEFINED)
16105 PlaySoundLoop(sound);
16108 void StopSound_MM(int sound_mm)
16110 int sound = map_sound_MM_to_RND(sound_mm);
16112 if (sound == SND_UNDEFINED)
16118 void RaiseScore(int value)
16120 game.score += value;
16122 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
16124 DisplayGameControlValues();
16127 void RaiseScoreElement(int element)
16132 case EL_BD_DIAMOND:
16133 case EL_EMERALD_YELLOW:
16134 case EL_EMERALD_RED:
16135 case EL_EMERALD_PURPLE:
16136 case EL_SP_INFOTRON:
16137 RaiseScore(level.score[SC_EMERALD]);
16140 RaiseScore(level.score[SC_DIAMOND]);
16143 RaiseScore(level.score[SC_CRYSTAL]);
16146 RaiseScore(level.score[SC_PEARL]);
16149 case EL_BD_BUTTERFLY:
16150 case EL_SP_ELECTRON:
16151 RaiseScore(level.score[SC_BUG]);
16154 case EL_BD_FIREFLY:
16155 case EL_SP_SNIKSNAK:
16156 RaiseScore(level.score[SC_SPACESHIP]);
16159 case EL_DARK_YAMYAM:
16160 RaiseScore(level.score[SC_YAMYAM]);
16163 RaiseScore(level.score[SC_ROBOT]);
16166 RaiseScore(level.score[SC_PACMAN]);
16169 RaiseScore(level.score[SC_NUT]);
16172 case EL_EM_DYNAMITE:
16173 case EL_SP_DISK_RED:
16174 case EL_DYNABOMB_INCREASE_NUMBER:
16175 case EL_DYNABOMB_INCREASE_SIZE:
16176 case EL_DYNABOMB_INCREASE_POWER:
16177 RaiseScore(level.score[SC_DYNAMITE]);
16179 case EL_SHIELD_NORMAL:
16180 case EL_SHIELD_DEADLY:
16181 RaiseScore(level.score[SC_SHIELD]);
16183 case EL_EXTRA_TIME:
16184 RaiseScore(level.extra_time_score);
16198 case EL_DC_KEY_WHITE:
16199 RaiseScore(level.score[SC_KEY]);
16202 RaiseScore(element_info[element].collect_score);
16207 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
16209 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
16213 // prevent short reactivation of overlay buttons while closing door
16214 SetOverlayActive(FALSE);
16215 UnmapGameButtons();
16217 // door may still be open due to skipped or envelope style request
16218 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
16221 if (network.enabled)
16223 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
16227 // when using BD game engine, cover screen before fading out
16228 if (!quick_quit && level.game_engine_type == GAME_ENGINE_TYPE_BD)
16229 game_bd.cover_screen = TRUE;
16232 FadeSkipNextFadeIn();
16234 SetGameStatus(GAME_MODE_MAIN);
16239 else // continue playing the game
16241 if (tape.playing && tape.deactivate_display)
16242 TapeDeactivateDisplayOff(TRUE);
16244 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
16246 if (tape.playing && tape.deactivate_display)
16247 TapeDeactivateDisplayOn();
16251 void RequestQuitGame(boolean escape_key_pressed)
16253 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
16254 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
16255 level_editor_test_game);
16256 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
16257 quick_quit || score_info_tape_play);
16259 RequestQuitGameExt(skip_request, quick_quit,
16260 "Do you really want to quit the game?");
16263 static char *getRestartGameMessage(void)
16265 boolean play_again = hasStartedNetworkGame();
16266 static char message[MAX_OUTPUT_LINESIZE];
16267 char *game_over_text = "Game over!";
16268 char *play_again_text = " Play it again?";
16270 if (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
16271 game_mm.game_over_message != NULL)
16272 game_over_text = game_mm.game_over_message;
16274 snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text,
16275 (play_again ? play_again_text : ""));
16280 static void RequestRestartGame(void)
16282 char *message = getRestartGameMessage();
16283 boolean has_started_game = hasStartedNetworkGame();
16284 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
16285 int door_state = DOOR_CLOSE_1;
16287 boolean restart_wanted = (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game);
16289 // if no restart wanted, continue with next level for BD style intermission levels
16290 if (!restart_wanted && !level_editor_test_game && level.bd_intermission)
16292 boolean success = AdvanceToNextLevel();
16294 restart_wanted = (success && setup.auto_play_next_level);
16297 if (restart_wanted)
16299 CloseDoor(door_state);
16301 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16305 // if game was invoked from level editor, also close tape recorder door
16306 if (level_editor_test_game)
16307 door_state = DOOR_CLOSE_ALL;
16309 CloseDoor(door_state);
16311 SetGameStatus(GAME_MODE_MAIN);
16317 boolean CheckRestartGame(void)
16319 static int game_over_delay = 0;
16320 int game_over_delay_value = 50;
16321 boolean game_over = checkGameFailed();
16325 game_over_delay = game_over_delay_value;
16330 if (game_over_delay > 0)
16332 if (game_over_delay == game_over_delay_value / 2)
16333 PlaySound(SND_GAME_LOSING);
16340 // do not ask to play again if request dialog is already active
16341 if (game.request_active)
16344 // do not ask to play again if request dialog already handled
16345 if (game.RestartGameRequested)
16348 // do not ask to play again if game was never actually played
16349 if (!game.GamePlayed)
16352 // do not ask to play again if this was disabled in setup menu
16353 if (!setup.ask_on_game_over)
16356 game.RestartGameRequested = TRUE;
16358 RequestRestartGame();
16363 boolean checkGameRunning(void)
16365 if (game_status != GAME_MODE_PLAYING)
16368 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGameRunning_BD())
16374 boolean checkGamePlaying(void)
16376 if (game_status != GAME_MODE_PLAYING)
16379 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGamePlaying_BD())
16385 boolean checkGameSolved(void)
16387 // set for all game engines if level was solved
16388 return game.LevelSolved_GameEnd;
16391 boolean checkGameFailed(void)
16393 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
16394 return (game_bd.game_over && !game_bd.level_solved);
16395 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16396 return (game_em.game_over && !game_em.level_solved);
16397 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16398 return (game_sp.game_over && !game_sp.level_solved);
16399 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16400 return (game_mm.game_over && !game_mm.level_solved);
16401 else // GAME_ENGINE_TYPE_RND
16402 return (game.GameOver && !game.LevelSolved);
16405 boolean checkGameEnded(void)
16407 return (checkGameSolved() || checkGameFailed());
16411 // ----------------------------------------------------------------------------
16412 // random generator functions
16413 // ----------------------------------------------------------------------------
16415 unsigned int InitEngineRandom_RND(int seed)
16417 game.num_random_calls = 0;
16419 return InitEngineRandom(seed);
16422 unsigned int RND(int max)
16426 game.num_random_calls++;
16428 return GetEngineRandom(max);
16435 // ----------------------------------------------------------------------------
16436 // game engine snapshot handling functions
16437 // ----------------------------------------------------------------------------
16439 struct EngineSnapshotInfo
16441 // runtime values for custom element collect score
16442 int collect_score[NUM_CUSTOM_ELEMENTS];
16444 // runtime values for group element choice position
16445 int choice_pos[NUM_GROUP_ELEMENTS];
16447 // runtime values for belt position animations
16448 int belt_graphic[4][NUM_BELT_PARTS];
16449 int belt_anim_mode[4][NUM_BELT_PARTS];
16452 static struct EngineSnapshotInfo engine_snapshot_rnd;
16453 static char *snapshot_level_identifier = NULL;
16454 static int snapshot_level_nr = -1;
16456 static void SaveEngineSnapshotValues_RND(void)
16458 static int belt_base_active_element[4] =
16460 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
16461 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
16462 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
16463 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
16467 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16469 int element = EL_CUSTOM_START + i;
16471 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
16474 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16476 int element = EL_GROUP_START + i;
16478 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
16481 for (i = 0; i < 4; i++)
16483 for (j = 0; j < NUM_BELT_PARTS; j++)
16485 int element = belt_base_active_element[i] + j;
16486 int graphic = el2img(element);
16487 int anim_mode = graphic_info[graphic].anim_mode;
16489 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
16490 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
16495 static void LoadEngineSnapshotValues_RND(void)
16497 unsigned int num_random_calls = game.num_random_calls;
16500 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16502 int element = EL_CUSTOM_START + i;
16504 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
16507 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16509 int element = EL_GROUP_START + i;
16511 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
16514 for (i = 0; i < 4; i++)
16516 for (j = 0; j < NUM_BELT_PARTS; j++)
16518 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
16519 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
16521 graphic_info[graphic].anim_mode = anim_mode;
16525 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16527 InitRND(tape.random_seed);
16528 for (i = 0; i < num_random_calls; i++)
16532 if (game.num_random_calls != num_random_calls)
16534 Error("number of random calls out of sync");
16535 Error("number of random calls should be %d", num_random_calls);
16536 Error("number of random calls is %d", game.num_random_calls);
16538 Fail("this should not happen -- please debug");
16542 void FreeEngineSnapshotSingle(void)
16544 FreeSnapshotSingle();
16546 setString(&snapshot_level_identifier, NULL);
16547 snapshot_level_nr = -1;
16550 void FreeEngineSnapshotList(void)
16552 FreeSnapshotList();
16555 static ListNode *SaveEngineSnapshotBuffers(void)
16557 ListNode *buffers = NULL;
16559 // copy some special values to a structure better suited for the snapshot
16561 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16562 SaveEngineSnapshotValues_RND();
16563 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16564 SaveEngineSnapshotValues_EM();
16565 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16566 SaveEngineSnapshotValues_SP(&buffers);
16567 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16568 SaveEngineSnapshotValues_MM();
16570 // save values stored in special snapshot structure
16572 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16573 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
16574 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16575 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
16576 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16577 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
16578 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16579 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
16581 // save further RND engine values
16583 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
16584 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
16585 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
16587 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
16588 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
16589 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
16590 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16591 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTimeFrames));
16592 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16594 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16595 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16596 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16598 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16600 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16601 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16603 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16604 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16605 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16606 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16607 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16608 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16609 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16610 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16611 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16612 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16613 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16614 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16615 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16616 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16617 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16618 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16619 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16620 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16622 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16623 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16625 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16626 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16627 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16629 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16630 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16632 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16633 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16634 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16635 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16636 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16637 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16639 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16640 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16643 ListNode *node = engine_snapshot_list_rnd;
16646 while (node != NULL)
16648 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16653 Debug("game:playing:SaveEngineSnapshotBuffers",
16654 "size of engine snapshot: %d bytes", num_bytes);
16660 void SaveEngineSnapshotSingle(void)
16662 ListNode *buffers = SaveEngineSnapshotBuffers();
16664 // finally save all snapshot buffers to single snapshot
16665 SaveSnapshotSingle(buffers);
16667 // save level identification information
16668 setString(&snapshot_level_identifier, leveldir_current->identifier);
16669 snapshot_level_nr = level_nr;
16672 boolean CheckSaveEngineSnapshotToList(void)
16674 boolean save_snapshot =
16675 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16676 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16677 game.snapshot.changed_action) ||
16678 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16679 game.snapshot.collected_item));
16681 game.snapshot.changed_action = FALSE;
16682 game.snapshot.collected_item = FALSE;
16683 game.snapshot.save_snapshot = save_snapshot;
16685 return save_snapshot;
16688 void SaveEngineSnapshotToList(void)
16690 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16694 ListNode *buffers = SaveEngineSnapshotBuffers();
16696 // finally save all snapshot buffers to snapshot list
16697 SaveSnapshotToList(buffers);
16700 void SaveEngineSnapshotToListInitial(void)
16702 FreeEngineSnapshotList();
16704 SaveEngineSnapshotToList();
16707 static void LoadEngineSnapshotValues(void)
16709 // restore special values from snapshot structure
16711 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16712 LoadEngineSnapshotValues_RND();
16713 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16714 LoadEngineSnapshotValues_EM();
16715 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16716 LoadEngineSnapshotValues_SP();
16717 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16718 LoadEngineSnapshotValues_MM();
16721 void LoadEngineSnapshotSingle(void)
16723 LoadSnapshotSingle();
16725 LoadEngineSnapshotValues();
16728 static void LoadEngineSnapshot_Undo(int steps)
16730 LoadSnapshotFromList_Older(steps);
16732 LoadEngineSnapshotValues();
16735 static void LoadEngineSnapshot_Redo(int steps)
16737 LoadSnapshotFromList_Newer(steps);
16739 LoadEngineSnapshotValues();
16742 boolean CheckEngineSnapshotSingle(void)
16744 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16745 snapshot_level_nr == level_nr);
16748 boolean CheckEngineSnapshotList(void)
16750 return CheckSnapshotList();
16754 // ---------- new game button stuff -------------------------------------------
16761 boolean *setup_value;
16762 boolean allowed_on_tape;
16763 boolean is_touch_button;
16765 } gamebutton_info[NUM_GAME_BUTTONS] =
16768 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16769 GAME_CTRL_ID_STOP, NULL,
16770 TRUE, FALSE, "stop game"
16773 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16774 GAME_CTRL_ID_PAUSE, NULL,
16775 TRUE, FALSE, "pause game"
16778 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16779 GAME_CTRL_ID_PLAY, NULL,
16780 TRUE, FALSE, "play game"
16783 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16784 GAME_CTRL_ID_UNDO, NULL,
16785 TRUE, FALSE, "undo step"
16788 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16789 GAME_CTRL_ID_REDO, NULL,
16790 TRUE, FALSE, "redo step"
16793 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16794 GAME_CTRL_ID_SAVE, NULL,
16795 TRUE, FALSE, "save game"
16798 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16799 GAME_CTRL_ID_PAUSE2, NULL,
16800 TRUE, FALSE, "pause game"
16803 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16804 GAME_CTRL_ID_LOAD, NULL,
16805 TRUE, FALSE, "load game"
16808 IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart,
16809 GAME_CTRL_ID_RESTART, NULL,
16810 TRUE, FALSE, "restart game"
16813 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16814 GAME_CTRL_ID_PANEL_STOP, NULL,
16815 FALSE, FALSE, "stop game"
16818 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16819 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16820 FALSE, FALSE, "pause game"
16823 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16824 GAME_CTRL_ID_PANEL_PLAY, NULL,
16825 FALSE, FALSE, "play game"
16828 IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart,
16829 GAME_CTRL_ID_PANEL_RESTART, NULL,
16830 FALSE, FALSE, "restart game"
16833 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16834 GAME_CTRL_ID_TOUCH_STOP, NULL,
16835 FALSE, TRUE, "stop game"
16838 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16839 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16840 FALSE, TRUE, "pause game"
16843 IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart,
16844 GAME_CTRL_ID_TOUCH_RESTART, NULL,
16845 FALSE, TRUE, "restart game"
16848 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16849 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16850 TRUE, FALSE, "background music on/off"
16853 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16854 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16855 TRUE, FALSE, "sound loops on/off"
16858 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16859 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16860 TRUE, FALSE, "normal sounds on/off"
16863 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16864 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16865 FALSE, FALSE, "background music on/off"
16868 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16869 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16870 FALSE, FALSE, "sound loops on/off"
16873 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16874 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16875 FALSE, FALSE, "normal sounds on/off"
16879 void CreateGameButtons(void)
16883 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16885 int graphic = gamebutton_info[i].graphic;
16886 struct GraphicInfo *gfx = &graphic_info[graphic];
16887 struct XY *pos = gamebutton_info[i].pos;
16888 struct GadgetInfo *gi;
16891 unsigned int event_mask;
16892 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16893 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16894 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16895 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16896 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16897 int gd_x = gfx->src_x;
16898 int gd_y = gfx->src_y;
16899 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16900 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16901 int gd_xa = gfx->src_x + gfx->active_xoffset;
16902 int gd_ya = gfx->src_y + gfx->active_yoffset;
16903 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16904 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16905 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16906 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16909 // do not use touch buttons if overlay touch buttons are disabled
16910 if (is_touch_button && !setup.touch.overlay_buttons)
16913 if (gfx->bitmap == NULL)
16915 game_gadget[id] = NULL;
16920 if (id == GAME_CTRL_ID_STOP ||
16921 id == GAME_CTRL_ID_PANEL_STOP ||
16922 id == GAME_CTRL_ID_TOUCH_STOP ||
16923 id == GAME_CTRL_ID_PLAY ||
16924 id == GAME_CTRL_ID_PANEL_PLAY ||
16925 id == GAME_CTRL_ID_SAVE ||
16926 id == GAME_CTRL_ID_LOAD ||
16927 id == GAME_CTRL_ID_RESTART ||
16928 id == GAME_CTRL_ID_PANEL_RESTART ||
16929 id == GAME_CTRL_ID_TOUCH_RESTART)
16931 button_type = GD_TYPE_NORMAL_BUTTON;
16933 event_mask = GD_EVENT_RELEASED;
16935 else if (id == GAME_CTRL_ID_UNDO ||
16936 id == GAME_CTRL_ID_REDO)
16938 button_type = GD_TYPE_NORMAL_BUTTON;
16940 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16944 button_type = GD_TYPE_CHECK_BUTTON;
16945 checked = (gamebutton_info[i].setup_value != NULL ?
16946 *gamebutton_info[i].setup_value : FALSE);
16947 event_mask = GD_EVENT_PRESSED;
16950 gi = CreateGadget(GDI_CUSTOM_ID, id,
16951 GDI_IMAGE_ID, graphic,
16952 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16955 GDI_WIDTH, gfx->width,
16956 GDI_HEIGHT, gfx->height,
16957 GDI_TYPE, button_type,
16958 GDI_STATE, GD_BUTTON_UNPRESSED,
16959 GDI_CHECKED, checked,
16960 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16961 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16962 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16963 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16964 GDI_DIRECT_DRAW, FALSE,
16965 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16966 GDI_EVENT_MASK, event_mask,
16967 GDI_CALLBACK_ACTION, HandleGameButtons,
16971 Fail("cannot create gadget");
16973 game_gadget[id] = gi;
16977 void FreeGameButtons(void)
16981 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16982 FreeGadget(game_gadget[i]);
16985 static void UnmapGameButtonsAtSamePosition(int id)
16989 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16991 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16992 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16993 UnmapGadget(game_gadget[i]);
16996 static void UnmapGameButtonsAtSamePosition_All(void)
16998 if (setup.show_load_save_buttons)
17000 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
17001 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
17002 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
17004 else if (setup.show_undo_redo_buttons)
17006 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
17007 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
17008 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
17012 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
17013 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
17014 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
17016 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
17017 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
17018 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
17022 void MapLoadSaveButtons(void)
17024 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
17025 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
17027 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
17028 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
17031 void MapUndoRedoButtons(void)
17033 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
17034 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
17036 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
17037 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
17040 void ModifyPauseButtons(void)
17044 GAME_CTRL_ID_PAUSE,
17045 GAME_CTRL_ID_PAUSE2,
17046 GAME_CTRL_ID_PANEL_PAUSE,
17047 GAME_CTRL_ID_TOUCH_PAUSE,
17052 // do not redraw pause button on closed door (may happen when restarting game)
17053 if (!(GetDoorState() & DOOR_OPEN_1))
17056 for (i = 0; ids[i] > -1; i++)
17057 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
17060 static void MapGameButtonsExt(boolean on_tape)
17064 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17066 if ((i == GAME_CTRL_ID_UNDO ||
17067 i == GAME_CTRL_ID_REDO) &&
17068 game_status != GAME_MODE_PLAYING)
17071 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17072 MapGadget(game_gadget[i]);
17075 UnmapGameButtonsAtSamePosition_All();
17077 RedrawGameButtons();
17080 static void UnmapGameButtonsExt(boolean on_tape)
17084 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17085 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17086 UnmapGadget(game_gadget[i]);
17089 static void RedrawGameButtonsExt(boolean on_tape)
17093 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17094 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17095 RedrawGadget(game_gadget[i]);
17098 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
17103 gi->checked = state;
17106 static void RedrawSoundButtonGadget(int id)
17108 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
17109 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
17110 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
17111 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
17112 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
17113 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
17116 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
17117 RedrawGadget(game_gadget[id2]);
17120 void MapGameButtons(void)
17122 MapGameButtonsExt(FALSE);
17125 void UnmapGameButtons(void)
17127 UnmapGameButtonsExt(FALSE);
17130 void RedrawGameButtons(void)
17132 RedrawGameButtonsExt(FALSE);
17135 void MapGameButtonsOnTape(void)
17137 MapGameButtonsExt(TRUE);
17140 void UnmapGameButtonsOnTape(void)
17142 UnmapGameButtonsExt(TRUE);
17145 void RedrawGameButtonsOnTape(void)
17147 RedrawGameButtonsExt(TRUE);
17150 static void GameUndoRedoExt(void)
17152 ClearPlayerAction();
17154 tape.pausing = TRUE;
17157 UpdateAndDisplayGameControlValues();
17159 DrawCompleteVideoDisplay();
17160 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
17161 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
17162 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
17164 ModifyPauseButtons();
17169 static void GameUndo(int steps)
17171 if (!CheckEngineSnapshotList())
17174 int tape_property_bits = tape.property_bits;
17176 LoadEngineSnapshot_Undo(steps);
17178 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17183 static void GameRedo(int steps)
17185 if (!CheckEngineSnapshotList())
17188 int tape_property_bits = tape.property_bits;
17190 LoadEngineSnapshot_Redo(steps);
17192 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17197 static void HandleGameButtonsExt(int id, int button)
17199 static boolean game_undo_executed = FALSE;
17200 int steps = BUTTON_STEPSIZE(button);
17201 boolean handle_game_buttons =
17202 (game_status == GAME_MODE_PLAYING ||
17203 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
17205 if (!handle_game_buttons)
17210 case GAME_CTRL_ID_STOP:
17211 case GAME_CTRL_ID_PANEL_STOP:
17212 case GAME_CTRL_ID_TOUCH_STOP:
17217 case GAME_CTRL_ID_PAUSE:
17218 case GAME_CTRL_ID_PAUSE2:
17219 case GAME_CTRL_ID_PANEL_PAUSE:
17220 case GAME_CTRL_ID_TOUCH_PAUSE:
17221 if (network.enabled && game_status == GAME_MODE_PLAYING)
17224 SendToServer_ContinuePlaying();
17226 SendToServer_PausePlaying();
17229 TapeTogglePause(TAPE_TOGGLE_MANUAL);
17231 game_undo_executed = FALSE;
17235 case GAME_CTRL_ID_PLAY:
17236 case GAME_CTRL_ID_PANEL_PLAY:
17237 if (game_status == GAME_MODE_MAIN)
17239 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
17241 else if (tape.pausing)
17243 if (network.enabled)
17244 SendToServer_ContinuePlaying();
17246 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
17250 case GAME_CTRL_ID_UNDO:
17251 // Important: When using "save snapshot when collecting an item" mode,
17252 // load last (current) snapshot for first "undo" after pressing "pause"
17253 // (else the last-but-one snapshot would be loaded, because the snapshot
17254 // pointer already points to the last snapshot when pressing "pause",
17255 // which is fine for "every step/move" mode, but not for "every collect")
17256 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
17257 !game_undo_executed)
17260 game_undo_executed = TRUE;
17265 case GAME_CTRL_ID_REDO:
17269 case GAME_CTRL_ID_SAVE:
17273 case GAME_CTRL_ID_LOAD:
17277 case GAME_CTRL_ID_RESTART:
17278 case GAME_CTRL_ID_PANEL_RESTART:
17279 case GAME_CTRL_ID_TOUCH_RESTART:
17284 case SOUND_CTRL_ID_MUSIC:
17285 case SOUND_CTRL_ID_PANEL_MUSIC:
17286 if (setup.sound_music)
17288 setup.sound_music = FALSE;
17292 else if (audio.music_available)
17294 setup.sound = setup.sound_music = TRUE;
17296 SetAudioMode(setup.sound);
17298 if (game_status == GAME_MODE_PLAYING)
17302 RedrawSoundButtonGadget(id);
17306 case SOUND_CTRL_ID_LOOPS:
17307 case SOUND_CTRL_ID_PANEL_LOOPS:
17308 if (setup.sound_loops)
17309 setup.sound_loops = FALSE;
17310 else if (audio.loops_available)
17312 setup.sound = setup.sound_loops = TRUE;
17314 SetAudioMode(setup.sound);
17317 RedrawSoundButtonGadget(id);
17321 case SOUND_CTRL_ID_SIMPLE:
17322 case SOUND_CTRL_ID_PANEL_SIMPLE:
17323 if (setup.sound_simple)
17324 setup.sound_simple = FALSE;
17325 else if (audio.sound_available)
17327 setup.sound = setup.sound_simple = TRUE;
17329 SetAudioMode(setup.sound);
17332 RedrawSoundButtonGadget(id);
17341 static void HandleGameButtons(struct GadgetInfo *gi)
17343 HandleGameButtonsExt(gi->custom_id, gi->event.button);
17346 void HandleSoundButtonKeys(Key key)
17348 if (key == setup.shortcut.sound_simple)
17349 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
17350 else if (key == setup.shortcut.sound_loops)
17351 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
17352 else if (key == setup.shortcut.sound_music)
17353 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);