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();
4911 static int time_count_steps;
4912 static int time, time_final;
4913 static float score, score_final; // needed for time score < 10 for 10 seconds
4914 static int health, health_final;
4915 static int game_over_delay_1 = 0;
4916 static int game_over_delay_2 = 0;
4917 static int game_over_delay_3 = 0;
4918 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4919 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4921 if (!game.LevelSolved_GameWon)
4925 // do not start end game actions before the player stops moving (to exit)
4926 if (local_player->active && local_player->MovPos)
4929 // calculate final game values after player finished walking into exit
4930 LevelSolved_SetFinalGameValues();
4932 game.LevelSolved_GameWon = TRUE;
4933 game.LevelSolved_SaveTape = tape.recording;
4934 game.LevelSolved_SaveScore = !tape.playing;
4938 LevelStats_incSolved(level_nr);
4940 SaveLevelSetup_SeriesInfo();
4943 if (tape.auto_play) // tape might already be stopped here
4944 tape.auto_play_level_solved = TRUE;
4948 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4949 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4950 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4952 time = time_final = game.time_final;
4953 score = score_final = game.score_final;
4954 health = health_final = game.health_final;
4956 // update game panel values before (delayed) counting of score (if any)
4957 LevelSolved_DisplayFinalGameValues(time, score, health);
4959 // if level has time score defined, calculate new final game values
4962 int time_final_max = 999;
4963 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4964 int time_frames = 0;
4965 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4966 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4971 time_frames = time_frames_left;
4973 else if (game.no_level_time_limit && TimePlayed < time_final_max)
4975 time_final = time_final_max;
4976 time_frames = time_frames_final_max - time_frames_played;
4979 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4981 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4983 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
4985 // keep previous values (final values already processed here)
4987 score_final = score;
4989 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4992 score_final += health * time_score;
4995 game.score_final = score_final;
4996 game.health_final = health_final;
4999 // if not counting score after game, immediately update game panel values
5000 if (level_editor_test_game || !setup.count_score_after_game ||
5001 level.game_engine_type == GAME_ENGINE_TYPE_BD)
5004 score = score_final;
5006 LevelSolved_DisplayFinalGameValues(time, score, health);
5009 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
5011 // check if last player has left the level
5012 if (game.exit_x >= 0 &&
5015 int x = game.exit_x;
5016 int y = game.exit_y;
5017 int element = Tile[x][y];
5019 // close exit door after last player
5020 if ((game.all_players_gone &&
5021 (element == EL_EXIT_OPEN ||
5022 element == EL_SP_EXIT_OPEN ||
5023 element == EL_STEEL_EXIT_OPEN)) ||
5024 element == EL_EM_EXIT_OPEN ||
5025 element == EL_EM_STEEL_EXIT_OPEN)
5029 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
5030 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
5031 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
5032 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
5033 EL_EM_STEEL_EXIT_CLOSING);
5035 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
5038 // player disappears
5039 DrawLevelField(x, y);
5042 for (i = 0; i < MAX_PLAYERS; i++)
5044 struct PlayerInfo *player = &stored_player[i];
5046 if (player->present)
5048 RemovePlayer(player);
5050 // player disappears
5051 DrawLevelField(player->jx, player->jy);
5056 PlaySound(SND_GAME_WINNING);
5059 if (setup.count_score_after_game)
5061 if (time != time_final)
5063 if (game_over_delay_1 > 0)
5065 game_over_delay_1--;
5070 int time_to_go = ABS(time_final - time);
5071 int time_count_dir = (time < time_final ? +1 : -1);
5073 if (time_to_go < time_count_steps)
5074 time_count_steps = 1;
5076 time += time_count_steps * time_count_dir;
5077 score += time_count_steps * time_score;
5079 // set final score to correct rounding differences after counting score
5080 if (time == time_final)
5081 score = score_final;
5083 LevelSolved_DisplayFinalGameValues(time, score, health);
5085 if (time == time_final)
5086 StopSound(SND_GAME_LEVELTIME_BONUS);
5087 else if (setup.sound_loops)
5088 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5090 PlaySound(SND_GAME_LEVELTIME_BONUS);
5095 if (health != health_final)
5097 if (game_over_delay_2 > 0)
5099 game_over_delay_2--;
5104 int health_count_dir = (health < health_final ? +1 : -1);
5106 health += health_count_dir;
5107 score += time_score;
5109 LevelSolved_DisplayFinalGameValues(time, score, health);
5111 if (health == health_final)
5112 StopSound(SND_GAME_LEVELTIME_BONUS);
5113 else if (setup.sound_loops)
5114 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5116 PlaySound(SND_GAME_LEVELTIME_BONUS);
5122 game.panel.active = FALSE;
5124 if (game_over_delay_3 > 0)
5126 game_over_delay_3--;
5136 // used instead of "level_nr" (needed for network games)
5137 int last_level_nr = levelset.level_nr;
5138 boolean tape_saved = FALSE;
5140 game.LevelSolved_GameEnd = TRUE;
5142 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5144 // make sure that request dialog to save tape does not open door again
5145 if (!global.use_envelope_request)
5146 CloseDoor(DOOR_CLOSE_1);
5149 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5151 // set unique basename for score tape (also saved in high score table)
5152 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5155 // if no tape is to be saved, close both doors simultaneously
5156 CloseDoor(DOOR_CLOSE_ALL);
5158 if (level_editor_test_game || score_info_tape_play)
5160 SetGameStatus(GAME_MODE_MAIN);
5167 if (!game.LevelSolved_SaveScore)
5169 SetGameStatus(GAME_MODE_MAIN);
5176 if (level_nr == leveldir_current->handicap_level)
5178 leveldir_current->handicap_level++;
5180 SaveLevelSetup_SeriesInfo();
5183 // save score and score tape before potentially erasing tape below
5184 NewHighScore(last_level_nr, tape_saved);
5186 if (setup.increment_levels &&
5187 level_nr < leveldir_current->last_level &&
5190 level_nr++; // advance to next level
5191 TapeErase(); // start with empty tape
5193 if (setup.auto_play_next_level)
5195 scores.continue_playing = TRUE;
5196 scores.next_level_nr = level_nr;
5198 LoadLevel(level_nr);
5200 SaveLevelSetup_SeriesInfo();
5204 if (scores.last_added >= 0 && setup.show_scores_after_game)
5206 SetGameStatus(GAME_MODE_SCORES);
5208 DrawHallOfFame(last_level_nr);
5210 else if (scores.continue_playing)
5212 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5216 SetGameStatus(GAME_MODE_MAIN);
5222 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5223 boolean one_score_entry_per_name)
5227 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5230 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5232 struct ScoreEntry *entry = &list->entry[i];
5233 boolean score_is_better = (new_entry->score > entry->score);
5234 boolean score_is_equal = (new_entry->score == entry->score);
5235 boolean time_is_better = (new_entry->time < entry->time);
5236 boolean time_is_equal = (new_entry->time == entry->time);
5237 boolean better_by_score = (score_is_better ||
5238 (score_is_equal && time_is_better));
5239 boolean better_by_time = (time_is_better ||
5240 (time_is_equal && score_is_better));
5241 boolean is_better = (level.rate_time_over_score ? better_by_time :
5243 boolean entry_is_empty = (entry->score == 0 &&
5246 // prevent adding server score entries if also existing in local score file
5247 // (special case: historic score entries have an empty tape basename entry)
5248 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5249 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5251 // add fields from server score entry not stored in local score entry
5252 // (currently, this means setting platform, version and country fields;
5253 // in rare cases, this may also correct an invalid score value, as
5254 // historic scores might have been truncated to 16-bit values locally)
5255 *entry = *new_entry;
5260 if (is_better || entry_is_empty)
5262 // player has made it to the hall of fame
5264 if (i < MAX_SCORE_ENTRIES - 1)
5266 int m = MAX_SCORE_ENTRIES - 1;
5269 if (one_score_entry_per_name)
5271 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5272 if (strEqual(list->entry[l].name, new_entry->name))
5275 if (m == i) // player's new highscore overwrites his old one
5279 for (l = m; l > i; l--)
5280 list->entry[l] = list->entry[l - 1];
5285 *entry = *new_entry;
5289 else if (one_score_entry_per_name &&
5290 strEqual(entry->name, new_entry->name))
5292 // player already in high score list with better score or time
5298 // special case: new score is beyond the last high score list position
5299 return MAX_SCORE_ENTRIES;
5302 void NewHighScore(int level_nr, boolean tape_saved)
5304 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5305 boolean one_per_name = FALSE;
5307 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5308 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5310 new_entry.score = game.score_final;
5311 new_entry.time = game.score_time_final;
5313 LoadScore(level_nr);
5315 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5317 if (scores.last_added >= MAX_SCORE_ENTRIES)
5319 scores.last_added = MAX_SCORE_ENTRIES - 1;
5320 scores.force_last_added = TRUE;
5322 scores.entry[scores.last_added] = new_entry;
5324 // store last added local score entry (before merging server scores)
5325 scores.last_added_local = scores.last_added;
5330 if (scores.last_added < 0)
5333 SaveScore(level_nr);
5335 // store last added local score entry (before merging server scores)
5336 scores.last_added_local = scores.last_added;
5338 if (!game.LevelSolved_SaveTape)
5341 SaveScoreTape(level_nr);
5343 if (setup.ask_for_using_api_server)
5345 setup.use_api_server =
5346 Request("Upload your score and tape to the high score server?", REQ_ASK);
5348 if (!setup.use_api_server)
5349 Request("Not using high score server! Use setup menu to enable again!",
5352 runtime.use_api_server = setup.use_api_server;
5354 // after asking for using API server once, do not ask again
5355 setup.ask_for_using_api_server = FALSE;
5357 SaveSetup_ServerSetup();
5360 SaveServerScore(level_nr, tape_saved);
5363 void MergeServerScore(void)
5365 struct ScoreEntry last_added_entry;
5366 boolean one_per_name = FALSE;
5369 if (scores.last_added >= 0)
5370 last_added_entry = scores.entry[scores.last_added];
5372 for (i = 0; i < server_scores.num_entries; i++)
5374 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5376 if (pos >= 0 && pos <= scores.last_added)
5377 scores.last_added++;
5380 if (scores.last_added >= MAX_SCORE_ENTRIES)
5382 scores.last_added = MAX_SCORE_ENTRIES - 1;
5383 scores.force_last_added = TRUE;
5385 scores.entry[scores.last_added] = last_added_entry;
5389 static int getElementMoveStepsizeExt(int x, int y, int direction)
5391 int element = Tile[x][y];
5392 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5393 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5394 int horiz_move = (dx != 0);
5395 int sign = (horiz_move ? dx : dy);
5396 int step = sign * element_info[element].move_stepsize;
5398 // special values for move stepsize for spring and things on conveyor belt
5401 if (CAN_FALL(element) &&
5402 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5403 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5404 else if (element == EL_SPRING)
5405 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5411 static int getElementMoveStepsize(int x, int y)
5413 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5416 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5418 if (player->GfxAction != action || player->GfxDir != dir)
5420 player->GfxAction = action;
5421 player->GfxDir = dir;
5423 player->StepFrame = 0;
5427 static void ResetGfxFrame(int x, int y)
5429 // profiling showed that "autotest" spends 10~20% of its time in this function
5430 if (DrawingDeactivatedField())
5433 int element = Tile[x][y];
5434 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5436 if (graphic_info[graphic].anim_global_sync)
5437 GfxFrame[x][y] = FrameCounter;
5438 else if (graphic_info[graphic].anim_global_anim_sync)
5439 GfxFrame[x][y] = getGlobalAnimSyncFrame();
5440 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5441 GfxFrame[x][y] = CustomValue[x][y];
5442 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5443 GfxFrame[x][y] = element_info[element].collect_score;
5444 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5445 GfxFrame[x][y] = ChangeDelay[x][y];
5448 static void ResetGfxAnimation(int x, int y)
5450 GfxAction[x][y] = ACTION_DEFAULT;
5451 GfxDir[x][y] = MovDir[x][y];
5454 ResetGfxFrame(x, y);
5457 static void ResetRandomAnimationValue(int x, int y)
5459 GfxRandom[x][y] = INIT_GFX_RANDOM();
5462 static void InitMovingField(int x, int y, int direction)
5464 int element = Tile[x][y];
5465 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5466 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5469 boolean is_moving_before, is_moving_after;
5471 // check if element was/is moving or being moved before/after mode change
5472 is_moving_before = (WasJustMoving[x][y] != 0);
5473 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5475 // reset animation only for moving elements which change direction of moving
5476 // or which just started or stopped moving
5477 // (else CEs with property "can move" / "not moving" are reset each frame)
5478 if (is_moving_before != is_moving_after ||
5479 direction != MovDir[x][y])
5480 ResetGfxAnimation(x, y);
5482 MovDir[x][y] = direction;
5483 GfxDir[x][y] = direction;
5485 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5486 direction == MV_DOWN && CAN_FALL(element) ?
5487 ACTION_FALLING : ACTION_MOVING);
5489 // this is needed for CEs with property "can move" / "not moving"
5491 if (is_moving_after)
5493 if (Tile[newx][newy] == EL_EMPTY)
5494 Tile[newx][newy] = EL_BLOCKED;
5496 MovDir[newx][newy] = MovDir[x][y];
5498 CustomValue[newx][newy] = CustomValue[x][y];
5500 GfxFrame[newx][newy] = GfxFrame[x][y];
5501 GfxRandom[newx][newy] = GfxRandom[x][y];
5502 GfxAction[newx][newy] = GfxAction[x][y];
5503 GfxDir[newx][newy] = GfxDir[x][y];
5507 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5509 int direction = MovDir[x][y];
5510 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5511 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5517 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5519 int direction = MovDir[x][y];
5520 int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0);
5521 int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0);
5523 *comes_from_x = oldx;
5524 *comes_from_y = oldy;
5527 static int MovingOrBlocked2Element(int x, int y)
5529 int element = Tile[x][y];
5531 if (element == EL_BLOCKED)
5535 Blocked2Moving(x, y, &oldx, &oldy);
5537 return Tile[oldx][oldy];
5543 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5545 // like MovingOrBlocked2Element(), but if element is moving
5546 // and (x, y) is the field the moving element is just leaving,
5547 // return EL_BLOCKED instead of the element value
5548 int element = Tile[x][y];
5550 if (IS_MOVING(x, y))
5552 if (element == EL_BLOCKED)
5556 Blocked2Moving(x, y, &oldx, &oldy);
5557 return Tile[oldx][oldy];
5566 static void RemoveField(int x, int y)
5568 Tile[x][y] = EL_EMPTY;
5574 CustomValue[x][y] = 0;
5577 ChangeDelay[x][y] = 0;
5578 ChangePage[x][y] = -1;
5579 Pushed[x][y] = FALSE;
5581 GfxElement[x][y] = EL_UNDEFINED;
5582 GfxAction[x][y] = ACTION_DEFAULT;
5583 GfxDir[x][y] = MV_NONE;
5586 static void RemoveMovingField(int x, int y)
5588 int oldx = x, oldy = y, newx = x, newy = y;
5589 int element = Tile[x][y];
5590 int next_element = EL_UNDEFINED;
5592 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5595 if (IS_MOVING(x, y))
5597 Moving2Blocked(x, y, &newx, &newy);
5599 if (Tile[newx][newy] != EL_BLOCKED)
5601 // element is moving, but target field is not free (blocked), but
5602 // already occupied by something different (example: acid pool);
5603 // in this case, only remove the moving field, but not the target
5605 RemoveField(oldx, oldy);
5607 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5609 TEST_DrawLevelField(oldx, oldy);
5614 else if (element == EL_BLOCKED)
5616 Blocked2Moving(x, y, &oldx, &oldy);
5617 if (!IS_MOVING(oldx, oldy))
5621 if (element == EL_BLOCKED &&
5622 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5623 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5624 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5625 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5626 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5627 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5628 next_element = get_next_element(Tile[oldx][oldy]);
5630 RemoveField(oldx, oldy);
5631 RemoveField(newx, newy);
5633 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5635 if (next_element != EL_UNDEFINED)
5636 Tile[oldx][oldy] = next_element;
5638 TEST_DrawLevelField(oldx, oldy);
5639 TEST_DrawLevelField(newx, newy);
5642 void DrawDynamite(int x, int y)
5644 int sx = SCREENX(x), sy = SCREENY(y);
5645 int graphic = el2img(Tile[x][y]);
5648 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5651 if (IS_WALKABLE_INSIDE(Back[x][y]))
5655 DrawLevelElement(x, y, Back[x][y]);
5656 else if (Store[x][y])
5657 DrawLevelElement(x, y, Store[x][y]);
5658 else if (game.use_masked_elements)
5659 DrawLevelElement(x, y, EL_EMPTY);
5661 frame = getGraphicAnimationFrameXY(graphic, x, y);
5663 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5664 DrawGraphicThruMask(sx, sy, graphic, frame);
5666 DrawGraphic(sx, sy, graphic, frame);
5669 static void CheckDynamite(int x, int y)
5671 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5675 if (MovDelay[x][y] != 0)
5678 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5684 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5689 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5691 boolean num_checked_players = 0;
5694 for (i = 0; i < MAX_PLAYERS; i++)
5696 if (stored_player[i].active)
5698 int sx = stored_player[i].jx;
5699 int sy = stored_player[i].jy;
5701 if (num_checked_players == 0)
5708 *sx1 = MIN(*sx1, sx);
5709 *sy1 = MIN(*sy1, sy);
5710 *sx2 = MAX(*sx2, sx);
5711 *sy2 = MAX(*sy2, sy);
5714 num_checked_players++;
5719 static boolean checkIfAllPlayersFitToScreen_RND(void)
5721 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5723 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5725 return (sx2 - sx1 < SCR_FIELDX &&
5726 sy2 - sy1 < SCR_FIELDY);
5729 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5731 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5733 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5735 *sx = (sx1 + sx2) / 2;
5736 *sy = (sy1 + sy2) / 2;
5739 static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
5740 boolean center_screen, boolean quick_relocation)
5742 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5743 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5744 boolean no_delay = (tape.warp_forward);
5745 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5746 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5747 int new_scroll_x, new_scroll_y;
5749 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5751 // case 1: quick relocation inside visible screen (without scrolling)
5758 if (!level.shifted_relocation || center_screen)
5760 // relocation _with_ centering of screen
5762 new_scroll_x = SCROLL_POSITION_X(x);
5763 new_scroll_y = SCROLL_POSITION_Y(y);
5767 // relocation _without_ centering of screen
5769 // apply distance between old and new player position to scroll position
5770 int shifted_scroll_x = scroll_x + (x - old_x);
5771 int shifted_scroll_y = scroll_y + (y - old_y);
5773 // make sure that shifted scroll position does not scroll beyond screen
5774 new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
5775 new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
5777 // special case for teleporting from one end of the playfield to the other
5778 // (this kludge prevents the destination area to be shifted by half a tile
5779 // against the source destination for even screen width or screen height;
5780 // probably most useful when used with high "game.forced_scroll_delay_value"
5781 // in combination with "game.forced_scroll_x" and "game.forced_scroll_y")
5782 if (quick_relocation)
5784 if (EVEN(SCR_FIELDX))
5786 // relocate (teleport) between left and right border (half or full)
5787 if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1)
5788 new_scroll_x = SBX_Right;
5789 else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right)
5790 new_scroll_x = SBX_Right - 1;
5791 else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1)
5792 new_scroll_x = SBX_Left;
5793 else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left)
5794 new_scroll_x = SBX_Left + 1;
5797 if (EVEN(SCR_FIELDY))
5799 // relocate (teleport) between top and bottom border (half or full)
5800 if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1)
5801 new_scroll_y = SBY_Lower;
5802 else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower)
5803 new_scroll_y = SBY_Lower - 1;
5804 else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1)
5805 new_scroll_y = SBY_Upper;
5806 else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper)
5807 new_scroll_y = SBY_Upper + 1;
5812 if (quick_relocation)
5814 // case 2: quick relocation (redraw without visible scrolling)
5816 scroll_x = new_scroll_x;
5817 scroll_y = new_scroll_y;
5824 // case 3: visible relocation (with scrolling to new position)
5826 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5828 SetVideoFrameDelay(wait_delay_value);
5830 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5832 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5833 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5835 if (dx == 0 && dy == 0) // no scrolling needed at all
5841 // set values for horizontal/vertical screen scrolling (half tile size)
5842 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5843 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5844 int pos_x = dx * TILEX / 2;
5845 int pos_y = dy * TILEY / 2;
5846 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5847 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5849 ScrollLevel(dx, dy);
5852 // scroll in two steps of half tile size to make things smoother
5853 BlitScreenToBitmapExt_RND(window, fx, fy);
5855 // scroll second step to align at full tile size
5856 BlitScreenToBitmap(window);
5862 SetVideoFrameDelay(frame_delay_value_old);
5865 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5867 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5868 int player_nr = GET_PLAYER_NR(el_player);
5869 struct PlayerInfo *player = &stored_player[player_nr];
5870 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5871 boolean no_delay = (tape.warp_forward);
5872 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5873 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5874 int old_jx = player->jx;
5875 int old_jy = player->jy;
5876 int old_element = Tile[old_jx][old_jy];
5877 int element = Tile[jx][jy];
5878 boolean player_relocated = (old_jx != jx || old_jy != jy);
5880 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5881 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5882 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5883 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5884 int leave_side_horiz = move_dir_horiz;
5885 int leave_side_vert = move_dir_vert;
5886 int enter_side = enter_side_horiz | enter_side_vert;
5887 int leave_side = leave_side_horiz | leave_side_vert;
5889 if (player->buried) // do not reanimate dead player
5892 if (!player_relocated) // no need to relocate the player
5895 if (IS_PLAYER(jx, jy)) // player already placed at new position
5897 RemoveField(jx, jy); // temporarily remove newly placed player
5898 DrawLevelField(jx, jy);
5901 if (player->present)
5903 while (player->MovPos)
5905 ScrollPlayer(player, SCROLL_GO_ON);
5906 ScrollScreen(NULL, SCROLL_GO_ON);
5908 AdvanceFrameAndPlayerCounters(player->index_nr);
5912 BackToFront_WithFrameDelay(wait_delay_value);
5915 DrawPlayer(player); // needed here only to cleanup last field
5916 DrawLevelField(player->jx, player->jy); // remove player graphic
5918 player->is_moving = FALSE;
5921 if (IS_CUSTOM_ELEMENT(old_element))
5922 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5924 player->index_bit, leave_side);
5926 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5928 player->index_bit, leave_side);
5930 Tile[jx][jy] = el_player;
5931 InitPlayerField(jx, jy, el_player, TRUE);
5933 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5934 possible that the relocation target field did not contain a player element,
5935 but a walkable element, to which the new player was relocated -- in this
5936 case, restore that (already initialized!) element on the player field */
5937 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5939 Tile[jx][jy] = element; // restore previously existing element
5942 // only visually relocate centered player
5943 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy,
5944 FALSE, level.instant_relocation);
5946 TestIfPlayerTouchesBadThing(jx, jy);
5947 TestIfPlayerTouchesCustomElement(jx, jy);
5949 if (IS_CUSTOM_ELEMENT(element))
5950 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5951 player->index_bit, enter_side);
5953 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5954 player->index_bit, enter_side);
5956 if (player->is_switching)
5958 /* ensure that relocation while still switching an element does not cause
5959 a new element to be treated as also switched directly after relocation
5960 (this is important for teleporter switches that teleport the player to
5961 a place where another teleporter switch is in the same direction, which
5962 would then incorrectly be treated as immediately switched before the
5963 direction key that caused the switch was released) */
5965 player->switch_x += jx - old_jx;
5966 player->switch_y += jy - old_jy;
5970 static void Explode(int ex, int ey, int phase, int mode)
5976 if (game.explosions_delayed)
5978 ExplodeField[ex][ey] = mode;
5982 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5984 int center_element = Tile[ex][ey];
5985 int ce_value = CustomValue[ex][ey];
5986 int ce_score = element_info[center_element].collect_score;
5987 int artwork_element, explosion_element; // set these values later
5989 // remove things displayed in background while burning dynamite
5990 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5993 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5995 // put moving element to center field (and let it explode there)
5996 center_element = MovingOrBlocked2Element(ex, ey);
5997 RemoveMovingField(ex, ey);
5998 Tile[ex][ey] = center_element;
6001 // now "center_element" is finally determined -- set related values now
6002 artwork_element = center_element; // for custom player artwork
6003 explosion_element = center_element; // for custom player artwork
6005 if (IS_PLAYER(ex, ey))
6007 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
6009 artwork_element = stored_player[player_nr].artwork_element;
6011 if (level.use_explosion_element[player_nr])
6013 explosion_element = level.explosion_element[player_nr];
6014 artwork_element = explosion_element;
6018 if (mode == EX_TYPE_NORMAL ||
6019 mode == EX_TYPE_CENTER ||
6020 mode == EX_TYPE_CROSS)
6021 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
6023 last_phase = element_info[explosion_element].explosion_delay + 1;
6025 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
6027 int xx = x - ex + 1;
6028 int yy = y - ey + 1;
6031 if (!IN_LEV_FIELD(x, y) ||
6032 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
6033 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
6036 element = Tile[x][y];
6038 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
6040 element = MovingOrBlocked2Element(x, y);
6042 if (!IS_EXPLOSION_PROOF(element))
6043 RemoveMovingField(x, y);
6046 // indestructible elements can only explode in center (but not flames)
6047 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
6048 mode == EX_TYPE_BORDER)) ||
6049 element == EL_FLAMES)
6052 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
6053 behaviour, for example when touching a yamyam that explodes to rocks
6054 with active deadly shield, a rock is created under the player !!! */
6055 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
6057 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
6058 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
6059 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
6061 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
6064 if (IS_ACTIVE_BOMB(element))
6066 // re-activate things under the bomb like gate or penguin
6067 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
6074 // save walkable background elements while explosion on same tile
6075 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
6076 (x != ex || y != ey || mode == EX_TYPE_BORDER))
6077 Back[x][y] = element;
6079 // ignite explodable elements reached by other explosion
6080 if (element == EL_EXPLOSION)
6081 element = Store2[x][y];
6083 if (AmoebaNr[x][y] &&
6084 (element == EL_AMOEBA_FULL ||
6085 element == EL_BD_AMOEBA ||
6086 element == EL_AMOEBA_GROWING))
6088 AmoebaCnt[AmoebaNr[x][y]]--;
6089 AmoebaCnt2[AmoebaNr[x][y]]--;
6094 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
6096 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
6098 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
6100 if (PLAYERINFO(ex, ey)->use_murphy)
6101 Store[x][y] = EL_EMPTY;
6104 // !!! check this case -- currently needed for rnd_rado_negundo_v,
6105 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
6106 else if (IS_PLAYER_ELEMENT(center_element))
6107 Store[x][y] = EL_EMPTY;
6108 else if (center_element == EL_YAMYAM)
6109 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
6110 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
6111 Store[x][y] = element_info[center_element].content.e[xx][yy];
6113 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
6114 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
6115 // otherwise) -- FIX THIS !!!
6116 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
6117 Store[x][y] = element_info[element].content.e[1][1];
6119 else if (!CAN_EXPLODE(element))
6120 Store[x][y] = element_info[element].content.e[1][1];
6123 Store[x][y] = EL_EMPTY;
6125 if (IS_CUSTOM_ELEMENT(center_element))
6126 Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value :
6127 Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score :
6128 Store[x][y] >= EL_PREV_CE_8 &&
6129 Store[x][y] <= EL_NEXT_CE_8 ?
6130 RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) :
6133 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
6134 center_element == EL_AMOEBA_TO_DIAMOND)
6135 Store2[x][y] = element;
6137 Tile[x][y] = EL_EXPLOSION;
6138 GfxElement[x][y] = artwork_element;
6140 ExplodePhase[x][y] = 1;
6141 ExplodeDelay[x][y] = last_phase;
6146 if (center_element == EL_YAMYAM)
6147 game.yamyam_content_nr =
6148 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
6160 GfxFrame[x][y] = 0; // restart explosion animation
6162 last_phase = ExplodeDelay[x][y];
6164 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
6166 // this can happen if the player leaves an explosion just in time
6167 if (GfxElement[x][y] == EL_UNDEFINED)
6168 GfxElement[x][y] = EL_EMPTY;
6170 border_element = Store2[x][y];
6171 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6172 border_element = StorePlayer[x][y];
6174 if (phase == element_info[border_element].ignition_delay ||
6175 phase == last_phase)
6177 boolean border_explosion = FALSE;
6179 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6180 !PLAYER_EXPLOSION_PROTECTED(x, y))
6182 KillPlayerUnlessExplosionProtected(x, y);
6183 border_explosion = TRUE;
6185 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6187 Tile[x][y] = Store2[x][y];
6190 border_explosion = TRUE;
6192 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6194 AmoebaToDiamond(x, y);
6196 border_explosion = TRUE;
6199 // if an element just explodes due to another explosion (chain-reaction),
6200 // do not immediately end the new explosion when it was the last frame of
6201 // the explosion (as it would be done in the following "if"-statement!)
6202 if (border_explosion && phase == last_phase)
6206 // this can happen if the player was just killed by an explosion
6207 if (GfxElement[x][y] == EL_UNDEFINED)
6208 GfxElement[x][y] = EL_EMPTY;
6210 if (phase == last_phase)
6214 element = Tile[x][y] = Store[x][y];
6215 Store[x][y] = Store2[x][y] = 0;
6216 GfxElement[x][y] = EL_UNDEFINED;
6218 // player can escape from explosions and might therefore be still alive
6219 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6220 element <= EL_PLAYER_IS_EXPLODING_4)
6222 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6223 int explosion_element = EL_PLAYER_1 + player_nr;
6224 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6225 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6227 if (level.use_explosion_element[player_nr])
6228 explosion_element = level.explosion_element[player_nr];
6230 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6231 element_info[explosion_element].content.e[xx][yy]);
6234 // restore probably existing indestructible background element
6235 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6236 element = Tile[x][y] = Back[x][y];
6239 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6240 GfxDir[x][y] = MV_NONE;
6241 ChangeDelay[x][y] = 0;
6242 ChangePage[x][y] = -1;
6244 CustomValue[x][y] = 0;
6246 InitField_WithBug2(x, y, FALSE);
6248 TEST_DrawLevelField(x, y);
6250 TestIfElementTouchesCustomElement(x, y);
6252 if (GFX_CRUMBLED(element))
6253 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6255 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6256 StorePlayer[x][y] = 0;
6258 if (IS_PLAYER_ELEMENT(element))
6259 RelocatePlayer(x, y, element);
6261 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6263 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6264 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6267 TEST_DrawLevelFieldCrumbled(x, y);
6269 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6271 DrawLevelElement(x, y, Back[x][y]);
6272 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6274 else if (IS_WALKABLE_UNDER(Back[x][y]))
6276 DrawLevelGraphic(x, y, graphic, frame);
6277 DrawLevelElementThruMask(x, y, Back[x][y]);
6279 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6280 DrawLevelGraphic(x, y, graphic, frame);
6284 static void DynaExplode(int ex, int ey)
6287 int dynabomb_element = Tile[ex][ey];
6288 int dynabomb_size = 1;
6289 boolean dynabomb_xl = FALSE;
6290 struct PlayerInfo *player;
6291 struct XY *xy = xy_topdown;
6293 if (IS_ACTIVE_BOMB(dynabomb_element))
6295 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6296 dynabomb_size = player->dynabomb_size;
6297 dynabomb_xl = player->dynabomb_xl;
6298 player->dynabombs_left++;
6301 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6303 for (i = 0; i < NUM_DIRECTIONS; i++)
6305 for (j = 1; j <= dynabomb_size; j++)
6307 int x = ex + j * xy[i].x;
6308 int y = ey + j * xy[i].y;
6311 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6314 element = Tile[x][y];
6316 // do not restart explosions of fields with active bombs
6317 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6320 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6322 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6323 !IS_DIGGABLE(element) && !dynabomb_xl)
6329 void Bang(int x, int y)
6331 int element = MovingOrBlocked2Element(x, y);
6332 int explosion_type = EX_TYPE_NORMAL;
6334 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6336 struct PlayerInfo *player = PLAYERINFO(x, y);
6338 element = Tile[x][y] = player->initial_element;
6340 if (level.use_explosion_element[player->index_nr])
6342 int explosion_element = level.explosion_element[player->index_nr];
6344 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6345 explosion_type = EX_TYPE_CROSS;
6346 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6347 explosion_type = EX_TYPE_CENTER;
6355 case EL_BD_BUTTERFLY:
6358 case EL_DARK_YAMYAM:
6362 RaiseScoreElement(element);
6365 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6366 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6367 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6368 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6369 case EL_DYNABOMB_INCREASE_NUMBER:
6370 case EL_DYNABOMB_INCREASE_SIZE:
6371 case EL_DYNABOMB_INCREASE_POWER:
6372 explosion_type = EX_TYPE_DYNA;
6375 case EL_DC_LANDMINE:
6376 explosion_type = EX_TYPE_CENTER;
6381 case EL_LAMP_ACTIVE:
6382 case EL_AMOEBA_TO_DIAMOND:
6383 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6384 explosion_type = EX_TYPE_CENTER;
6388 if (element_info[element].explosion_type == EXPLODES_CROSS)
6389 explosion_type = EX_TYPE_CROSS;
6390 else if (element_info[element].explosion_type == EXPLODES_1X1)
6391 explosion_type = EX_TYPE_CENTER;
6395 if (explosion_type == EX_TYPE_DYNA)
6398 Explode(x, y, EX_PHASE_START, explosion_type);
6400 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6403 static void SplashAcid(int x, int y)
6405 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6406 (!IN_LEV_FIELD(x - 1, y - 2) ||
6407 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6408 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6410 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6411 (!IN_LEV_FIELD(x + 1, y - 2) ||
6412 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6413 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6415 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6418 static void InitBeltMovement(void)
6420 static int belt_base_element[4] =
6422 EL_CONVEYOR_BELT_1_LEFT,
6423 EL_CONVEYOR_BELT_2_LEFT,
6424 EL_CONVEYOR_BELT_3_LEFT,
6425 EL_CONVEYOR_BELT_4_LEFT
6427 static int belt_base_active_element[4] =
6429 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6430 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6431 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6432 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6437 // set frame order for belt animation graphic according to belt direction
6438 for (i = 0; i < NUM_BELTS; i++)
6442 for (j = 0; j < NUM_BELT_PARTS; j++)
6444 int element = belt_base_active_element[belt_nr] + j;
6445 int graphic_1 = el2img(element);
6446 int graphic_2 = el2panelimg(element);
6448 if (game.belt_dir[i] == MV_LEFT)
6450 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6451 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6455 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6456 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6461 SCAN_PLAYFIELD(x, y)
6463 int element = Tile[x][y];
6465 for (i = 0; i < NUM_BELTS; i++)
6467 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6469 int e_belt_nr = getBeltNrFromBeltElement(element);
6472 if (e_belt_nr == belt_nr)
6474 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6476 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6483 static void ToggleBeltSwitch(int x, int y)
6485 static int belt_base_element[4] =
6487 EL_CONVEYOR_BELT_1_LEFT,
6488 EL_CONVEYOR_BELT_2_LEFT,
6489 EL_CONVEYOR_BELT_3_LEFT,
6490 EL_CONVEYOR_BELT_4_LEFT
6492 static int belt_base_active_element[4] =
6494 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6495 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6496 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6497 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6499 static int belt_base_switch_element[4] =
6501 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6502 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6503 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6504 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6506 static int belt_move_dir[4] =
6514 int element = Tile[x][y];
6515 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6516 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6517 int belt_dir = belt_move_dir[belt_dir_nr];
6520 if (!IS_BELT_SWITCH(element))
6523 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6524 game.belt_dir[belt_nr] = belt_dir;
6526 if (belt_dir_nr == 3)
6529 // set frame order for belt animation graphic according to belt direction
6530 for (i = 0; i < NUM_BELT_PARTS; i++)
6532 int element = belt_base_active_element[belt_nr] + i;
6533 int graphic_1 = el2img(element);
6534 int graphic_2 = el2panelimg(element);
6536 if (belt_dir == MV_LEFT)
6538 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6539 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6543 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6544 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6548 SCAN_PLAYFIELD(xx, yy)
6550 int element = Tile[xx][yy];
6552 if (IS_BELT_SWITCH(element))
6554 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6556 if (e_belt_nr == belt_nr)
6558 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6559 TEST_DrawLevelField(xx, yy);
6562 else if (IS_BELT(element) && belt_dir != MV_NONE)
6564 int e_belt_nr = getBeltNrFromBeltElement(element);
6566 if (e_belt_nr == belt_nr)
6568 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6570 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6571 TEST_DrawLevelField(xx, yy);
6574 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6576 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6578 if (e_belt_nr == belt_nr)
6580 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6582 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6583 TEST_DrawLevelField(xx, yy);
6589 static void ToggleSwitchgateSwitch(void)
6593 game.switchgate_pos = !game.switchgate_pos;
6595 SCAN_PLAYFIELD(xx, yy)
6597 int element = Tile[xx][yy];
6599 if (element == EL_SWITCHGATE_SWITCH_UP)
6601 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6602 TEST_DrawLevelField(xx, yy);
6604 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6606 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6607 TEST_DrawLevelField(xx, yy);
6609 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6611 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6612 TEST_DrawLevelField(xx, yy);
6614 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6616 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6617 TEST_DrawLevelField(xx, yy);
6619 else if (element == EL_SWITCHGATE_OPEN ||
6620 element == EL_SWITCHGATE_OPENING)
6622 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6624 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6626 else if (element == EL_SWITCHGATE_CLOSED ||
6627 element == EL_SWITCHGATE_CLOSING)
6629 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6631 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6636 static int getInvisibleActiveFromInvisibleElement(int element)
6638 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6639 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6640 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6644 static int getInvisibleFromInvisibleActiveElement(int element)
6646 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6647 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6648 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6652 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6656 SCAN_PLAYFIELD(x, y)
6658 int element = Tile[x][y];
6660 if (element == EL_LIGHT_SWITCH &&
6661 game.light_time_left > 0)
6663 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6664 TEST_DrawLevelField(x, y);
6666 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6667 game.light_time_left == 0)
6669 Tile[x][y] = EL_LIGHT_SWITCH;
6670 TEST_DrawLevelField(x, y);
6672 else if (element == EL_EMC_DRIPPER &&
6673 game.light_time_left > 0)
6675 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6676 TEST_DrawLevelField(x, y);
6678 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6679 game.light_time_left == 0)
6681 Tile[x][y] = EL_EMC_DRIPPER;
6682 TEST_DrawLevelField(x, y);
6684 else if (element == EL_INVISIBLE_STEELWALL ||
6685 element == EL_INVISIBLE_WALL ||
6686 element == EL_INVISIBLE_SAND)
6688 if (game.light_time_left > 0)
6689 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6691 TEST_DrawLevelField(x, y);
6693 // uncrumble neighbour fields, if needed
6694 if (element == EL_INVISIBLE_SAND)
6695 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6697 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6698 element == EL_INVISIBLE_WALL_ACTIVE ||
6699 element == EL_INVISIBLE_SAND_ACTIVE)
6701 if (game.light_time_left == 0)
6702 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6704 TEST_DrawLevelField(x, y);
6706 // re-crumble neighbour fields, if needed
6707 if (element == EL_INVISIBLE_SAND)
6708 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6713 static void RedrawAllInvisibleElementsForLenses(void)
6717 SCAN_PLAYFIELD(x, y)
6719 int element = Tile[x][y];
6721 if (element == EL_EMC_DRIPPER &&
6722 game.lenses_time_left > 0)
6724 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6725 TEST_DrawLevelField(x, y);
6727 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6728 game.lenses_time_left == 0)
6730 Tile[x][y] = EL_EMC_DRIPPER;
6731 TEST_DrawLevelField(x, y);
6733 else if (element == EL_INVISIBLE_STEELWALL ||
6734 element == EL_INVISIBLE_WALL ||
6735 element == EL_INVISIBLE_SAND)
6737 if (game.lenses_time_left > 0)
6738 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6740 TEST_DrawLevelField(x, y);
6742 // uncrumble neighbour fields, if needed
6743 if (element == EL_INVISIBLE_SAND)
6744 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6746 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6747 element == EL_INVISIBLE_WALL_ACTIVE ||
6748 element == EL_INVISIBLE_SAND_ACTIVE)
6750 if (game.lenses_time_left == 0)
6751 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6753 TEST_DrawLevelField(x, y);
6755 // re-crumble neighbour fields, if needed
6756 if (element == EL_INVISIBLE_SAND)
6757 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6762 static void RedrawAllInvisibleElementsForMagnifier(void)
6766 SCAN_PLAYFIELD(x, y)
6768 int element = Tile[x][y];
6770 if (element == EL_EMC_FAKE_GRASS &&
6771 game.magnify_time_left > 0)
6773 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6774 TEST_DrawLevelField(x, y);
6776 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6777 game.magnify_time_left == 0)
6779 Tile[x][y] = EL_EMC_FAKE_GRASS;
6780 TEST_DrawLevelField(x, y);
6782 else if (IS_GATE_GRAY(element) &&
6783 game.magnify_time_left > 0)
6785 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6786 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6787 IS_EM_GATE_GRAY(element) ?
6788 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6789 IS_EMC_GATE_GRAY(element) ?
6790 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6791 IS_DC_GATE_GRAY(element) ?
6792 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6794 TEST_DrawLevelField(x, y);
6796 else if (IS_GATE_GRAY_ACTIVE(element) &&
6797 game.magnify_time_left == 0)
6799 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6800 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6801 IS_EM_GATE_GRAY_ACTIVE(element) ?
6802 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6803 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6804 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6805 IS_DC_GATE_GRAY_ACTIVE(element) ?
6806 EL_DC_GATE_WHITE_GRAY :
6808 TEST_DrawLevelField(x, y);
6813 static void ToggleLightSwitch(int x, int y)
6815 int element = Tile[x][y];
6817 game.light_time_left =
6818 (element == EL_LIGHT_SWITCH ?
6819 level.time_light * FRAMES_PER_SECOND : 0);
6821 RedrawAllLightSwitchesAndInvisibleElements();
6824 static void ActivateTimegateSwitch(int x, int y)
6828 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6830 SCAN_PLAYFIELD(xx, yy)
6832 int element = Tile[xx][yy];
6834 if (element == EL_TIMEGATE_CLOSED ||
6835 element == EL_TIMEGATE_CLOSING)
6837 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6838 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6842 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6844 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6845 TEST_DrawLevelField(xx, yy);
6851 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6852 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6855 static void Impact(int x, int y)
6857 boolean last_line = (y == lev_fieldy - 1);
6858 boolean object_hit = FALSE;
6859 boolean impact = (last_line || object_hit);
6860 int element = Tile[x][y];
6861 int smashed = EL_STEELWALL;
6863 if (!last_line) // check if element below was hit
6865 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6868 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6869 MovDir[x][y + 1] != MV_DOWN ||
6870 MovPos[x][y + 1] <= TILEY / 2));
6872 // do not smash moving elements that left the smashed field in time
6873 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6874 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6877 #if USE_QUICKSAND_IMPACT_BUGFIX
6878 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6880 RemoveMovingField(x, y + 1);
6881 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6882 Tile[x][y + 2] = EL_ROCK;
6883 TEST_DrawLevelField(x, y + 2);
6888 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6890 RemoveMovingField(x, y + 1);
6891 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6892 Tile[x][y + 2] = EL_ROCK;
6893 TEST_DrawLevelField(x, y + 2);
6900 smashed = MovingOrBlocked2Element(x, y + 1);
6902 impact = (last_line || object_hit);
6905 if (!last_line && smashed == EL_ACID) // element falls into acid
6907 SplashAcid(x, y + 1);
6911 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6912 // only reset graphic animation if graphic really changes after impact
6914 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6916 ResetGfxAnimation(x, y);
6917 TEST_DrawLevelField(x, y);
6920 if (impact && CAN_EXPLODE_IMPACT(element))
6925 else if (impact && element == EL_PEARL &&
6926 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6928 ResetGfxAnimation(x, y);
6930 Tile[x][y] = EL_PEARL_BREAKING;
6931 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6934 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6936 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6941 if (impact && element == EL_AMOEBA_DROP)
6943 if (object_hit && IS_PLAYER(x, y + 1))
6944 KillPlayerUnlessEnemyProtected(x, y + 1);
6945 else if (object_hit && smashed == EL_PENGUIN)
6949 Tile[x][y] = EL_AMOEBA_GROWING;
6950 Store[x][y] = EL_AMOEBA_WET;
6952 ResetRandomAnimationValue(x, y);
6957 if (object_hit) // check which object was hit
6959 if ((CAN_PASS_MAGIC_WALL(element) &&
6960 (smashed == EL_MAGIC_WALL ||
6961 smashed == EL_BD_MAGIC_WALL)) ||
6962 (CAN_PASS_DC_MAGIC_WALL(element) &&
6963 smashed == EL_DC_MAGIC_WALL))
6966 int activated_magic_wall =
6967 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6968 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6969 EL_DC_MAGIC_WALL_ACTIVE);
6971 // activate magic wall / mill
6972 SCAN_PLAYFIELD(xx, yy)
6974 if (Tile[xx][yy] == smashed)
6975 Tile[xx][yy] = activated_magic_wall;
6978 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6979 game.magic_wall_active = TRUE;
6981 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6982 SND_MAGIC_WALL_ACTIVATING :
6983 smashed == EL_BD_MAGIC_WALL ?
6984 SND_BD_MAGIC_WALL_ACTIVATING :
6985 SND_DC_MAGIC_WALL_ACTIVATING));
6988 if (IS_PLAYER(x, y + 1))
6990 if (CAN_SMASH_PLAYER(element))
6992 KillPlayerUnlessEnemyProtected(x, y + 1);
6996 else if (smashed == EL_PENGUIN)
6998 if (CAN_SMASH_PLAYER(element))
7004 else if (element == EL_BD_DIAMOND)
7006 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
7012 else if (((element == EL_SP_INFOTRON ||
7013 element == EL_SP_ZONK) &&
7014 (smashed == EL_SP_SNIKSNAK ||
7015 smashed == EL_SP_ELECTRON ||
7016 smashed == EL_SP_DISK_ORANGE)) ||
7017 (element == EL_SP_INFOTRON &&
7018 smashed == EL_SP_DISK_YELLOW))
7023 else if (CAN_SMASH_EVERYTHING(element))
7025 if (IS_CLASSIC_ENEMY(smashed) ||
7026 CAN_EXPLODE_SMASHED(smashed))
7031 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
7033 if (smashed == EL_LAMP ||
7034 smashed == EL_LAMP_ACTIVE)
7039 else if (smashed == EL_NUT)
7041 Tile[x][y + 1] = EL_NUT_BREAKING;
7042 PlayLevelSound(x, y, SND_NUT_BREAKING);
7043 RaiseScoreElement(EL_NUT);
7046 else if (smashed == EL_PEARL)
7048 ResetGfxAnimation(x, y);
7050 Tile[x][y + 1] = EL_PEARL_BREAKING;
7051 PlayLevelSound(x, y, SND_PEARL_BREAKING);
7054 else if (smashed == EL_DIAMOND)
7056 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
7057 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
7060 else if (IS_BELT_SWITCH(smashed))
7062 ToggleBeltSwitch(x, y + 1);
7064 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
7065 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
7066 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
7067 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
7069 ToggleSwitchgateSwitch();
7071 else if (smashed == EL_LIGHT_SWITCH ||
7072 smashed == EL_LIGHT_SWITCH_ACTIVE)
7074 ToggleLightSwitch(x, y + 1);
7078 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7080 CheckElementChangeBySide(x, y + 1, smashed, element,
7081 CE_SWITCHED, CH_SIDE_TOP);
7082 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
7088 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7093 // play sound of magic wall / mill
7095 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7096 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
7097 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
7099 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7100 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
7101 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7102 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
7103 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7104 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
7109 // play sound of object that hits the ground
7110 if (last_line || object_hit)
7111 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
7114 static void TurnRoundExt(int x, int y)
7126 { 0, 0 }, { 0, 0 }, { 0, 0 },
7131 int left, right, back;
7135 { MV_DOWN, MV_UP, MV_RIGHT },
7136 { MV_UP, MV_DOWN, MV_LEFT },
7138 { MV_LEFT, MV_RIGHT, MV_DOWN },
7142 { MV_RIGHT, MV_LEFT, MV_UP }
7145 int element = Tile[x][y];
7146 int move_pattern = element_info[element].move_pattern;
7148 int old_move_dir = MovDir[x][y];
7149 int left_dir = turn[old_move_dir].left;
7150 int right_dir = turn[old_move_dir].right;
7151 int back_dir = turn[old_move_dir].back;
7153 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
7154 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
7155 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
7156 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
7158 int left_x = x + left_dx, left_y = y + left_dy;
7159 int right_x = x + right_dx, right_y = y + right_dy;
7160 int move_x = x + move_dx, move_y = y + move_dy;
7164 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
7166 TestIfBadThingTouchesOtherBadThing(x, y);
7168 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
7169 MovDir[x][y] = right_dir;
7170 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7171 MovDir[x][y] = left_dir;
7173 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7175 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7178 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7180 TestIfBadThingTouchesOtherBadThing(x, y);
7182 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7183 MovDir[x][y] = left_dir;
7184 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7185 MovDir[x][y] = right_dir;
7187 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7189 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7192 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7194 TestIfBadThingTouchesOtherBadThing(x, y);
7196 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7197 MovDir[x][y] = left_dir;
7198 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7199 MovDir[x][y] = right_dir;
7201 if (MovDir[x][y] != old_move_dir)
7204 else if (element == EL_YAMYAM)
7206 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7207 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7209 if (can_turn_left && can_turn_right)
7210 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7211 else if (can_turn_left)
7212 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7213 else if (can_turn_right)
7214 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7216 MovDir[x][y] = back_dir;
7218 MovDelay[x][y] = 16 + 16 * RND(3);
7220 else if (element == EL_DARK_YAMYAM)
7222 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7224 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7227 if (can_turn_left && can_turn_right)
7228 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7229 else if (can_turn_left)
7230 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7231 else if (can_turn_right)
7232 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7234 MovDir[x][y] = back_dir;
7236 MovDelay[x][y] = 16 + 16 * RND(3);
7238 else if (element == EL_PACMAN)
7240 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7241 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7243 if (can_turn_left && can_turn_right)
7244 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7245 else if (can_turn_left)
7246 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7247 else if (can_turn_right)
7248 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7250 MovDir[x][y] = back_dir;
7252 MovDelay[x][y] = 6 + RND(40);
7254 else if (element == EL_PIG)
7256 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7257 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7258 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7259 boolean should_turn_left, should_turn_right, should_move_on;
7261 int rnd = RND(rnd_value);
7263 should_turn_left = (can_turn_left &&
7265 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7266 y + back_dy + left_dy)));
7267 should_turn_right = (can_turn_right &&
7269 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7270 y + back_dy + right_dy)));
7271 should_move_on = (can_move_on &&
7274 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7275 y + move_dy + left_dy) ||
7276 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7277 y + move_dy + right_dy)));
7279 if (should_turn_left || should_turn_right || should_move_on)
7281 if (should_turn_left && should_turn_right && should_move_on)
7282 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7283 rnd < 2 * rnd_value / 3 ? right_dir :
7285 else if (should_turn_left && should_turn_right)
7286 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7287 else if (should_turn_left && should_move_on)
7288 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7289 else if (should_turn_right && should_move_on)
7290 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7291 else if (should_turn_left)
7292 MovDir[x][y] = left_dir;
7293 else if (should_turn_right)
7294 MovDir[x][y] = right_dir;
7295 else if (should_move_on)
7296 MovDir[x][y] = old_move_dir;
7298 else if (can_move_on && rnd > rnd_value / 8)
7299 MovDir[x][y] = old_move_dir;
7300 else if (can_turn_left && can_turn_right)
7301 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7302 else if (can_turn_left && rnd > rnd_value / 8)
7303 MovDir[x][y] = left_dir;
7304 else if (can_turn_right && rnd > rnd_value/8)
7305 MovDir[x][y] = right_dir;
7307 MovDir[x][y] = back_dir;
7309 xx = x + move_xy[MovDir[x][y]].dx;
7310 yy = y + move_xy[MovDir[x][y]].dy;
7312 if (!IN_LEV_FIELD(xx, yy) ||
7313 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7314 MovDir[x][y] = old_move_dir;
7318 else if (element == EL_DRAGON)
7320 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7321 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7322 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7324 int rnd = RND(rnd_value);
7326 if (can_move_on && rnd > rnd_value / 8)
7327 MovDir[x][y] = old_move_dir;
7328 else if (can_turn_left && can_turn_right)
7329 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7330 else if (can_turn_left && rnd > rnd_value / 8)
7331 MovDir[x][y] = left_dir;
7332 else if (can_turn_right && rnd > rnd_value / 8)
7333 MovDir[x][y] = right_dir;
7335 MovDir[x][y] = back_dir;
7337 xx = x + move_xy[MovDir[x][y]].dx;
7338 yy = y + move_xy[MovDir[x][y]].dy;
7340 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7341 MovDir[x][y] = old_move_dir;
7345 else if (element == EL_MOLE)
7347 boolean can_move_on =
7348 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7349 IS_AMOEBOID(Tile[move_x][move_y]) ||
7350 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7353 boolean can_turn_left =
7354 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7355 IS_AMOEBOID(Tile[left_x][left_y])));
7357 boolean can_turn_right =
7358 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7359 IS_AMOEBOID(Tile[right_x][right_y])));
7361 if (can_turn_left && can_turn_right)
7362 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7363 else if (can_turn_left)
7364 MovDir[x][y] = left_dir;
7366 MovDir[x][y] = right_dir;
7369 if (MovDir[x][y] != old_move_dir)
7372 else if (element == EL_BALLOON)
7374 MovDir[x][y] = game.wind_direction;
7377 else if (element == EL_SPRING)
7379 if (MovDir[x][y] & MV_HORIZONTAL)
7381 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7382 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7384 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7385 ResetGfxAnimation(move_x, move_y);
7386 TEST_DrawLevelField(move_x, move_y);
7388 MovDir[x][y] = back_dir;
7390 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7391 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7392 MovDir[x][y] = MV_NONE;
7397 else if (element == EL_ROBOT ||
7398 element == EL_SATELLITE ||
7399 element == EL_PENGUIN ||
7400 element == EL_EMC_ANDROID)
7402 int attr_x = -1, attr_y = -1;
7404 if (game.all_players_gone)
7406 attr_x = game.exit_x;
7407 attr_y = game.exit_y;
7413 for (i = 0; i < MAX_PLAYERS; i++)
7415 struct PlayerInfo *player = &stored_player[i];
7416 int jx = player->jx, jy = player->jy;
7418 if (!player->active)
7422 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7430 if (element == EL_ROBOT &&
7431 game.robot_wheel_x >= 0 &&
7432 game.robot_wheel_y >= 0 &&
7433 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7434 game.engine_version < VERSION_IDENT(3,1,0,0)))
7436 attr_x = game.robot_wheel_x;
7437 attr_y = game.robot_wheel_y;
7440 if (element == EL_PENGUIN)
7443 struct XY *xy = xy_topdown;
7445 for (i = 0; i < NUM_DIRECTIONS; i++)
7447 int ex = x + xy[i].x;
7448 int ey = y + xy[i].y;
7450 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7451 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7452 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7453 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7462 MovDir[x][y] = MV_NONE;
7464 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7465 else if (attr_x > x)
7466 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7468 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7469 else if (attr_y > y)
7470 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7472 if (element == EL_ROBOT)
7476 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7477 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7478 Moving2Blocked(x, y, &newx, &newy);
7480 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7481 MovDelay[x][y] = 8 + 8 * !RND(3);
7483 MovDelay[x][y] = 16;
7485 else if (element == EL_PENGUIN)
7491 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7493 boolean first_horiz = RND(2);
7494 int new_move_dir = MovDir[x][y];
7497 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7498 Moving2Blocked(x, y, &newx, &newy);
7500 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7504 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7505 Moving2Blocked(x, y, &newx, &newy);
7507 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7510 MovDir[x][y] = old_move_dir;
7514 else if (element == EL_SATELLITE)
7520 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7522 boolean first_horiz = RND(2);
7523 int new_move_dir = MovDir[x][y];
7526 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7527 Moving2Blocked(x, y, &newx, &newy);
7529 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7533 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7534 Moving2Blocked(x, y, &newx, &newy);
7536 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7539 MovDir[x][y] = old_move_dir;
7543 else if (element == EL_EMC_ANDROID)
7545 static int check_pos[16] =
7547 -1, // 0 => (invalid)
7550 -1, // 3 => (invalid)
7552 0, // 5 => MV_LEFT | MV_UP
7553 2, // 6 => MV_RIGHT | MV_UP
7554 -1, // 7 => (invalid)
7556 6, // 9 => MV_LEFT | MV_DOWN
7557 4, // 10 => MV_RIGHT | MV_DOWN
7558 -1, // 11 => (invalid)
7559 -1, // 12 => (invalid)
7560 -1, // 13 => (invalid)
7561 -1, // 14 => (invalid)
7562 -1, // 15 => (invalid)
7570 { -1, -1, MV_LEFT | MV_UP },
7572 { +1, -1, MV_RIGHT | MV_UP },
7573 { +1, 0, MV_RIGHT },
7574 { +1, +1, MV_RIGHT | MV_DOWN },
7576 { -1, +1, MV_LEFT | MV_DOWN },
7579 int start_pos, check_order;
7580 boolean can_clone = FALSE;
7583 // check if there is any free field around current position
7584 for (i = 0; i < 8; i++)
7586 int newx = x + check_xy[i].dx;
7587 int newy = y + check_xy[i].dy;
7589 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7597 if (can_clone) // randomly find an element to clone
7601 start_pos = check_pos[RND(8)];
7602 check_order = (RND(2) ? -1 : +1);
7604 for (i = 0; i < 8; i++)
7606 int pos_raw = start_pos + i * check_order;
7607 int pos = (pos_raw + 8) % 8;
7608 int newx = x + check_xy[pos].dx;
7609 int newy = y + check_xy[pos].dy;
7611 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7613 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7614 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7616 Store[x][y] = Tile[newx][newy];
7625 if (can_clone) // randomly find a direction to move
7629 start_pos = check_pos[RND(8)];
7630 check_order = (RND(2) ? -1 : +1);
7632 for (i = 0; i < 8; i++)
7634 int pos_raw = start_pos + i * check_order;
7635 int pos = (pos_raw + 8) % 8;
7636 int newx = x + check_xy[pos].dx;
7637 int newy = y + check_xy[pos].dy;
7638 int new_move_dir = check_xy[pos].dir;
7640 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7642 MovDir[x][y] = new_move_dir;
7643 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7652 if (can_clone) // cloning and moving successful
7655 // cannot clone -- try to move towards player
7657 start_pos = check_pos[MovDir[x][y] & 0x0f];
7658 check_order = (RND(2) ? -1 : +1);
7660 for (i = 0; i < 3; i++)
7662 // first check start_pos, then previous/next or (next/previous) pos
7663 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7664 int pos = (pos_raw + 8) % 8;
7665 int newx = x + check_xy[pos].dx;
7666 int newy = y + check_xy[pos].dy;
7667 int new_move_dir = check_xy[pos].dir;
7669 if (IS_PLAYER(newx, newy))
7672 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7674 MovDir[x][y] = new_move_dir;
7675 MovDelay[x][y] = level.android_move_time * 8 + 1;
7682 else if (move_pattern == MV_TURNING_LEFT ||
7683 move_pattern == MV_TURNING_RIGHT ||
7684 move_pattern == MV_TURNING_LEFT_RIGHT ||
7685 move_pattern == MV_TURNING_RIGHT_LEFT ||
7686 move_pattern == MV_TURNING_RANDOM ||
7687 move_pattern == MV_ALL_DIRECTIONS)
7689 boolean can_turn_left =
7690 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7691 boolean can_turn_right =
7692 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y);
7694 if (element_info[element].move_stepsize == 0) // "not moving"
7697 if (move_pattern == MV_TURNING_LEFT)
7698 MovDir[x][y] = left_dir;
7699 else if (move_pattern == MV_TURNING_RIGHT)
7700 MovDir[x][y] = right_dir;
7701 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7702 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7703 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7704 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7705 else if (move_pattern == MV_TURNING_RANDOM)
7706 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7707 can_turn_right && !can_turn_left ? right_dir :
7708 RND(2) ? left_dir : right_dir);
7709 else if (can_turn_left && can_turn_right)
7710 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7711 else if (can_turn_left)
7712 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7713 else if (can_turn_right)
7714 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7716 MovDir[x][y] = back_dir;
7718 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7720 else if (move_pattern == MV_HORIZONTAL ||
7721 move_pattern == MV_VERTICAL)
7723 if (move_pattern & old_move_dir)
7724 MovDir[x][y] = back_dir;
7725 else if (move_pattern == MV_HORIZONTAL)
7726 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7727 else if (move_pattern == MV_VERTICAL)
7728 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7730 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7732 else if (move_pattern & MV_ANY_DIRECTION)
7734 MovDir[x][y] = move_pattern;
7735 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7737 else if (move_pattern & MV_WIND_DIRECTION)
7739 MovDir[x][y] = game.wind_direction;
7740 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7742 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7744 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7745 MovDir[x][y] = left_dir;
7746 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7747 MovDir[x][y] = right_dir;
7749 if (MovDir[x][y] != old_move_dir)
7750 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7752 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7754 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7755 MovDir[x][y] = right_dir;
7756 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7757 MovDir[x][y] = left_dir;
7759 if (MovDir[x][y] != old_move_dir)
7760 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7762 else if (move_pattern == MV_TOWARDS_PLAYER ||
7763 move_pattern == MV_AWAY_FROM_PLAYER)
7765 int attr_x = -1, attr_y = -1;
7767 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7769 if (game.all_players_gone)
7771 attr_x = game.exit_x;
7772 attr_y = game.exit_y;
7778 for (i = 0; i < MAX_PLAYERS; i++)
7780 struct PlayerInfo *player = &stored_player[i];
7781 int jx = player->jx, jy = player->jy;
7783 if (!player->active)
7787 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7795 MovDir[x][y] = MV_NONE;
7797 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7798 else if (attr_x > x)
7799 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7801 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7802 else if (attr_y > y)
7803 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7805 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7807 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7809 boolean first_horiz = RND(2);
7810 int new_move_dir = MovDir[x][y];
7812 if (element_info[element].move_stepsize == 0) // "not moving"
7814 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7815 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7821 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7822 Moving2Blocked(x, y, &newx, &newy);
7824 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7828 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7829 Moving2Blocked(x, y, &newx, &newy);
7831 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7834 MovDir[x][y] = old_move_dir;
7837 else if (move_pattern == MV_WHEN_PUSHED ||
7838 move_pattern == MV_WHEN_DROPPED)
7840 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7841 MovDir[x][y] = MV_NONE;
7845 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7847 struct XY *test_xy = xy_topdown;
7848 static int test_dir[4] =
7855 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7856 int move_preference = -1000000; // start with very low preference
7857 int new_move_dir = MV_NONE;
7858 int start_test = RND(4);
7861 for (i = 0; i < NUM_DIRECTIONS; i++)
7863 int j = (start_test + i) % 4;
7864 int move_dir = test_dir[j];
7865 int move_dir_preference;
7867 xx = x + test_xy[j].x;
7868 yy = y + test_xy[j].y;
7870 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7871 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7873 new_move_dir = move_dir;
7878 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7881 move_dir_preference = -1 * RunnerVisit[xx][yy];
7882 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7883 move_dir_preference = PlayerVisit[xx][yy];
7885 if (move_dir_preference > move_preference)
7887 // prefer field that has not been visited for the longest time
7888 move_preference = move_dir_preference;
7889 new_move_dir = move_dir;
7891 else if (move_dir_preference == move_preference &&
7892 move_dir == old_move_dir)
7894 // prefer last direction when all directions are preferred equally
7895 move_preference = move_dir_preference;
7896 new_move_dir = move_dir;
7900 MovDir[x][y] = new_move_dir;
7901 if (old_move_dir != new_move_dir)
7902 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7906 static void TurnRound(int x, int y)
7908 int direction = MovDir[x][y];
7912 GfxDir[x][y] = MovDir[x][y];
7914 if (direction != MovDir[x][y])
7918 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7920 ResetGfxFrame(x, y);
7923 static boolean JustBeingPushed(int x, int y)
7927 for (i = 0; i < MAX_PLAYERS; i++)
7929 struct PlayerInfo *player = &stored_player[i];
7931 if (player->active && player->is_pushing && player->MovPos)
7933 int next_jx = player->jx + (player->jx - player->last_jx);
7934 int next_jy = player->jy + (player->jy - player->last_jy);
7936 if (x == next_jx && y == next_jy)
7944 static void StartMoving(int x, int y)
7946 boolean started_moving = FALSE; // some elements can fall _and_ move
7947 int element = Tile[x][y];
7952 if (MovDelay[x][y] == 0)
7953 GfxAction[x][y] = ACTION_DEFAULT;
7955 if (CAN_FALL(element) && y < lev_fieldy - 1)
7957 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7958 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7959 if (JustBeingPushed(x, y))
7962 if (element == EL_QUICKSAND_FULL)
7964 if (IS_FREE(x, y + 1))
7966 InitMovingField(x, y, MV_DOWN);
7967 started_moving = TRUE;
7969 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7970 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7971 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7972 Store[x][y] = EL_ROCK;
7974 Store[x][y] = EL_ROCK;
7977 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7979 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7981 if (!MovDelay[x][y])
7983 MovDelay[x][y] = TILEY + 1;
7985 ResetGfxAnimation(x, y);
7986 ResetGfxAnimation(x, y + 1);
7991 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7992 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7999 Tile[x][y] = EL_QUICKSAND_EMPTY;
8000 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8001 Store[x][y + 1] = Store[x][y];
8004 PlayLevelSoundAction(x, y, ACTION_FILLING);
8006 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8008 if (!MovDelay[x][y])
8010 MovDelay[x][y] = TILEY + 1;
8012 ResetGfxAnimation(x, y);
8013 ResetGfxAnimation(x, y + 1);
8018 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
8019 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8026 Tile[x][y] = EL_QUICKSAND_EMPTY;
8027 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8028 Store[x][y + 1] = Store[x][y];
8031 PlayLevelSoundAction(x, y, ACTION_FILLING);
8034 else if (element == EL_QUICKSAND_FAST_FULL)
8036 if (IS_FREE(x, y + 1))
8038 InitMovingField(x, y, MV_DOWN);
8039 started_moving = TRUE;
8041 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
8042 #if USE_QUICKSAND_BD_ROCK_BUGFIX
8043 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
8044 Store[x][y] = EL_ROCK;
8046 Store[x][y] = EL_ROCK;
8049 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
8051 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8053 if (!MovDelay[x][y])
8055 MovDelay[x][y] = TILEY + 1;
8057 ResetGfxAnimation(x, y);
8058 ResetGfxAnimation(x, y + 1);
8063 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8064 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8071 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8072 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8073 Store[x][y + 1] = Store[x][y];
8076 PlayLevelSoundAction(x, y, ACTION_FILLING);
8078 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8080 if (!MovDelay[x][y])
8082 MovDelay[x][y] = TILEY + 1;
8084 ResetGfxAnimation(x, y);
8085 ResetGfxAnimation(x, y + 1);
8090 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8091 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8098 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8099 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8100 Store[x][y + 1] = Store[x][y];
8103 PlayLevelSoundAction(x, y, ACTION_FILLING);
8106 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8107 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8109 InitMovingField(x, y, MV_DOWN);
8110 started_moving = TRUE;
8112 Tile[x][y] = EL_QUICKSAND_FILLING;
8113 Store[x][y] = element;
8115 PlayLevelSoundAction(x, y, ACTION_FILLING);
8117 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8118 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8120 InitMovingField(x, y, MV_DOWN);
8121 started_moving = TRUE;
8123 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
8124 Store[x][y] = element;
8126 PlayLevelSoundAction(x, y, ACTION_FILLING);
8128 else if (element == EL_MAGIC_WALL_FULL)
8130 if (IS_FREE(x, y + 1))
8132 InitMovingField(x, y, MV_DOWN);
8133 started_moving = TRUE;
8135 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
8136 Store[x][y] = EL_CHANGED(Store[x][y]);
8138 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
8140 if (!MovDelay[x][y])
8141 MovDelay[x][y] = TILEY / 4 + 1;
8150 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
8151 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
8152 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8156 else if (element == EL_BD_MAGIC_WALL_FULL)
8158 if (IS_FREE(x, y + 1))
8160 InitMovingField(x, y, MV_DOWN);
8161 started_moving = TRUE;
8163 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8164 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8166 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8168 if (!MovDelay[x][y])
8169 MovDelay[x][y] = TILEY / 4 + 1;
8178 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8179 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8180 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8184 else if (element == EL_DC_MAGIC_WALL_FULL)
8186 if (IS_FREE(x, y + 1))
8188 InitMovingField(x, y, MV_DOWN);
8189 started_moving = TRUE;
8191 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8192 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8194 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8196 if (!MovDelay[x][y])
8197 MovDelay[x][y] = TILEY / 4 + 1;
8206 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8207 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8208 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8212 else if ((CAN_PASS_MAGIC_WALL(element) &&
8213 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8214 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8215 (CAN_PASS_DC_MAGIC_WALL(element) &&
8216 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8219 InitMovingField(x, y, MV_DOWN);
8220 started_moving = TRUE;
8223 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8224 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8225 EL_DC_MAGIC_WALL_FILLING);
8226 Store[x][y] = element;
8228 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8230 SplashAcid(x, y + 1);
8232 InitMovingField(x, y, MV_DOWN);
8233 started_moving = TRUE;
8235 Store[x][y] = EL_ACID;
8238 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8239 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8240 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8241 CAN_FALL(element) && WasJustFalling[x][y] &&
8242 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8244 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8245 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8246 (Tile[x][y + 1] == EL_BLOCKED)))
8248 /* this is needed for a special case not covered by calling "Impact()"
8249 from "ContinueMoving()": if an element moves to a tile directly below
8250 another element which was just falling on that tile (which was empty
8251 in the previous frame), the falling element above would just stop
8252 instead of smashing the element below (in previous version, the above
8253 element was just checked for "moving" instead of "falling", resulting
8254 in incorrect smashes caused by horizontal movement of the above
8255 element; also, the case of the player being the element to smash was
8256 simply not covered here... :-/ ) */
8258 CheckCollision[x][y] = 0;
8259 CheckImpact[x][y] = 0;
8263 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8265 if (MovDir[x][y] == MV_NONE)
8267 InitMovingField(x, y, MV_DOWN);
8268 started_moving = TRUE;
8271 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8273 if (WasJustFalling[x][y]) // prevent animation from being restarted
8274 MovDir[x][y] = MV_DOWN;
8276 InitMovingField(x, y, MV_DOWN);
8277 started_moving = TRUE;
8279 else if (element == EL_AMOEBA_DROP)
8281 Tile[x][y] = EL_AMOEBA_GROWING;
8282 Store[x][y] = EL_AMOEBA_WET;
8284 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8285 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8286 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8287 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8289 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8290 (IS_FREE(x - 1, y + 1) ||
8291 Tile[x - 1][y + 1] == EL_ACID));
8292 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8293 (IS_FREE(x + 1, y + 1) ||
8294 Tile[x + 1][y + 1] == EL_ACID));
8295 boolean can_fall_any = (can_fall_left || can_fall_right);
8296 boolean can_fall_both = (can_fall_left && can_fall_right);
8297 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8299 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8301 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8302 can_fall_right = FALSE;
8303 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8304 can_fall_left = FALSE;
8305 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8306 can_fall_right = FALSE;
8307 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8308 can_fall_left = FALSE;
8310 can_fall_any = (can_fall_left || can_fall_right);
8311 can_fall_both = FALSE;
8316 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8317 can_fall_right = FALSE; // slip down on left side
8319 can_fall_left = !(can_fall_right = RND(2));
8321 can_fall_both = FALSE;
8326 // if not determined otherwise, prefer left side for slipping down
8327 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8328 started_moving = TRUE;
8331 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8333 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8334 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8335 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8336 int belt_dir = game.belt_dir[belt_nr];
8338 if ((belt_dir == MV_LEFT && left_is_free) ||
8339 (belt_dir == MV_RIGHT && right_is_free))
8341 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8343 InitMovingField(x, y, belt_dir);
8344 started_moving = TRUE;
8346 Pushed[x][y] = TRUE;
8347 Pushed[nextx][y] = TRUE;
8349 GfxAction[x][y] = ACTION_DEFAULT;
8353 MovDir[x][y] = 0; // if element was moving, stop it
8358 // not "else if" because of elements that can fall and move (EL_SPRING)
8359 if (CAN_MOVE(element) && !started_moving)
8361 int move_pattern = element_info[element].move_pattern;
8364 Moving2Blocked(x, y, &newx, &newy);
8366 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8369 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8370 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8372 WasJustMoving[x][y] = 0;
8373 CheckCollision[x][y] = 0;
8375 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8377 if (Tile[x][y] != element) // element has changed
8381 if (!MovDelay[x][y]) // start new movement phase
8383 // all objects that can change their move direction after each step
8384 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8386 if (element != EL_YAMYAM &&
8387 element != EL_DARK_YAMYAM &&
8388 element != EL_PACMAN &&
8389 !(move_pattern & MV_ANY_DIRECTION) &&
8390 move_pattern != MV_TURNING_LEFT &&
8391 move_pattern != MV_TURNING_RIGHT &&
8392 move_pattern != MV_TURNING_LEFT_RIGHT &&
8393 move_pattern != MV_TURNING_RIGHT_LEFT &&
8394 move_pattern != MV_TURNING_RANDOM)
8398 if (MovDelay[x][y] && (element == EL_BUG ||
8399 element == EL_SPACESHIP ||
8400 element == EL_SP_SNIKSNAK ||
8401 element == EL_SP_ELECTRON ||
8402 element == EL_MOLE))
8403 TEST_DrawLevelField(x, y);
8407 if (MovDelay[x][y]) // wait some time before next movement
8411 if (element == EL_ROBOT ||
8412 element == EL_YAMYAM ||
8413 element == EL_DARK_YAMYAM)
8415 DrawLevelElementAnimationIfNeeded(x, y, element);
8416 PlayLevelSoundAction(x, y, ACTION_WAITING);
8418 else if (element == EL_SP_ELECTRON)
8419 DrawLevelElementAnimationIfNeeded(x, y, element);
8420 else if (element == EL_DRAGON)
8423 int dir = MovDir[x][y];
8424 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8425 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8426 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8427 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8428 dir == MV_UP ? IMG_FLAMES_1_UP :
8429 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8430 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8432 GfxAction[x][y] = ACTION_ATTACKING;
8434 if (IS_PLAYER(x, y))
8435 DrawPlayerField(x, y);
8437 TEST_DrawLevelField(x, y);
8439 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8441 for (i = 1; i <= 3; i++)
8443 int xx = x + i * dx;
8444 int yy = y + i * dy;
8445 int sx = SCREENX(xx);
8446 int sy = SCREENY(yy);
8447 int flame_graphic = graphic + (i - 1);
8449 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8454 int flamed = MovingOrBlocked2Element(xx, yy);
8456 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8459 RemoveMovingField(xx, yy);
8461 ChangeDelay[xx][yy] = 0;
8463 Tile[xx][yy] = EL_FLAMES;
8465 if (IN_SCR_FIELD(sx, sy))
8467 TEST_DrawLevelFieldCrumbled(xx, yy);
8468 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8473 if (Tile[xx][yy] == EL_FLAMES)
8474 Tile[xx][yy] = EL_EMPTY;
8475 TEST_DrawLevelField(xx, yy);
8480 if (MovDelay[x][y]) // element still has to wait some time
8482 PlayLevelSoundAction(x, y, ACTION_WAITING);
8488 // now make next step
8490 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8492 if (DONT_COLLIDE_WITH(element) &&
8493 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8494 !PLAYER_ENEMY_PROTECTED(newx, newy))
8496 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8501 else if (CAN_MOVE_INTO_ACID(element) &&
8502 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8503 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8504 (MovDir[x][y] == MV_DOWN ||
8505 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8507 SplashAcid(newx, newy);
8508 Store[x][y] = EL_ACID;
8510 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8512 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8513 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8514 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8515 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8518 TEST_DrawLevelField(x, y);
8520 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8521 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8522 DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0);
8524 game.friends_still_needed--;
8525 if (!game.friends_still_needed &&
8527 game.all_players_gone)
8532 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8534 if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING)
8535 TEST_DrawLevelField(newx, newy);
8537 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8539 else if (!IS_FREE(newx, newy))
8541 GfxAction[x][y] = ACTION_WAITING;
8543 if (IS_PLAYER(x, y))
8544 DrawPlayerField(x, y);
8546 TEST_DrawLevelField(x, y);
8551 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8553 if (IS_FOOD_PIG(Tile[newx][newy]))
8555 if (IS_MOVING(newx, newy))
8556 RemoveMovingField(newx, newy);
8559 Tile[newx][newy] = EL_EMPTY;
8560 TEST_DrawLevelField(newx, newy);
8563 PlayLevelSound(x, y, SND_PIG_DIGGING);
8565 else if (!IS_FREE(newx, newy))
8567 if (IS_PLAYER(x, y))
8568 DrawPlayerField(x, y);
8570 TEST_DrawLevelField(x, y);
8575 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8577 if (Store[x][y] != EL_EMPTY)
8579 boolean can_clone = FALSE;
8582 // check if element to clone is still there
8583 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8585 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8593 // cannot clone or target field not free anymore -- do not clone
8594 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8595 Store[x][y] = EL_EMPTY;
8598 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8600 if (IS_MV_DIAGONAL(MovDir[x][y]))
8602 int diagonal_move_dir = MovDir[x][y];
8603 int stored = Store[x][y];
8604 int change_delay = 8;
8607 // android is moving diagonally
8609 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8611 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8612 GfxElement[x][y] = EL_EMC_ANDROID;
8613 GfxAction[x][y] = ACTION_SHRINKING;
8614 GfxDir[x][y] = diagonal_move_dir;
8615 ChangeDelay[x][y] = change_delay;
8617 if (Store[x][y] == EL_EMPTY)
8618 Store[x][y] = GfxElementEmpty[x][y];
8620 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8623 DrawLevelGraphicAnimation(x, y, graphic);
8624 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8626 if (Tile[newx][newy] == EL_ACID)
8628 SplashAcid(newx, newy);
8633 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8635 Store[newx][newy] = EL_EMC_ANDROID;
8636 GfxElement[newx][newy] = EL_EMC_ANDROID;
8637 GfxAction[newx][newy] = ACTION_GROWING;
8638 GfxDir[newx][newy] = diagonal_move_dir;
8639 ChangeDelay[newx][newy] = change_delay;
8641 graphic = el_act_dir2img(GfxElement[newx][newy],
8642 GfxAction[newx][newy], GfxDir[newx][newy]);
8644 DrawLevelGraphicAnimation(newx, newy, graphic);
8645 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8651 Tile[newx][newy] = EL_EMPTY;
8652 TEST_DrawLevelField(newx, newy);
8654 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8657 else if (!IS_FREE(newx, newy))
8662 else if (IS_CUSTOM_ELEMENT(element) &&
8663 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8665 if (!DigFieldByCE(newx, newy, element))
8668 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8670 RunnerVisit[x][y] = FrameCounter;
8671 PlayerVisit[x][y] /= 8; // expire player visit path
8674 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8676 if (!IS_FREE(newx, newy))
8678 if (IS_PLAYER(x, y))
8679 DrawPlayerField(x, y);
8681 TEST_DrawLevelField(x, y);
8687 boolean wanna_flame = !RND(10);
8688 int dx = newx - x, dy = newy - y;
8689 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8690 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8691 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8692 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8693 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8694 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8697 IS_CLASSIC_ENEMY(element1) ||
8698 IS_CLASSIC_ENEMY(element2)) &&
8699 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8700 element1 != EL_FLAMES && element2 != EL_FLAMES)
8702 ResetGfxAnimation(x, y);
8703 GfxAction[x][y] = ACTION_ATTACKING;
8705 if (IS_PLAYER(x, y))
8706 DrawPlayerField(x, y);
8708 TEST_DrawLevelField(x, y);
8710 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8712 MovDelay[x][y] = 50;
8714 Tile[newx][newy] = EL_FLAMES;
8715 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8716 Tile[newx1][newy1] = EL_FLAMES;
8717 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8718 Tile[newx2][newy2] = EL_FLAMES;
8724 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8725 Tile[newx][newy] == EL_DIAMOND)
8727 if (IS_MOVING(newx, newy))
8728 RemoveMovingField(newx, newy);
8731 Tile[newx][newy] = EL_EMPTY;
8732 TEST_DrawLevelField(newx, newy);
8735 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8737 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8738 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8740 if (AmoebaNr[newx][newy])
8742 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8743 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8744 Tile[newx][newy] == EL_BD_AMOEBA)
8745 AmoebaCnt[AmoebaNr[newx][newy]]--;
8748 if (IS_MOVING(newx, newy))
8750 RemoveMovingField(newx, newy);
8754 Tile[newx][newy] = EL_EMPTY;
8755 TEST_DrawLevelField(newx, newy);
8758 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8760 else if ((element == EL_PACMAN || element == EL_MOLE)
8761 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8763 if (AmoebaNr[newx][newy])
8765 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8766 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8767 Tile[newx][newy] == EL_BD_AMOEBA)
8768 AmoebaCnt[AmoebaNr[newx][newy]]--;
8771 if (element == EL_MOLE)
8773 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8774 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8776 ResetGfxAnimation(x, y);
8777 GfxAction[x][y] = ACTION_DIGGING;
8778 TEST_DrawLevelField(x, y);
8780 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8782 return; // wait for shrinking amoeba
8784 else // element == EL_PACMAN
8786 Tile[newx][newy] = EL_EMPTY;
8787 TEST_DrawLevelField(newx, newy);
8788 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8791 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8792 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8793 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8795 // wait for shrinking amoeba to completely disappear
8798 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8800 // object was running against a wall
8804 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8805 DrawLevelElementAnimation(x, y, element);
8807 if (DONT_TOUCH(element))
8808 TestIfBadThingTouchesPlayer(x, y);
8813 InitMovingField(x, y, MovDir[x][y]);
8815 PlayLevelSoundAction(x, y, ACTION_MOVING);
8819 ContinueMoving(x, y);
8822 void ContinueMoving(int x, int y)
8824 int element = Tile[x][y];
8825 struct ElementInfo *ei = &element_info[element];
8826 int direction = MovDir[x][y];
8827 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8828 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8829 int newx = x + dx, newy = y + dy;
8830 int stored = Store[x][y];
8831 int stored_new = Store[newx][newy];
8832 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8833 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8834 boolean last_line = (newy == lev_fieldy - 1);
8835 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8837 if (pushed_by_player) // special case: moving object pushed by player
8839 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos));
8841 else if (use_step_delay) // special case: moving object has step delay
8843 if (!MovDelay[x][y])
8844 MovPos[x][y] += getElementMoveStepsize(x, y);
8849 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8853 TEST_DrawLevelField(x, y);
8855 return; // element is still waiting
8858 else // normal case: generically moving object
8860 MovPos[x][y] += getElementMoveStepsize(x, y);
8863 if (ABS(MovPos[x][y]) < TILEX)
8865 TEST_DrawLevelField(x, y);
8867 return; // element is still moving
8870 // element reached destination field
8872 Tile[x][y] = EL_EMPTY;
8873 Tile[newx][newy] = element;
8874 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8876 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8878 element = Tile[newx][newy] = EL_ACID;
8880 else if (element == EL_MOLE)
8882 Tile[x][y] = EL_SAND;
8884 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8886 else if (element == EL_QUICKSAND_FILLING)
8888 element = Tile[newx][newy] = get_next_element(element);
8889 Store[newx][newy] = Store[x][y];
8891 else if (element == EL_QUICKSAND_EMPTYING)
8893 Tile[x][y] = get_next_element(element);
8894 element = Tile[newx][newy] = Store[x][y];
8896 else if (element == EL_QUICKSAND_FAST_FILLING)
8898 element = Tile[newx][newy] = get_next_element(element);
8899 Store[newx][newy] = Store[x][y];
8901 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8903 Tile[x][y] = get_next_element(element);
8904 element = Tile[newx][newy] = Store[x][y];
8906 else if (element == EL_MAGIC_WALL_FILLING)
8908 element = Tile[newx][newy] = get_next_element(element);
8909 if (!game.magic_wall_active)
8910 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8911 Store[newx][newy] = Store[x][y];
8913 else if (element == EL_MAGIC_WALL_EMPTYING)
8915 Tile[x][y] = get_next_element(element);
8916 if (!game.magic_wall_active)
8917 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8918 element = Tile[newx][newy] = Store[x][y];
8920 InitField(newx, newy, FALSE);
8922 else if (element == EL_BD_MAGIC_WALL_FILLING)
8924 element = Tile[newx][newy] = get_next_element(element);
8925 if (!game.magic_wall_active)
8926 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8927 Store[newx][newy] = Store[x][y];
8929 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8931 Tile[x][y] = get_next_element(element);
8932 if (!game.magic_wall_active)
8933 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8934 element = Tile[newx][newy] = Store[x][y];
8936 InitField(newx, newy, FALSE);
8938 else if (element == EL_DC_MAGIC_WALL_FILLING)
8940 element = Tile[newx][newy] = get_next_element(element);
8941 if (!game.magic_wall_active)
8942 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8943 Store[newx][newy] = Store[x][y];
8945 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8947 Tile[x][y] = get_next_element(element);
8948 if (!game.magic_wall_active)
8949 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8950 element = Tile[newx][newy] = Store[x][y];
8952 InitField(newx, newy, FALSE);
8954 else if (element == EL_AMOEBA_DROPPING)
8956 Tile[x][y] = get_next_element(element);
8957 element = Tile[newx][newy] = Store[x][y];
8959 else if (element == EL_SOKOBAN_OBJECT)
8962 Tile[x][y] = Back[x][y];
8964 if (Back[newx][newy])
8965 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8967 Back[x][y] = Back[newx][newy] = 0;
8970 Store[x][y] = EL_EMPTY;
8975 MovDelay[newx][newy] = 0;
8977 if (CAN_CHANGE_OR_HAS_ACTION(element))
8979 // copy element change control values to new field
8980 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8981 ChangePage[newx][newy] = ChangePage[x][y];
8982 ChangeCount[newx][newy] = ChangeCount[x][y];
8983 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8986 CustomValue[newx][newy] = CustomValue[x][y];
8988 ChangeDelay[x][y] = 0;
8989 ChangePage[x][y] = -1;
8990 ChangeCount[x][y] = 0;
8991 ChangeEvent[x][y] = -1;
8993 CustomValue[x][y] = 0;
8995 // copy animation control values to new field
8996 GfxFrame[newx][newy] = GfxFrame[x][y];
8997 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8998 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8999 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
9001 Pushed[x][y] = Pushed[newx][newy] = FALSE;
9003 // some elements can leave other elements behind after moving
9004 if (ei->move_leave_element != EL_EMPTY &&
9005 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
9006 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
9008 int move_leave_element = ei->move_leave_element;
9010 // this makes it possible to leave the removed element again
9011 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
9012 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
9014 Tile[x][y] = move_leave_element;
9016 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
9017 MovDir[x][y] = direction;
9019 InitField(x, y, FALSE);
9021 if (GFX_CRUMBLED(Tile[x][y]))
9022 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
9024 if (IS_PLAYER_ELEMENT(move_leave_element))
9025 RelocatePlayer(x, y, move_leave_element);
9028 // do this after checking for left-behind element
9029 ResetGfxAnimation(x, y); // reset animation values for old field
9031 if (!CAN_MOVE(element) ||
9032 (CAN_FALL(element) && direction == MV_DOWN &&
9033 (element == EL_SPRING ||
9034 element_info[element].move_pattern == MV_WHEN_PUSHED ||
9035 element_info[element].move_pattern == MV_WHEN_DROPPED)))
9036 GfxDir[x][y] = MovDir[newx][newy] = 0;
9038 TEST_DrawLevelField(x, y);
9039 TEST_DrawLevelField(newx, newy);
9041 Stop[newx][newy] = TRUE; // ignore this element until the next frame
9043 // prevent pushed element from moving on in pushed direction
9044 if (pushed_by_player && CAN_MOVE(element) &&
9045 element_info[element].move_pattern & MV_ANY_DIRECTION &&
9046 !(element_info[element].move_pattern & direction))
9047 TurnRound(newx, newy);
9049 // prevent elements on conveyor belt from moving on in last direction
9050 if (pushed_by_conveyor && CAN_FALL(element) &&
9051 direction & MV_HORIZONTAL)
9052 MovDir[newx][newy] = 0;
9054 if (!pushed_by_player)
9056 int nextx = newx + dx, nexty = newy + dy;
9057 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
9059 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
9061 if (CAN_FALL(element) && direction == MV_DOWN)
9062 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
9064 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
9065 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
9067 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
9068 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
9071 if (DONT_TOUCH(element)) // object may be nasty to player or others
9073 TestIfBadThingTouchesPlayer(newx, newy);
9074 TestIfBadThingTouchesFriend(newx, newy);
9076 if (!IS_CUSTOM_ELEMENT(element))
9077 TestIfBadThingTouchesOtherBadThing(newx, newy);
9079 else if (element == EL_PENGUIN)
9080 TestIfFriendTouchesBadThing(newx, newy);
9082 if (DONT_GET_HIT_BY(element))
9084 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
9087 // give the player one last chance (one more frame) to move away
9088 if (CAN_FALL(element) && direction == MV_DOWN &&
9089 (last_line || (!IS_FREE(x, newy + 1) &&
9090 (!IS_PLAYER(x, newy + 1) ||
9091 game.engine_version < VERSION_IDENT(3,1,1,0)))))
9094 if (pushed_by_player && !game.use_change_when_pushing_bug)
9096 int push_side = MV_DIR_OPPOSITE(direction);
9097 struct PlayerInfo *player = PLAYERINFO(x, y);
9099 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
9100 player->index_bit, push_side);
9101 CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X,
9102 player->index_bit, push_side);
9105 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
9106 MovDelay[newx][newy] = 1;
9108 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
9110 TestIfElementTouchesCustomElement(x, y); // empty or new element
9111 TestIfElementHitsCustomElement(newx, newy, direction);
9112 TestIfPlayerTouchesCustomElement(newx, newy);
9113 TestIfElementTouchesCustomElement(newx, newy);
9115 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
9116 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
9117 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
9118 MV_DIR_OPPOSITE(direction));
9121 int AmoebaNeighbourNr(int ax, int ay)
9124 int element = Tile[ax][ay];
9126 struct XY *xy = xy_topdown;
9128 for (i = 0; i < NUM_DIRECTIONS; i++)
9130 int x = ax + xy[i].x;
9131 int y = ay + xy[i].y;
9133 if (!IN_LEV_FIELD(x, y))
9136 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
9137 group_nr = AmoebaNr[x][y];
9143 static void AmoebaMerge(int ax, int ay)
9145 int i, x, y, xx, yy;
9146 int new_group_nr = AmoebaNr[ax][ay];
9147 struct XY *xy = xy_topdown;
9149 if (new_group_nr == 0)
9152 for (i = 0; i < NUM_DIRECTIONS; i++)
9157 if (!IN_LEV_FIELD(x, y))
9160 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9161 Tile[x][y] == EL_BD_AMOEBA ||
9162 Tile[x][y] == EL_AMOEBA_DEAD) &&
9163 AmoebaNr[x][y] != new_group_nr)
9165 int old_group_nr = AmoebaNr[x][y];
9167 if (old_group_nr == 0)
9170 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9171 AmoebaCnt[old_group_nr] = 0;
9172 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9173 AmoebaCnt2[old_group_nr] = 0;
9175 SCAN_PLAYFIELD(xx, yy)
9177 if (AmoebaNr[xx][yy] == old_group_nr)
9178 AmoebaNr[xx][yy] = new_group_nr;
9184 void AmoebaToDiamond(int ax, int ay)
9188 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9190 int group_nr = AmoebaNr[ax][ay];
9195 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9196 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9202 SCAN_PLAYFIELD(x, y)
9204 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9207 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9211 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9212 SND_AMOEBA_TURNING_TO_GEM :
9213 SND_AMOEBA_TURNING_TO_ROCK));
9218 struct XY *xy = xy_topdown;
9220 for (i = 0; i < NUM_DIRECTIONS; i++)
9225 if (!IN_LEV_FIELD(x, y))
9228 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9230 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9231 SND_AMOEBA_TURNING_TO_GEM :
9232 SND_AMOEBA_TURNING_TO_ROCK));
9239 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9242 int group_nr = AmoebaNr[ax][ay];
9243 boolean done = FALSE;
9248 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9249 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9255 SCAN_PLAYFIELD(x, y)
9257 if (AmoebaNr[x][y] == group_nr &&
9258 (Tile[x][y] == EL_AMOEBA_DEAD ||
9259 Tile[x][y] == EL_BD_AMOEBA ||
9260 Tile[x][y] == EL_AMOEBA_GROWING))
9263 Tile[x][y] = new_element;
9264 InitField(x, y, FALSE);
9265 TEST_DrawLevelField(x, y);
9271 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9272 SND_BD_AMOEBA_TURNING_TO_ROCK :
9273 SND_BD_AMOEBA_TURNING_TO_GEM));
9276 static void AmoebaGrowing(int x, int y)
9278 static DelayCounter sound_delay = { 0 };
9280 if (!MovDelay[x][y]) // start new growing cycle
9284 if (DelayReached(&sound_delay))
9286 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9287 sound_delay.value = 30;
9291 if (MovDelay[x][y]) // wait some time before growing bigger
9294 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9296 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9297 6 - MovDelay[x][y]);
9299 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9302 if (!MovDelay[x][y])
9304 Tile[x][y] = Store[x][y];
9306 TEST_DrawLevelField(x, y);
9311 static void AmoebaShrinking(int x, int y)
9313 static DelayCounter sound_delay = { 0 };
9315 if (!MovDelay[x][y]) // start new shrinking cycle
9319 if (DelayReached(&sound_delay))
9320 sound_delay.value = 30;
9323 if (MovDelay[x][y]) // wait some time before shrinking
9326 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9328 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9329 6 - MovDelay[x][y]);
9331 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9334 if (!MovDelay[x][y])
9336 Tile[x][y] = EL_EMPTY;
9337 TEST_DrawLevelField(x, y);
9339 // don't let mole enter this field in this cycle;
9340 // (give priority to objects falling to this field from above)
9346 static void AmoebaReproduce(int ax, int ay)
9349 int element = Tile[ax][ay];
9350 int graphic = el2img(element);
9351 int newax = ax, neway = ay;
9352 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9353 struct XY *xy = xy_topdown;
9355 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9357 Tile[ax][ay] = EL_AMOEBA_DEAD;
9358 TEST_DrawLevelField(ax, ay);
9362 if (IS_ANIMATED(graphic))
9363 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9365 if (!MovDelay[ax][ay]) // start making new amoeba field
9366 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9368 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9371 if (MovDelay[ax][ay])
9375 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9378 int x = ax + xy[start].x;
9379 int y = ay + xy[start].y;
9381 if (!IN_LEV_FIELD(x, y))
9384 if (IS_FREE(x, y) ||
9385 CAN_GROW_INTO(Tile[x][y]) ||
9386 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9387 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9393 if (newax == ax && neway == ay)
9396 else // normal or "filled" (BD style) amoeba
9399 boolean waiting_for_player = FALSE;
9401 for (i = 0; i < NUM_DIRECTIONS; i++)
9403 int j = (start + i) % 4;
9404 int x = ax + xy[j].x;
9405 int y = ay + xy[j].y;
9407 if (!IN_LEV_FIELD(x, y))
9410 if (IS_FREE(x, y) ||
9411 CAN_GROW_INTO(Tile[x][y]) ||
9412 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9413 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9419 else if (IS_PLAYER(x, y))
9420 waiting_for_player = TRUE;
9423 if (newax == ax && neway == ay) // amoeba cannot grow
9425 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9427 Tile[ax][ay] = EL_AMOEBA_DEAD;
9428 TEST_DrawLevelField(ax, ay);
9429 AmoebaCnt[AmoebaNr[ax][ay]]--;
9431 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9433 if (element == EL_AMOEBA_FULL)
9434 AmoebaToDiamond(ax, ay);
9435 else if (element == EL_BD_AMOEBA)
9436 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9441 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9443 // amoeba gets larger by growing in some direction
9445 int new_group_nr = AmoebaNr[ax][ay];
9448 if (new_group_nr == 0)
9450 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9452 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9458 AmoebaNr[newax][neway] = new_group_nr;
9459 AmoebaCnt[new_group_nr]++;
9460 AmoebaCnt2[new_group_nr]++;
9462 // if amoeba touches other amoeba(s) after growing, unify them
9463 AmoebaMerge(newax, neway);
9465 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9467 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9473 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9474 (neway == lev_fieldy - 1 && newax != ax))
9476 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9477 Store[newax][neway] = element;
9479 else if (neway == ay || element == EL_EMC_DRIPPER)
9481 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9483 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9487 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9488 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9489 Store[ax][ay] = EL_AMOEBA_DROP;
9490 ContinueMoving(ax, ay);
9494 TEST_DrawLevelField(newax, neway);
9497 static void Life(int ax, int ay)
9501 int element = Tile[ax][ay];
9502 int graphic = el2img(element);
9503 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9505 boolean changed = FALSE;
9507 if (IS_ANIMATED(graphic))
9508 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9513 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9514 MovDelay[ax][ay] = life_time;
9516 if (MovDelay[ax][ay]) // wait some time before next cycle
9519 if (MovDelay[ax][ay])
9523 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9525 int xx = ax + x1, yy = ay + y1;
9526 int old_element = Tile[xx][yy];
9527 int num_neighbours = 0;
9529 if (!IN_LEV_FIELD(xx, yy))
9532 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9534 int x = xx + x2, y = yy + y2;
9536 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9539 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9540 boolean is_neighbour = FALSE;
9542 if (level.use_life_bugs)
9544 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9545 (IS_FREE(x, y) && Stop[x][y]));
9548 (Last[x][y] == element || is_player_cell);
9554 boolean is_free = FALSE;
9556 if (level.use_life_bugs)
9557 is_free = (IS_FREE(xx, yy));
9559 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9561 if (xx == ax && yy == ay) // field in the middle
9563 if (num_neighbours < life_parameter[0] ||
9564 num_neighbours > life_parameter[1])
9566 Tile[xx][yy] = EL_EMPTY;
9567 if (Tile[xx][yy] != old_element)
9568 TEST_DrawLevelField(xx, yy);
9569 Stop[xx][yy] = TRUE;
9573 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9574 { // free border field
9575 if (num_neighbours >= life_parameter[2] &&
9576 num_neighbours <= life_parameter[3])
9578 Tile[xx][yy] = element;
9579 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9580 if (Tile[xx][yy] != old_element)
9581 TEST_DrawLevelField(xx, yy);
9582 Stop[xx][yy] = TRUE;
9589 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9590 SND_GAME_OF_LIFE_GROWING);
9593 static void InitRobotWheel(int x, int y)
9595 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9598 static void RunRobotWheel(int x, int y)
9600 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9603 static void StopRobotWheel(int x, int y)
9605 if (game.robot_wheel_x == x &&
9606 game.robot_wheel_y == y)
9608 game.robot_wheel_x = -1;
9609 game.robot_wheel_y = -1;
9610 game.robot_wheel_active = FALSE;
9614 static void InitTimegateWheel(int x, int y)
9616 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9619 static void RunTimegateWheel(int x, int y)
9621 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9624 static void InitMagicBallDelay(int x, int y)
9626 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9629 static void ActivateMagicBall(int bx, int by)
9633 if (level.ball_random)
9635 int pos_border = RND(8); // select one of the eight border elements
9636 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9637 int xx = pos_content % 3;
9638 int yy = pos_content / 3;
9643 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9644 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9648 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9650 int xx = x - bx + 1;
9651 int yy = y - by + 1;
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 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9661 static void CheckExit(int x, int y)
9663 if (game.gems_still_needed > 0 ||
9664 game.sokoban_fields_still_needed > 0 ||
9665 game.sokoban_objects_still_needed > 0 ||
9666 game.lights_still_needed > 0)
9668 int element = Tile[x][y];
9669 int graphic = el2img(element);
9671 if (IS_ANIMATED(graphic))
9672 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9677 // do not re-open exit door closed after last player
9678 if (game.all_players_gone)
9681 Tile[x][y] = EL_EXIT_OPENING;
9683 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9686 static void CheckExitEM(int x, int y)
9688 if (game.gems_still_needed > 0 ||
9689 game.sokoban_fields_still_needed > 0 ||
9690 game.sokoban_objects_still_needed > 0 ||
9691 game.lights_still_needed > 0)
9693 int element = Tile[x][y];
9694 int graphic = el2img(element);
9696 if (IS_ANIMATED(graphic))
9697 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9702 // do not re-open exit door closed after last player
9703 if (game.all_players_gone)
9706 Tile[x][y] = EL_EM_EXIT_OPENING;
9708 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9711 static void CheckExitSteel(int x, int y)
9713 if (game.gems_still_needed > 0 ||
9714 game.sokoban_fields_still_needed > 0 ||
9715 game.sokoban_objects_still_needed > 0 ||
9716 game.lights_still_needed > 0)
9718 int element = Tile[x][y];
9719 int graphic = el2img(element);
9721 if (IS_ANIMATED(graphic))
9722 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9727 // do not re-open exit door closed after last player
9728 if (game.all_players_gone)
9731 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9733 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9736 static void CheckExitSteelEM(int x, int y)
9738 if (game.gems_still_needed > 0 ||
9739 game.sokoban_fields_still_needed > 0 ||
9740 game.sokoban_objects_still_needed > 0 ||
9741 game.lights_still_needed > 0)
9743 int element = Tile[x][y];
9744 int graphic = el2img(element);
9746 if (IS_ANIMATED(graphic))
9747 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9752 // do not re-open exit door closed after last player
9753 if (game.all_players_gone)
9756 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9758 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9761 static void CheckExitSP(int x, int y)
9763 if (game.gems_still_needed > 0)
9765 int element = Tile[x][y];
9766 int graphic = el2img(element);
9768 if (IS_ANIMATED(graphic))
9769 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9774 // do not re-open exit door closed after last player
9775 if (game.all_players_gone)
9778 Tile[x][y] = EL_SP_EXIT_OPENING;
9780 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9783 static void CloseAllOpenTimegates(void)
9787 SCAN_PLAYFIELD(x, y)
9789 int element = Tile[x][y];
9791 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9793 Tile[x][y] = EL_TIMEGATE_CLOSING;
9795 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9800 static void DrawTwinkleOnField(int x, int y)
9802 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9805 if (Tile[x][y] == EL_BD_DIAMOND)
9808 if (MovDelay[x][y] == 0) // next animation frame
9809 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9811 if (MovDelay[x][y] != 0) // wait some time before next frame
9815 DrawLevelElementAnimation(x, y, Tile[x][y]);
9817 if (MovDelay[x][y] != 0)
9819 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9820 10 - MovDelay[x][y]);
9822 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9827 static void WallGrowing(int x, int y)
9831 if (!MovDelay[x][y]) // next animation frame
9832 MovDelay[x][y] = 3 * delay;
9834 if (MovDelay[x][y]) // wait some time before next frame
9838 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9840 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9841 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9843 DrawLevelGraphic(x, y, graphic, frame);
9846 if (!MovDelay[x][y])
9848 if (MovDir[x][y] == MV_LEFT)
9850 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9851 TEST_DrawLevelField(x - 1, y);
9853 else if (MovDir[x][y] == MV_RIGHT)
9855 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9856 TEST_DrawLevelField(x + 1, y);
9858 else if (MovDir[x][y] == MV_UP)
9860 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9861 TEST_DrawLevelField(x, y - 1);
9865 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9866 TEST_DrawLevelField(x, y + 1);
9869 Tile[x][y] = Store[x][y];
9871 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9872 TEST_DrawLevelField(x, y);
9877 static void CheckWallGrowing(int ax, int ay)
9879 int element = Tile[ax][ay];
9880 int graphic = el2img(element);
9881 boolean free_top = FALSE;
9882 boolean free_bottom = FALSE;
9883 boolean free_left = FALSE;
9884 boolean free_right = FALSE;
9885 boolean stop_top = FALSE;
9886 boolean stop_bottom = FALSE;
9887 boolean stop_left = FALSE;
9888 boolean stop_right = FALSE;
9889 boolean new_wall = FALSE;
9891 boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9892 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9893 element == EL_EXPANDABLE_STEELWALL_ANY);
9895 boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9896 element == EL_EXPANDABLE_WALL_ANY ||
9897 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9898 element == EL_EXPANDABLE_STEELWALL_ANY);
9900 boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9901 element == EL_EXPANDABLE_WALL_ANY ||
9902 element == EL_EXPANDABLE_WALL ||
9903 element == EL_BD_EXPANDABLE_WALL ||
9904 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9905 element == EL_EXPANDABLE_STEELWALL_ANY);
9907 boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9908 element == EL_EXPANDABLE_STEELWALL_VERTICAL);
9910 boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9911 element == EL_EXPANDABLE_WALL ||
9912 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL);
9914 int wall_growing = (is_steelwall ?
9915 EL_EXPANDABLE_STEELWALL_GROWING :
9916 EL_EXPANDABLE_WALL_GROWING);
9918 int gfx_wall_growing_up = (is_steelwall ?
9919 IMG_EXPANDABLE_STEELWALL_GROWING_UP :
9920 IMG_EXPANDABLE_WALL_GROWING_UP);
9921 int gfx_wall_growing_down = (is_steelwall ?
9922 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN :
9923 IMG_EXPANDABLE_WALL_GROWING_DOWN);
9924 int gfx_wall_growing_left = (is_steelwall ?
9925 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT :
9926 IMG_EXPANDABLE_WALL_GROWING_LEFT);
9927 int gfx_wall_growing_right = (is_steelwall ?
9928 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT :
9929 IMG_EXPANDABLE_WALL_GROWING_RIGHT);
9931 if (IS_ANIMATED(graphic))
9932 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9934 if (!MovDelay[ax][ay]) // start building new wall
9935 MovDelay[ax][ay] = 6;
9937 if (MovDelay[ax][ay]) // wait some time before building new wall
9940 if (MovDelay[ax][ay])
9944 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9946 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9948 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9950 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9957 Tile[ax][ay - 1] = wall_growing;
9958 Store[ax][ay - 1] = element;
9959 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9961 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9962 DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0);
9969 Tile[ax][ay + 1] = wall_growing;
9970 Store[ax][ay + 1] = element;
9971 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9973 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9974 DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0);
9980 if (grow_horizontal)
9984 Tile[ax - 1][ay] = wall_growing;
9985 Store[ax - 1][ay] = element;
9986 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9988 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9989 DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0);
9996 Tile[ax + 1][ay] = wall_growing;
9997 Store[ax + 1][ay] = element;
9998 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
10000 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
10001 DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0);
10007 if (element == EL_EXPANDABLE_WALL && (free_left || free_right))
10008 TEST_DrawLevelField(ax, ay);
10010 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
10012 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
10013 stop_bottom = TRUE;
10014 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
10016 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
10019 if (((stop_top && stop_bottom) || stop_horizontal) &&
10020 ((stop_left && stop_right) || stop_vertical))
10021 Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL);
10024 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
10027 static void CheckForDragon(int x, int y)
10030 boolean dragon_found = FALSE;
10031 struct XY *xy = xy_topdown;
10033 for (i = 0; i < NUM_DIRECTIONS; i++)
10035 for (j = 0; j < 4; j++)
10037 int xx = x + j * xy[i].x;
10038 int yy = y + j * xy[i].y;
10040 if (IN_LEV_FIELD(xx, yy) &&
10041 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
10043 if (Tile[xx][yy] == EL_DRAGON)
10044 dragon_found = TRUE;
10053 for (i = 0; i < NUM_DIRECTIONS; i++)
10055 for (j = 0; j < 3; j++)
10057 int xx = x + j * xy[i].x;
10058 int yy = y + j * xy[i].y;
10060 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
10062 Tile[xx][yy] = EL_EMPTY;
10063 TEST_DrawLevelField(xx, yy);
10072 static void InitBuggyBase(int x, int y)
10074 int element = Tile[x][y];
10075 int activating_delay = FRAMES_PER_SECOND / 4;
10077 ChangeDelay[x][y] =
10078 (element == EL_SP_BUGGY_BASE ?
10079 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10080 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10082 element == EL_SP_BUGGY_BASE_ACTIVE ?
10083 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10086 static void WarnBuggyBase(int x, int y)
10089 struct XY *xy = xy_topdown;
10091 for (i = 0; i < NUM_DIRECTIONS; i++)
10093 int xx = x + xy[i].x;
10094 int yy = y + xy[i].y;
10096 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10098 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10105 static void InitTrap(int x, int y)
10107 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10110 static void ActivateTrap(int x, int y)
10112 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10115 static void ChangeActiveTrap(int x, int y)
10117 int graphic = IMG_TRAP_ACTIVE;
10119 // if new animation frame was drawn, correct crumbled sand border
10120 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10121 TEST_DrawLevelFieldCrumbled(x, y);
10124 static int getSpecialActionElement(int element, int number, int base_element)
10126 return (element != EL_EMPTY ? element :
10127 number != -1 ? base_element + number - 1 :
10131 static int getModifiedActionNumber(int value_old, int operator, int operand,
10132 int value_min, int value_max)
10134 int value_new = (operator == CA_MODE_SET ? operand :
10135 operator == CA_MODE_ADD ? value_old + operand :
10136 operator == CA_MODE_SUBTRACT ? value_old - operand :
10137 operator == CA_MODE_MULTIPLY ? value_old * operand :
10138 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10139 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10142 return (value_new < value_min ? value_min :
10143 value_new > value_max ? value_max :
10147 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10149 struct ElementInfo *ei = &element_info[element];
10150 struct ElementChangeInfo *change = &ei->change_page[page];
10151 int target_element = change->target_element;
10152 int action_type = change->action_type;
10153 int action_mode = change->action_mode;
10154 int action_arg = change->action_arg;
10155 int action_element = change->action_element;
10158 if (!change->has_action)
10161 // ---------- determine action paramater values -----------------------------
10163 int level_time_value =
10164 (level.time > 0 ? TimeLeft :
10167 int action_arg_element_raw =
10168 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10169 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10170 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10171 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10172 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10173 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10174 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10176 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10178 int action_arg_direction =
10179 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10180 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10181 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10182 change->actual_trigger_side :
10183 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10184 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10187 int action_arg_number_min =
10188 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10191 int action_arg_number_max =
10192 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10193 action_type == CA_SET_LEVEL_GEMS ? 999 :
10194 action_type == CA_SET_LEVEL_TIME ? 9999 :
10195 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10196 action_type == CA_SET_CE_VALUE ? 9999 :
10197 action_type == CA_SET_CE_SCORE ? 9999 :
10200 int action_arg_number_reset =
10201 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10202 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10203 action_type == CA_SET_LEVEL_TIME ? level.time :
10204 action_type == CA_SET_LEVEL_SCORE ? 0 :
10205 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10206 action_type == CA_SET_CE_SCORE ? 0 :
10209 int action_arg_number =
10210 (action_arg <= CA_ARG_MAX ? action_arg :
10211 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10212 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10213 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10214 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10215 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10216 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10217 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10218 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10219 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10220 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10221 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10222 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10223 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10224 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10225 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10226 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10227 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10228 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10229 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10230 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10231 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10234 int action_arg_number_old =
10235 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10236 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10237 action_type == CA_SET_LEVEL_SCORE ? game.score :
10238 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10239 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10242 int action_arg_number_new =
10243 getModifiedActionNumber(action_arg_number_old,
10244 action_mode, action_arg_number,
10245 action_arg_number_min, action_arg_number_max);
10247 int trigger_player_bits =
10248 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10249 change->actual_trigger_player_bits : change->trigger_player);
10251 int action_arg_player_bits =
10252 (action_arg >= CA_ARG_PLAYER_1 &&
10253 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10254 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10255 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10258 // ---------- execute action -----------------------------------------------
10260 switch (action_type)
10267 // ---------- level actions ----------------------------------------------
10269 case CA_RESTART_LEVEL:
10271 game.restart_level = TRUE;
10276 case CA_SHOW_ENVELOPE:
10278 int element = getSpecialActionElement(action_arg_element,
10279 action_arg_number, EL_ENVELOPE_1);
10281 if (IS_ENVELOPE(element))
10282 local_player->show_envelope = element;
10287 case CA_SET_LEVEL_TIME:
10289 if (level.time > 0) // only modify limited time value
10291 TimeLeft = action_arg_number_new;
10293 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10295 DisplayGameControlValues();
10297 if (!TimeLeft && game.time_limit)
10298 for (i = 0; i < MAX_PLAYERS; i++)
10299 KillPlayer(&stored_player[i]);
10305 case CA_SET_LEVEL_SCORE:
10307 game.score = action_arg_number_new;
10309 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10311 DisplayGameControlValues();
10316 case CA_SET_LEVEL_GEMS:
10318 game.gems_still_needed = action_arg_number_new;
10320 game.snapshot.collected_item = TRUE;
10322 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10324 DisplayGameControlValues();
10329 case CA_SET_LEVEL_WIND:
10331 game.wind_direction = action_arg_direction;
10336 case CA_SET_LEVEL_RANDOM_SEED:
10338 // ensure that setting a new random seed while playing is predictable
10339 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10344 // ---------- player actions ---------------------------------------------
10346 case CA_MOVE_PLAYER:
10347 case CA_MOVE_PLAYER_NEW:
10349 // automatically move to the next field in specified direction
10350 for (i = 0; i < MAX_PLAYERS; i++)
10351 if (trigger_player_bits & (1 << i))
10352 if (action_type == CA_MOVE_PLAYER ||
10353 stored_player[i].MovPos == 0)
10354 stored_player[i].programmed_action = action_arg_direction;
10359 case CA_EXIT_PLAYER:
10361 for (i = 0; i < MAX_PLAYERS; i++)
10362 if (action_arg_player_bits & (1 << i))
10363 ExitPlayer(&stored_player[i]);
10365 if (game.players_still_needed == 0)
10371 case CA_KILL_PLAYER:
10373 for (i = 0; i < MAX_PLAYERS; i++)
10374 if (action_arg_player_bits & (1 << i))
10375 KillPlayer(&stored_player[i]);
10380 case CA_SET_PLAYER_KEYS:
10382 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10383 int element = getSpecialActionElement(action_arg_element,
10384 action_arg_number, EL_KEY_1);
10386 if (IS_KEY(element))
10388 for (i = 0; i < MAX_PLAYERS; i++)
10390 if (trigger_player_bits & (1 << i))
10392 stored_player[i].key[KEY_NR(element)] = key_state;
10394 DrawGameDoorValues();
10402 case CA_SET_PLAYER_SPEED:
10404 for (i = 0; i < MAX_PLAYERS; i++)
10406 if (trigger_player_bits & (1 << i))
10408 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10410 if (action_arg == CA_ARG_SPEED_FASTER &&
10411 stored_player[i].cannot_move)
10413 action_arg_number = STEPSIZE_VERY_SLOW;
10415 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10416 action_arg == CA_ARG_SPEED_FASTER)
10418 action_arg_number = 2;
10419 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10422 else if (action_arg == CA_ARG_NUMBER_RESET)
10424 action_arg_number = level.initial_player_stepsize[i];
10428 getModifiedActionNumber(move_stepsize,
10431 action_arg_number_min,
10432 action_arg_number_max);
10434 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10441 case CA_SET_PLAYER_SHIELD:
10443 for (i = 0; i < MAX_PLAYERS; i++)
10445 if (trigger_player_bits & (1 << i))
10447 if (action_arg == CA_ARG_SHIELD_OFF)
10449 stored_player[i].shield_normal_time_left = 0;
10450 stored_player[i].shield_deadly_time_left = 0;
10452 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10454 stored_player[i].shield_normal_time_left = 999999;
10456 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10458 stored_player[i].shield_normal_time_left = 999999;
10459 stored_player[i].shield_deadly_time_left = 999999;
10467 case CA_SET_PLAYER_GRAVITY:
10469 for (i = 0; i < MAX_PLAYERS; i++)
10471 if (trigger_player_bits & (1 << i))
10473 stored_player[i].gravity =
10474 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10475 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10476 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10477 stored_player[i].gravity);
10484 case CA_SET_PLAYER_ARTWORK:
10486 for (i = 0; i < MAX_PLAYERS; i++)
10488 if (trigger_player_bits & (1 << i))
10490 int artwork_element = action_arg_element;
10492 if (action_arg == CA_ARG_ELEMENT_RESET)
10494 (level.use_artwork_element[i] ? level.artwork_element[i] :
10495 stored_player[i].element_nr);
10497 if (stored_player[i].artwork_element != artwork_element)
10498 stored_player[i].Frame = 0;
10500 stored_player[i].artwork_element = artwork_element;
10502 SetPlayerWaiting(&stored_player[i], FALSE);
10504 // set number of special actions for bored and sleeping animation
10505 stored_player[i].num_special_action_bored =
10506 get_num_special_action(artwork_element,
10507 ACTION_BORING_1, ACTION_BORING_LAST);
10508 stored_player[i].num_special_action_sleeping =
10509 get_num_special_action(artwork_element,
10510 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10517 case CA_SET_PLAYER_INVENTORY:
10519 for (i = 0; i < MAX_PLAYERS; i++)
10521 struct PlayerInfo *player = &stored_player[i];
10524 if (trigger_player_bits & (1 << i))
10526 int inventory_element = action_arg_element;
10528 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10529 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10530 action_arg == CA_ARG_ELEMENT_ACTION)
10532 int element = inventory_element;
10533 int collect_count = element_info[element].collect_count_initial;
10535 if (!IS_CUSTOM_ELEMENT(element))
10538 if (collect_count == 0)
10539 player->inventory_infinite_element = element;
10541 for (k = 0; k < collect_count; k++)
10542 if (player->inventory_size < MAX_INVENTORY_SIZE)
10543 player->inventory_element[player->inventory_size++] =
10546 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10547 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10548 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10550 if (player->inventory_infinite_element != EL_UNDEFINED &&
10551 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10552 action_arg_element_raw))
10553 player->inventory_infinite_element = EL_UNDEFINED;
10555 for (k = 0, j = 0; j < player->inventory_size; j++)
10557 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10558 action_arg_element_raw))
10559 player->inventory_element[k++] = player->inventory_element[j];
10562 player->inventory_size = k;
10564 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10566 if (player->inventory_size > 0)
10568 for (j = 0; j < player->inventory_size - 1; j++)
10569 player->inventory_element[j] = player->inventory_element[j + 1];
10571 player->inventory_size--;
10574 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10576 if (player->inventory_size > 0)
10577 player->inventory_size--;
10579 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10581 player->inventory_infinite_element = EL_UNDEFINED;
10582 player->inventory_size = 0;
10584 else if (action_arg == CA_ARG_INVENTORY_RESET)
10586 player->inventory_infinite_element = EL_UNDEFINED;
10587 player->inventory_size = 0;
10589 if (level.use_initial_inventory[i])
10591 for (j = 0; j < level.initial_inventory_size[i]; j++)
10593 int element = level.initial_inventory_content[i][j];
10594 int collect_count = element_info[element].collect_count_initial;
10596 if (!IS_CUSTOM_ELEMENT(element))
10599 if (collect_count == 0)
10600 player->inventory_infinite_element = element;
10602 for (k = 0; k < collect_count; k++)
10603 if (player->inventory_size < MAX_INVENTORY_SIZE)
10604 player->inventory_element[player->inventory_size++] =
10615 // ---------- CE actions -------------------------------------------------
10617 case CA_SET_CE_VALUE:
10619 int last_ce_value = CustomValue[x][y];
10621 CustomValue[x][y] = action_arg_number_new;
10623 if (CustomValue[x][y] != last_ce_value)
10625 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10626 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10628 if (CustomValue[x][y] == 0)
10630 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10631 ChangeCount[x][y] = 0; // allow at least one more change
10633 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10634 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10641 case CA_SET_CE_SCORE:
10643 int last_ce_score = ei->collect_score;
10645 ei->collect_score = action_arg_number_new;
10647 if (ei->collect_score != last_ce_score)
10649 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10650 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10652 if (ei->collect_score == 0)
10656 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10657 ChangeCount[x][y] = 0; // allow at least one more change
10659 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10660 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10663 This is a very special case that seems to be a mixture between
10664 CheckElementChange() and CheckTriggeredElementChange(): while
10665 the first one only affects single elements that are triggered
10666 directly, the second one affects multiple elements in the playfield
10667 that are triggered indirectly by another element. This is a third
10668 case: Changing the CE score always affects multiple identical CEs,
10669 so every affected CE must be checked, not only the single CE for
10670 which the CE score was changed in the first place (as every instance
10671 of that CE shares the same CE score, and therefore also can change)!
10673 SCAN_PLAYFIELD(xx, yy)
10675 if (Tile[xx][yy] == element)
10676 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10677 CE_SCORE_GETS_ZERO);
10685 case CA_SET_CE_ARTWORK:
10687 int artwork_element = action_arg_element;
10688 boolean reset_frame = FALSE;
10691 if (action_arg == CA_ARG_ELEMENT_RESET)
10692 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10695 if (ei->gfx_element != artwork_element)
10696 reset_frame = TRUE;
10698 ei->gfx_element = artwork_element;
10700 SCAN_PLAYFIELD(xx, yy)
10702 if (Tile[xx][yy] == element)
10706 ResetGfxAnimation(xx, yy);
10707 ResetRandomAnimationValue(xx, yy);
10710 TEST_DrawLevelField(xx, yy);
10717 // ---------- engine actions ---------------------------------------------
10719 case CA_SET_ENGINE_SCAN_MODE:
10721 InitPlayfieldScanMode(action_arg);
10731 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10733 int old_element = Tile[x][y];
10734 int new_element = GetElementFromGroupElement(element);
10735 int previous_move_direction = MovDir[x][y];
10736 int last_ce_value = CustomValue[x][y];
10737 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10738 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10739 boolean add_player_onto_element = (new_element_is_player &&
10740 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10741 IS_WALKABLE(old_element));
10743 if (!add_player_onto_element)
10745 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10746 RemoveMovingField(x, y);
10750 Tile[x][y] = new_element;
10752 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10753 MovDir[x][y] = previous_move_direction;
10755 if (element_info[new_element].use_last_ce_value)
10756 CustomValue[x][y] = last_ce_value;
10758 InitField_WithBug1(x, y, FALSE);
10760 new_element = Tile[x][y]; // element may have changed
10762 ResetGfxAnimation(x, y);
10763 ResetRandomAnimationValue(x, y);
10765 TEST_DrawLevelField(x, y);
10767 if (GFX_CRUMBLED(new_element))
10768 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10770 if (old_element == EL_EXPLOSION)
10772 Store[x][y] = Store2[x][y] = 0;
10774 // check if new element replaces an exploding player, requiring cleanup
10775 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
10776 StorePlayer[x][y] = 0;
10779 // check if element under the player changes from accessible to unaccessible
10780 // (needed for special case of dropping element which then changes)
10781 // (must be checked after creating new element for walkable group elements)
10782 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10783 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10785 KillPlayer(PLAYERINFO(x, y));
10791 // "ChangeCount" not set yet to allow "entered by player" change one time
10792 if (new_element_is_player)
10793 RelocatePlayer(x, y, new_element);
10796 ChangeCount[x][y]++; // count number of changes in the same frame
10798 TestIfBadThingTouchesPlayer(x, y);
10799 TestIfPlayerTouchesCustomElement(x, y);
10800 TestIfElementTouchesCustomElement(x, y);
10803 static void CreateField(int x, int y, int element)
10805 CreateFieldExt(x, y, element, FALSE);
10808 static void CreateElementFromChange(int x, int y, int element)
10810 element = GET_VALID_RUNTIME_ELEMENT(element);
10812 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10814 int old_element = Tile[x][y];
10816 // prevent changed element from moving in same engine frame
10817 // unless both old and new element can either fall or move
10818 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10819 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10823 CreateFieldExt(x, y, element, TRUE);
10826 static boolean ChangeElement(int x, int y, int element, int page)
10828 struct ElementInfo *ei = &element_info[element];
10829 struct ElementChangeInfo *change = &ei->change_page[page];
10830 int ce_value = CustomValue[x][y];
10831 int ce_score = ei->collect_score;
10832 int target_element;
10833 int old_element = Tile[x][y];
10835 // always use default change event to prevent running into a loop
10836 if (ChangeEvent[x][y] == -1)
10837 ChangeEvent[x][y] = CE_DELAY;
10839 if (ChangeEvent[x][y] == CE_DELAY)
10841 // reset actual trigger element, trigger player and action element
10842 change->actual_trigger_element = EL_EMPTY;
10843 change->actual_trigger_player = EL_EMPTY;
10844 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10845 change->actual_trigger_side = CH_SIDE_NONE;
10846 change->actual_trigger_ce_value = 0;
10847 change->actual_trigger_ce_score = 0;
10848 change->actual_trigger_x = -1;
10849 change->actual_trigger_y = -1;
10852 // do not change elements more than a specified maximum number of changes
10853 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10856 ChangeCount[x][y]++; // count number of changes in the same frame
10858 if (ei->has_anim_event)
10859 HandleGlobalAnimEventByElementChange(element, page, x, y,
10860 change->actual_trigger_x,
10861 change->actual_trigger_y);
10863 if (change->explode)
10870 if (change->use_target_content)
10872 boolean complete_replace = TRUE;
10873 boolean can_replace[3][3];
10876 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10879 boolean is_walkable;
10880 boolean is_diggable;
10881 boolean is_collectible;
10882 boolean is_removable;
10883 boolean is_destructible;
10884 int ex = x + xx - 1;
10885 int ey = y + yy - 1;
10886 int content_element = change->target_content.e[xx][yy];
10889 can_replace[xx][yy] = TRUE;
10891 if (ex == x && ey == y) // do not check changing element itself
10894 if (content_element == EL_EMPTY_SPACE)
10896 can_replace[xx][yy] = FALSE; // do not replace border with space
10901 if (!IN_LEV_FIELD(ex, ey))
10903 can_replace[xx][yy] = FALSE;
10904 complete_replace = FALSE;
10911 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10912 e = MovingOrBlocked2Element(ex, ey);
10914 is_empty = (IS_FREE(ex, ey) ||
10915 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10917 is_walkable = (is_empty || IS_WALKABLE(e));
10918 is_diggable = (is_empty || IS_DIGGABLE(e));
10919 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10920 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10921 is_removable = (is_diggable || is_collectible);
10923 can_replace[xx][yy] =
10924 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10925 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10926 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10927 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10928 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10929 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10930 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10932 if (!can_replace[xx][yy])
10933 complete_replace = FALSE;
10936 if (!change->only_if_complete || complete_replace)
10938 boolean something_has_changed = FALSE;
10940 if (change->only_if_complete && change->use_random_replace &&
10941 RND(100) < change->random_percentage)
10944 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10946 int ex = x + xx - 1;
10947 int ey = y + yy - 1;
10948 int content_element;
10950 if (can_replace[xx][yy] && (!change->use_random_replace ||
10951 RND(100) < change->random_percentage))
10953 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10954 RemoveMovingField(ex, ey);
10956 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10958 content_element = change->target_content.e[xx][yy];
10959 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10960 ce_value, ce_score);
10962 CreateElementFromChange(ex, ey, target_element);
10964 something_has_changed = TRUE;
10966 // for symmetry reasons, freeze newly created border elements
10967 if (ex != x || ey != y)
10968 Stop[ex][ey] = TRUE; // no more moving in this frame
10972 if (something_has_changed)
10974 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10975 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10981 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10982 ce_value, ce_score);
10984 if (element == EL_DIAGONAL_GROWING ||
10985 element == EL_DIAGONAL_SHRINKING)
10987 target_element = Store[x][y];
10989 Store[x][y] = EL_EMPTY;
10992 // special case: element changes to player (and may be kept if walkable)
10993 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10994 CreateElementFromChange(x, y, EL_EMPTY);
10996 CreateElementFromChange(x, y, target_element);
10998 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10999 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
11002 // this uses direct change before indirect change
11003 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
11008 static void HandleElementChange(int x, int y, int page)
11010 int element = MovingOrBlocked2Element(x, y);
11011 struct ElementInfo *ei = &element_info[element];
11012 struct ElementChangeInfo *change = &ei->change_page[page];
11013 boolean handle_action_before_change = FALSE;
11016 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
11017 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
11019 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
11020 x, y, element, element_info[element].token_name);
11021 Debug("game:playing:HandleElementChange", "This should never happen!");
11025 // this can happen with classic bombs on walkable, changing elements
11026 if (!CAN_CHANGE_OR_HAS_ACTION(element))
11031 if (ChangeDelay[x][y] == 0) // initialize element change
11033 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
11035 if (change->can_change)
11037 // !!! not clear why graphic animation should be reset at all here !!!
11038 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
11039 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
11042 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
11044 When using an animation frame delay of 1 (this only happens with
11045 "sp_zonk.moving.left/right" in the classic graphics), the default
11046 (non-moving) animation shows wrong animation frames (while the
11047 moving animation, like "sp_zonk.moving.left/right", is correct,
11048 so this graphical bug never shows up with the classic graphics).
11049 For an animation with 4 frames, this causes wrong frames 0,0,1,2
11050 be drawn instead of the correct frames 0,1,2,3. This is caused by
11051 "GfxFrame[][]" being reset *twice* (in two successive frames) after
11052 an element change: First when the change delay ("ChangeDelay[][]")
11053 counter has reached zero after decrementing, then a second time in
11054 the next frame (after "GfxFrame[][]" was already incremented) when
11055 "ChangeDelay[][]" is reset to the initial delay value again.
11057 This causes frame 0 to be drawn twice, while the last frame won't
11058 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
11060 As some animations may already be cleverly designed around this bug
11061 (at least the "Snake Bite" snake tail animation does this), it cannot
11062 simply be fixed here without breaking such existing animations.
11063 Unfortunately, it cannot easily be detected if a graphics set was
11064 designed "before" or "after" the bug was fixed. As a workaround,
11065 a new graphics set option "game.graphics_engine_version" was added
11066 to be able to specify the game's major release version for which the
11067 graphics set was designed, which can then be used to decide if the
11068 bugfix should be used (version 4 and above) or not (version 3 or
11069 below, or if no version was specified at all, as with old sets).
11071 (The wrong/fixed animation frames can be tested with the test level set
11072 "test_gfxframe" and level "000", which contains a specially prepared
11073 custom element at level position (x/y) == (11/9) which uses the zonk
11074 animation mentioned above. Using "game.graphics_engine_version: 4"
11075 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
11076 This can also be seen from the debug output for this test element.)
11079 // when a custom element is about to change (for example by change delay),
11080 // do not reset graphic animation when the custom element is moving
11081 if (game.graphics_engine_version < 4 &&
11084 ResetGfxAnimation(x, y);
11085 ResetRandomAnimationValue(x, y);
11088 if (change->pre_change_function)
11089 change->pre_change_function(x, y);
11093 ChangeDelay[x][y]--;
11095 if (ChangeDelay[x][y] != 0) // continue element change
11097 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11099 // also needed if CE can not change, but has CE delay with CE action
11100 if (IS_ANIMATED(graphic))
11101 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11103 if (change->can_change)
11105 if (change->change_function)
11106 change->change_function(x, y);
11109 else // finish element change
11111 if (ChangePage[x][y] != -1) // remember page from delayed change
11113 page = ChangePage[x][y];
11114 ChangePage[x][y] = -1;
11116 change = &ei->change_page[page];
11119 if (IS_MOVING(x, y)) // never change a running system ;-)
11121 ChangeDelay[x][y] = 1; // try change after next move step
11122 ChangePage[x][y] = page; // remember page to use for change
11127 // special case: set new level random seed before changing element
11128 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11129 handle_action_before_change = TRUE;
11131 if (change->has_action && handle_action_before_change)
11132 ExecuteCustomElementAction(x, y, element, page);
11134 if (change->can_change)
11136 if (ChangeElement(x, y, element, page))
11138 if (change->post_change_function)
11139 change->post_change_function(x, y);
11143 if (change->has_action && !handle_action_before_change)
11144 ExecuteCustomElementAction(x, y, element, page);
11148 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11149 int trigger_element,
11151 int trigger_player,
11155 boolean change_done_any = FALSE;
11156 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11159 if (!(trigger_events[trigger_element][trigger_event]))
11162 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11164 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11166 int element = EL_CUSTOM_START + i;
11167 boolean change_done = FALSE;
11170 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11171 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11174 for (p = 0; p < element_info[element].num_change_pages; p++)
11176 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11178 if (change->can_change_or_has_action &&
11179 change->has_event[trigger_event] &&
11180 change->trigger_side & trigger_side &&
11181 change->trigger_player & trigger_player &&
11182 change->trigger_page & trigger_page_bits &&
11183 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11185 change->actual_trigger_element = trigger_element;
11186 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11187 change->actual_trigger_player_bits = trigger_player;
11188 change->actual_trigger_side = trigger_side;
11189 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11190 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11191 change->actual_trigger_x = trigger_x;
11192 change->actual_trigger_y = trigger_y;
11194 if ((change->can_change && !change_done) || change->has_action)
11198 SCAN_PLAYFIELD(x, y)
11200 if (Tile[x][y] == element)
11202 if (change->can_change && !change_done)
11204 // if element already changed in this frame, not only prevent
11205 // another element change (checked in ChangeElement()), but
11206 // also prevent additional element actions for this element
11208 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11209 !level.use_action_after_change_bug)
11212 ChangeDelay[x][y] = 1;
11213 ChangeEvent[x][y] = trigger_event;
11215 HandleElementChange(x, y, p);
11217 else if (change->has_action)
11219 // if element already changed in this frame, not only prevent
11220 // another element change (checked in ChangeElement()), but
11221 // also prevent additional element actions for this element
11223 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11224 !level.use_action_after_change_bug)
11227 ExecuteCustomElementAction(x, y, element, p);
11228 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11233 if (change->can_change)
11235 change_done = TRUE;
11236 change_done_any = TRUE;
11243 RECURSION_LOOP_DETECTION_END();
11245 return change_done_any;
11248 static boolean CheckElementChangeExt(int x, int y,
11250 int trigger_element,
11252 int trigger_player,
11255 boolean change_done = FALSE;
11258 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11259 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11262 if (Tile[x][y] == EL_BLOCKED)
11264 Blocked2Moving(x, y, &x, &y);
11265 element = Tile[x][y];
11268 // check if element has already changed or is about to change after moving
11269 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11270 Tile[x][y] != element) ||
11272 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11273 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11274 ChangePage[x][y] != -1)))
11277 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11279 for (p = 0; p < element_info[element].num_change_pages; p++)
11281 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11283 /* check trigger element for all events where the element that is checked
11284 for changing interacts with a directly adjacent element -- this is
11285 different to element changes that affect other elements to change on the
11286 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11287 boolean check_trigger_element =
11288 (trigger_event == CE_NEXT_TO_X ||
11289 trigger_event == CE_TOUCHING_X ||
11290 trigger_event == CE_HITTING_X ||
11291 trigger_event == CE_HIT_BY_X ||
11292 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11294 if (change->can_change_or_has_action &&
11295 change->has_event[trigger_event] &&
11296 change->trigger_side & trigger_side &&
11297 change->trigger_player & trigger_player &&
11298 (!check_trigger_element ||
11299 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11301 change->actual_trigger_element = trigger_element;
11302 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11303 change->actual_trigger_player_bits = trigger_player;
11304 change->actual_trigger_side = trigger_side;
11305 change->actual_trigger_ce_value = CustomValue[x][y];
11306 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11307 change->actual_trigger_x = x;
11308 change->actual_trigger_y = y;
11310 // special case: trigger element not at (x,y) position for some events
11311 if (check_trigger_element)
11323 { 0, 0 }, { 0, 0 }, { 0, 0 },
11327 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11328 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11330 change->actual_trigger_ce_value = CustomValue[xx][yy];
11331 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11332 change->actual_trigger_x = xx;
11333 change->actual_trigger_y = yy;
11336 if (change->can_change && !change_done)
11338 ChangeDelay[x][y] = 1;
11339 ChangeEvent[x][y] = trigger_event;
11341 HandleElementChange(x, y, p);
11343 change_done = TRUE;
11345 else if (change->has_action)
11347 ExecuteCustomElementAction(x, y, element, p);
11348 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11353 RECURSION_LOOP_DETECTION_END();
11355 return change_done;
11358 static void PlayPlayerSound(struct PlayerInfo *player)
11360 int jx = player->jx, jy = player->jy;
11361 int sound_element = player->artwork_element;
11362 int last_action = player->last_action_waiting;
11363 int action = player->action_waiting;
11365 if (player->is_waiting)
11367 if (action != last_action)
11368 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11370 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11374 if (action != last_action)
11375 StopSound(element_info[sound_element].sound[last_action]);
11377 if (last_action == ACTION_SLEEPING)
11378 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11382 static void PlayAllPlayersSound(void)
11386 for (i = 0; i < MAX_PLAYERS; i++)
11387 if (stored_player[i].active)
11388 PlayPlayerSound(&stored_player[i]);
11391 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11393 boolean last_waiting = player->is_waiting;
11394 int move_dir = player->MovDir;
11396 player->dir_waiting = move_dir;
11397 player->last_action_waiting = player->action_waiting;
11401 if (!last_waiting) // not waiting -> waiting
11403 player->is_waiting = TRUE;
11405 player->frame_counter_bored =
11407 game.player_boring_delay_fixed +
11408 GetSimpleRandom(game.player_boring_delay_random);
11409 player->frame_counter_sleeping =
11411 game.player_sleeping_delay_fixed +
11412 GetSimpleRandom(game.player_sleeping_delay_random);
11414 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11417 if (game.player_sleeping_delay_fixed +
11418 game.player_sleeping_delay_random > 0 &&
11419 player->anim_delay_counter == 0 &&
11420 player->post_delay_counter == 0 &&
11421 FrameCounter >= player->frame_counter_sleeping)
11422 player->is_sleeping = TRUE;
11423 else if (game.player_boring_delay_fixed +
11424 game.player_boring_delay_random > 0 &&
11425 FrameCounter >= player->frame_counter_bored)
11426 player->is_bored = TRUE;
11428 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11429 player->is_bored ? ACTION_BORING :
11432 if (player->is_sleeping && player->use_murphy)
11434 // special case for sleeping Murphy when leaning against non-free tile
11436 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11437 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11438 !IS_MOVING(player->jx - 1, player->jy)))
11439 move_dir = MV_LEFT;
11440 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11441 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11442 !IS_MOVING(player->jx + 1, player->jy)))
11443 move_dir = MV_RIGHT;
11445 player->is_sleeping = FALSE;
11447 player->dir_waiting = move_dir;
11450 if (player->is_sleeping)
11452 if (player->num_special_action_sleeping > 0)
11454 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11456 int last_special_action = player->special_action_sleeping;
11457 int num_special_action = player->num_special_action_sleeping;
11458 int special_action =
11459 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11460 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11461 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11462 last_special_action + 1 : ACTION_SLEEPING);
11463 int special_graphic =
11464 el_act_dir2img(player->artwork_element, special_action, move_dir);
11466 player->anim_delay_counter =
11467 graphic_info[special_graphic].anim_delay_fixed +
11468 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11469 player->post_delay_counter =
11470 graphic_info[special_graphic].post_delay_fixed +
11471 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11473 player->special_action_sleeping = special_action;
11476 if (player->anim_delay_counter > 0)
11478 player->action_waiting = player->special_action_sleeping;
11479 player->anim_delay_counter--;
11481 else if (player->post_delay_counter > 0)
11483 player->post_delay_counter--;
11487 else if (player->is_bored)
11489 if (player->num_special_action_bored > 0)
11491 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11493 int special_action =
11494 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11495 int special_graphic =
11496 el_act_dir2img(player->artwork_element, special_action, move_dir);
11498 player->anim_delay_counter =
11499 graphic_info[special_graphic].anim_delay_fixed +
11500 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11501 player->post_delay_counter =
11502 graphic_info[special_graphic].post_delay_fixed +
11503 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11505 player->special_action_bored = special_action;
11508 if (player->anim_delay_counter > 0)
11510 player->action_waiting = player->special_action_bored;
11511 player->anim_delay_counter--;
11513 else if (player->post_delay_counter > 0)
11515 player->post_delay_counter--;
11520 else if (last_waiting) // waiting -> not waiting
11522 player->is_waiting = FALSE;
11523 player->is_bored = FALSE;
11524 player->is_sleeping = FALSE;
11526 player->frame_counter_bored = -1;
11527 player->frame_counter_sleeping = -1;
11529 player->anim_delay_counter = 0;
11530 player->post_delay_counter = 0;
11532 player->dir_waiting = player->MovDir;
11533 player->action_waiting = ACTION_DEFAULT;
11535 player->special_action_bored = ACTION_DEFAULT;
11536 player->special_action_sleeping = ACTION_DEFAULT;
11540 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11542 if ((!player->is_moving && player->was_moving) ||
11543 (player->MovPos == 0 && player->was_moving) ||
11544 (player->is_snapping && !player->was_snapping) ||
11545 (player->is_dropping && !player->was_dropping))
11547 if (!CheckSaveEngineSnapshotToList())
11550 player->was_moving = FALSE;
11551 player->was_snapping = TRUE;
11552 player->was_dropping = TRUE;
11556 if (player->is_moving)
11557 player->was_moving = TRUE;
11559 if (!player->is_snapping)
11560 player->was_snapping = FALSE;
11562 if (!player->is_dropping)
11563 player->was_dropping = FALSE;
11566 static struct MouseActionInfo mouse_action_last = { 0 };
11567 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11568 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11571 CheckSaveEngineSnapshotToList();
11573 mouse_action_last = mouse_action;
11576 static void CheckSingleStepMode(struct PlayerInfo *player)
11578 if (tape.single_step && tape.recording && !tape.pausing)
11580 // as it is called "single step mode", just return to pause mode when the
11581 // player stopped moving after one tile (or never starts moving at all)
11582 // (reverse logic needed here in case single step mode used in team mode)
11583 if (player->is_moving ||
11584 player->is_pushing ||
11585 player->is_dropping_pressed ||
11586 player->effective_mouse_action.button)
11587 game.enter_single_step_mode = FALSE;
11590 CheckSaveEngineSnapshot(player);
11593 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11595 int left = player_action & JOY_LEFT;
11596 int right = player_action & JOY_RIGHT;
11597 int up = player_action & JOY_UP;
11598 int down = player_action & JOY_DOWN;
11599 int button1 = player_action & JOY_BUTTON_1;
11600 int button2 = player_action & JOY_BUTTON_2;
11601 int dx = (left ? -1 : right ? 1 : 0);
11602 int dy = (up ? -1 : down ? 1 : 0);
11604 if (!player->active || tape.pausing)
11610 SnapField(player, dx, dy);
11614 DropElement(player);
11616 MovePlayer(player, dx, dy);
11619 CheckSingleStepMode(player);
11621 SetPlayerWaiting(player, FALSE);
11623 return player_action;
11627 // no actions for this player (no input at player's configured device)
11629 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11630 SnapField(player, 0, 0);
11631 CheckGravityMovementWhenNotMoving(player);
11633 if (player->MovPos == 0)
11634 SetPlayerWaiting(player, TRUE);
11636 if (player->MovPos == 0) // needed for tape.playing
11637 player->is_moving = FALSE;
11639 player->is_dropping = FALSE;
11640 player->is_dropping_pressed = FALSE;
11641 player->drop_pressed_delay = 0;
11643 CheckSingleStepMode(player);
11649 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11652 if (!tape.use_mouse_actions)
11655 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11656 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11657 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11660 static void SetTapeActionFromMouseAction(byte *tape_action,
11661 struct MouseActionInfo *mouse_action)
11663 if (!tape.use_mouse_actions)
11666 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11667 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11668 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11671 static void CheckLevelSolved(void)
11673 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11675 if (game_bd.level_solved &&
11676 !game_bd.game_over) // game won
11680 game_bd.game_over = TRUE;
11682 game.all_players_gone = TRUE;
11685 if (game_bd.game_over) // game lost
11686 game.all_players_gone = TRUE;
11688 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11690 if (game_em.level_solved &&
11691 !game_em.game_over) // game won
11695 game_em.game_over = TRUE;
11697 game.all_players_gone = TRUE;
11700 if (game_em.game_over) // game lost
11701 game.all_players_gone = TRUE;
11703 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11705 if (game_sp.level_solved &&
11706 !game_sp.game_over) // game won
11710 game_sp.game_over = TRUE;
11712 game.all_players_gone = TRUE;
11715 if (game_sp.game_over) // game lost
11716 game.all_players_gone = TRUE;
11718 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11720 if (game_mm.level_solved &&
11721 !game_mm.game_over) // game won
11725 game_mm.game_over = TRUE;
11727 game.all_players_gone = TRUE;
11730 if (game_mm.game_over) // game lost
11731 game.all_players_gone = TRUE;
11735 static void PlayTimeoutSound(int seconds_left)
11737 // will be played directly by BD engine (for classic bonus time sounds)
11738 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && checkBonusTime_BD())
11741 // try to use individual "running out of time" sound for each second left
11742 int sound = SND_GAME_RUNNING_OUT_OF_TIME_0 - seconds_left;
11744 // if special sound per second not defined, use default sound
11745 if (getSoundInfoEntryFilename(sound) == NULL)
11746 sound = SND_GAME_RUNNING_OUT_OF_TIME;
11748 // if out of time, but player still alive, play special "timeout" sound, if defined
11749 if (seconds_left == 0 && !checkGameFailed())
11750 if (getSoundInfoEntryFilename(SND_GAME_TIMEOUT) != NULL)
11751 sound = SND_GAME_TIMEOUT;
11756 static void CheckLevelTime_StepCounter(void)
11766 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11767 PlayTimeoutSound(TimeLeft);
11769 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11771 DisplayGameControlValues();
11773 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11774 for (i = 0; i < MAX_PLAYERS; i++)
11775 KillPlayer(&stored_player[i]);
11777 else if (game.no_level_time_limit && !game.all_players_gone)
11779 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11781 DisplayGameControlValues();
11785 static void CheckLevelTime(void)
11787 int frames_per_second = FRAMES_PER_SECOND;
11790 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11792 // level time may be running slower in native BD engine
11793 frames_per_second = getFramesPerSecond_BD();
11795 // if native engine time changed, force main engine time change
11796 if (getTimeLeft_BD() < TimeLeft)
11797 TimeFrames = frames_per_second;
11799 // if last second running, wait for native engine time to exactly reach zero
11800 if (getTimeLeft_BD() == 1 && TimeLeft == 1)
11801 TimeFrames = frames_per_second - 1;
11804 if (TimeFrames >= frames_per_second)
11808 for (i = 0; i < MAX_PLAYERS; i++)
11810 struct PlayerInfo *player = &stored_player[i];
11812 if (SHIELD_ON(player))
11814 player->shield_normal_time_left--;
11816 if (player->shield_deadly_time_left > 0)
11817 player->shield_deadly_time_left--;
11821 if (!game.LevelSolved && !level.use_step_counter)
11829 if (TimeLeft <= 10 && game.time_limit)
11830 PlayTimeoutSound(TimeLeft);
11832 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11833 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11835 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11837 if (!TimeLeft && game.time_limit)
11839 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11841 if (game_bd.game->cave->player_state == GD_PL_LIVING)
11842 game_bd.game->cave->player_state = GD_PL_TIMEOUT;
11844 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11846 game_em.lev->killed_out_of_time = TRUE;
11850 for (i = 0; i < MAX_PLAYERS; i++)
11851 KillPlayer(&stored_player[i]);
11855 else if (game.no_level_time_limit && !game.all_players_gone)
11857 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11860 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11864 if (TapeTimeFrames >= FRAMES_PER_SECOND)
11866 TapeTimeFrames = 0;
11869 if (tape.recording || tape.playing)
11870 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11873 if (tape.recording || tape.playing)
11874 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11876 UpdateAndDisplayGameControlValues();
11879 void AdvanceFrameAndPlayerCounters(int player_nr)
11883 // handle game and tape time differently for native BD game engine
11885 // tape time is running in native BD engine even if player is not hatched yet
11886 if (!checkGameRunning())
11889 // advance frame counters (global frame counter and tape time frame counter)
11893 // level time is running in native BD engine after player is being hatched
11894 if (!checkGamePlaying())
11897 // advance time frame counter (used to control available time to solve level)
11900 // advance player counters (counters for move delay, move animation etc.)
11901 for (i = 0; i < MAX_PLAYERS; i++)
11903 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11904 int move_delay_value = stored_player[i].move_delay_value;
11905 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11907 if (!advance_player_counters) // not all players may be affected
11910 if (move_frames == 0) // less than one move per game frame
11912 int stepsize = TILEX / move_delay_value;
11913 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11914 int count = (stored_player[i].is_moving ?
11915 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11917 if (count % delay == 0)
11921 stored_player[i].Frame += move_frames;
11923 if (stored_player[i].MovPos != 0)
11924 stored_player[i].StepFrame += move_frames;
11926 if (stored_player[i].move_delay > 0)
11927 stored_player[i].move_delay--;
11929 // due to bugs in previous versions, counter must count up, not down
11930 if (stored_player[i].push_delay != -1)
11931 stored_player[i].push_delay++;
11933 if (stored_player[i].drop_delay > 0)
11934 stored_player[i].drop_delay--;
11936 if (stored_player[i].is_dropping_pressed)
11937 stored_player[i].drop_pressed_delay++;
11941 void AdvanceFrameCounter(void)
11946 void AdvanceGfxFrame(void)
11950 SCAN_PLAYFIELD(x, y)
11956 static void HandleMouseAction(struct MouseActionInfo *mouse_action,
11957 struct MouseActionInfo *mouse_action_last)
11959 if (mouse_action->button)
11961 int new_button = (mouse_action->button && mouse_action_last->button == 0);
11962 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button);
11963 int x = mouse_action->lx;
11964 int y = mouse_action->ly;
11965 int element = Tile[x][y];
11969 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
11970 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
11974 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
11975 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
11978 if (level.use_step_counter)
11980 boolean counted_click = FALSE;
11982 // element clicked that can change when clicked/pressed
11983 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
11984 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
11985 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
11986 counted_click = TRUE;
11988 // element clicked that can trigger change when clicked/pressed
11989 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
11990 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
11991 counted_click = TRUE;
11993 if (new_button && counted_click)
11994 CheckLevelTime_StepCounter();
11999 void StartGameActions(boolean init_network_game, boolean record_tape,
12002 unsigned int new_random_seed = InitRND(random_seed);
12005 TapeStartRecording(new_random_seed);
12007 if (setup.auto_pause_on_start && !tape.pausing)
12008 TapeTogglePause(TAPE_TOGGLE_MANUAL);
12010 if (init_network_game)
12012 SendToServer_LevelFile();
12013 SendToServer_StartPlaying();
12021 static void GameActionsExt(void)
12024 static unsigned int game_frame_delay = 0;
12026 unsigned int game_frame_delay_value;
12027 byte *recorded_player_action;
12028 byte summarized_player_action = 0;
12029 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
12032 // detect endless loops, caused by custom element programming
12033 if (recursion_loop_detected && recursion_loop_depth == 0)
12035 char *message = getStringCat3("Internal Error! Element ",
12036 EL_NAME(recursion_loop_element),
12037 " caused endless loop! Quit the game?");
12039 Warn("element '%s' caused endless loop in game engine",
12040 EL_NAME(recursion_loop_element));
12042 RequestQuitGameExt(program.headless, level_editor_test_game, message);
12044 recursion_loop_detected = FALSE; // if game should be continued
12051 if (game.restart_level)
12052 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
12054 CheckLevelSolved();
12056 if (game.LevelSolved && !game.LevelSolved_GameEnd)
12059 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
12062 if (game_status != GAME_MODE_PLAYING) // status might have changed
12065 game_frame_delay_value =
12066 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
12068 if (tape.playing && tape.warp_forward && !tape.pausing)
12069 game_frame_delay_value = 0;
12071 SetVideoFrameDelay(game_frame_delay_value);
12073 // (de)activate virtual buttons depending on current game status
12074 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
12076 if (game.all_players_gone) // if no players there to be controlled anymore
12077 SetOverlayActive(FALSE);
12078 else if (!tape.playing) // if game continues after tape stopped playing
12079 SetOverlayActive(TRUE);
12084 // ---------- main game synchronization point ----------
12086 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12088 Debug("game:playing:skip", "skip == %d", skip);
12091 // ---------- main game synchronization point ----------
12093 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12097 if (network_playing && !network_player_action_received)
12099 // try to get network player actions in time
12101 // last chance to get network player actions without main loop delay
12102 HandleNetworking();
12104 // game was quit by network peer
12105 if (game_status != GAME_MODE_PLAYING)
12108 // check if network player actions still missing and game still running
12109 if (!network_player_action_received && !checkGameEnded())
12110 return; // failed to get network player actions in time
12112 // do not yet reset "network_player_action_received" (for tape.pausing)
12118 // at this point we know that we really continue executing the game
12120 network_player_action_received = FALSE;
12122 // when playing tape, read previously recorded player input from tape data
12123 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
12125 local_player->effective_mouse_action = local_player->mouse_action;
12127 if (recorded_player_action != NULL)
12128 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
12129 recorded_player_action);
12131 // TapePlayAction() may return NULL when toggling to "pause before death"
12135 if (tape.set_centered_player)
12137 game.centered_player_nr_next = tape.centered_player_nr_next;
12138 game.set_centered_player = TRUE;
12141 for (i = 0; i < MAX_PLAYERS; i++)
12143 summarized_player_action |= stored_player[i].action;
12145 if (!network_playing && (game.team_mode || tape.playing))
12146 stored_player[i].effective_action = stored_player[i].action;
12149 if (network_playing && !checkGameEnded())
12150 SendToServer_MovePlayer(summarized_player_action);
12152 // summarize all actions at local players mapped input device position
12153 // (this allows using different input devices in single player mode)
12154 if (!network.enabled && !game.team_mode)
12155 stored_player[map_player_action[local_player->index_nr]].effective_action =
12156 summarized_player_action;
12158 // summarize all actions at centered player in local team mode
12159 if (tape.recording &&
12160 setup.team_mode && !network.enabled &&
12161 setup.input_on_focus &&
12162 game.centered_player_nr != -1)
12164 for (i = 0; i < MAX_PLAYERS; i++)
12165 stored_player[map_player_action[i]].effective_action =
12166 (i == game.centered_player_nr ? summarized_player_action : 0);
12169 if (recorded_player_action != NULL)
12170 for (i = 0; i < MAX_PLAYERS; i++)
12171 stored_player[i].effective_action = recorded_player_action[i];
12173 for (i = 0; i < MAX_PLAYERS; i++)
12175 tape_action[i] = stored_player[i].effective_action;
12177 /* (this may happen in the RND game engine if a player was not present on
12178 the playfield on level start, but appeared later from a custom element */
12179 if (setup.team_mode &&
12182 !tape.player_participates[i])
12183 tape.player_participates[i] = TRUE;
12186 SetTapeActionFromMouseAction(tape_action,
12187 &local_player->effective_mouse_action);
12189 // only record actions from input devices, but not programmed actions
12190 if (tape.recording)
12191 TapeRecordAction(tape_action);
12193 // remember if game was played (especially after tape stopped playing)
12194 if (!tape.playing && summarized_player_action && !checkGameFailed())
12195 game.GamePlayed = TRUE;
12197 #if USE_NEW_PLAYER_ASSIGNMENTS
12198 // !!! also map player actions in single player mode !!!
12199 // if (game.team_mode)
12202 byte mapped_action[MAX_PLAYERS];
12204 #if DEBUG_PLAYER_ACTIONS
12205 for (i = 0; i < MAX_PLAYERS; i++)
12206 DebugContinued("", "%d, ", stored_player[i].effective_action);
12209 for (i = 0; i < MAX_PLAYERS; i++)
12210 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
12212 for (i = 0; i < MAX_PLAYERS; i++)
12213 stored_player[i].effective_action = mapped_action[i];
12215 #if DEBUG_PLAYER_ACTIONS
12216 DebugContinued("", "=> ");
12217 for (i = 0; i < MAX_PLAYERS; i++)
12218 DebugContinued("", "%d, ", stored_player[i].effective_action);
12219 DebugContinued("game:playing:player", "\n");
12222 #if DEBUG_PLAYER_ACTIONS
12225 for (i = 0; i < MAX_PLAYERS; i++)
12226 DebugContinued("", "%d, ", stored_player[i].effective_action);
12227 DebugContinued("game:playing:player", "\n");
12232 for (i = 0; i < MAX_PLAYERS; i++)
12234 // allow engine snapshot in case of changed movement attempt
12235 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12236 (stored_player[i].effective_action & KEY_MOTION))
12237 game.snapshot.changed_action = TRUE;
12239 // allow engine snapshot in case of snapping/dropping attempt
12240 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12241 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12242 game.snapshot.changed_action = TRUE;
12244 game.snapshot.last_action[i] = stored_player[i].effective_action;
12247 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
12249 GameActions_BD_Main();
12251 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12253 GameActions_EM_Main();
12255 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12257 GameActions_SP_Main();
12259 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12261 GameActions_MM_Main();
12265 GameActions_RND_Main();
12268 BlitScreenToBitmap(backbuffer);
12270 CheckLevelSolved();
12273 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12275 if (global.show_frames_per_second)
12277 static unsigned int fps_counter = 0;
12278 static int fps_frames = 0;
12279 unsigned int fps_delay_ms = Counter() - fps_counter;
12283 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12285 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12288 fps_counter = Counter();
12290 // always draw FPS to screen after FPS value was updated
12291 redraw_mask |= REDRAW_FPS;
12294 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12295 if (GetDrawDeactivationMask() == REDRAW_NONE)
12296 redraw_mask |= REDRAW_FPS;
12300 static void GameActions_CheckSaveEngineSnapshot(void)
12302 if (!game.snapshot.save_snapshot)
12305 // clear flag for saving snapshot _before_ saving snapshot
12306 game.snapshot.save_snapshot = FALSE;
12308 SaveEngineSnapshotToList();
12311 void GameActions(void)
12315 GameActions_CheckSaveEngineSnapshot();
12318 void GameActions_BD_Main(void)
12320 byte effective_action[MAX_PLAYERS];
12323 for (i = 0; i < MAX_PLAYERS; i++)
12324 effective_action[i] = stored_player[i].effective_action;
12326 GameActions_BD(effective_action);
12329 void GameActions_EM_Main(void)
12331 byte effective_action[MAX_PLAYERS];
12334 for (i = 0; i < MAX_PLAYERS; i++)
12335 effective_action[i] = stored_player[i].effective_action;
12337 GameActions_EM(effective_action);
12340 void GameActions_SP_Main(void)
12342 byte effective_action[MAX_PLAYERS];
12345 for (i = 0; i < MAX_PLAYERS; i++)
12346 effective_action[i] = stored_player[i].effective_action;
12348 GameActions_SP(effective_action);
12350 for (i = 0; i < MAX_PLAYERS; i++)
12352 if (stored_player[i].force_dropping)
12353 stored_player[i].action |= KEY_BUTTON_DROP;
12355 stored_player[i].force_dropping = FALSE;
12359 void GameActions_MM_Main(void)
12363 GameActions_MM(local_player->effective_mouse_action);
12366 void GameActions_RND_Main(void)
12371 void GameActions_RND(void)
12373 static struct MouseActionInfo mouse_action_last = { 0 };
12374 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12375 int magic_wall_x = 0, magic_wall_y = 0;
12376 int i, x, y, element, graphic, last_gfx_frame;
12378 InitPlayfieldScanModeVars();
12380 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12382 SCAN_PLAYFIELD(x, y)
12384 ChangeCount[x][y] = 0;
12385 ChangeEvent[x][y] = -1;
12389 if (game.set_centered_player)
12391 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12393 // switching to "all players" only possible if all players fit to screen
12394 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12396 game.centered_player_nr_next = game.centered_player_nr;
12397 game.set_centered_player = FALSE;
12400 // do not switch focus to non-existing (or non-active) player
12401 if (game.centered_player_nr_next >= 0 &&
12402 !stored_player[game.centered_player_nr_next].active)
12404 game.centered_player_nr_next = game.centered_player_nr;
12405 game.set_centered_player = FALSE;
12409 if (game.set_centered_player &&
12410 ScreenMovPos == 0) // screen currently aligned at tile position
12414 if (game.centered_player_nr_next == -1)
12416 setScreenCenteredToAllPlayers(&sx, &sy);
12420 sx = stored_player[game.centered_player_nr_next].jx;
12421 sy = stored_player[game.centered_player_nr_next].jy;
12424 game.centered_player_nr = game.centered_player_nr_next;
12425 game.set_centered_player = FALSE;
12427 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12428 DrawGameDoorValues();
12431 // check single step mode (set flag and clear again if any player is active)
12432 game.enter_single_step_mode =
12433 (tape.single_step && tape.recording && !tape.pausing);
12435 for (i = 0; i < MAX_PLAYERS; i++)
12437 int actual_player_action = stored_player[i].effective_action;
12440 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12441 - rnd_equinox_tetrachloride 048
12442 - rnd_equinox_tetrachloride_ii 096
12443 - rnd_emanuel_schmieg 002
12444 - doctor_sloan_ww 001, 020
12446 if (stored_player[i].MovPos == 0)
12447 CheckGravityMovement(&stored_player[i]);
12450 // overwrite programmed action with tape action
12451 if (stored_player[i].programmed_action)
12452 actual_player_action = stored_player[i].programmed_action;
12454 PlayerActions(&stored_player[i], actual_player_action);
12456 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12459 // single step pause mode may already have been toggled by "ScrollPlayer()"
12460 if (game.enter_single_step_mode && !tape.pausing)
12461 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12463 ScrollScreen(NULL, SCROLL_GO_ON);
12465 /* for backwards compatibility, the following code emulates a fixed bug that
12466 occured when pushing elements (causing elements that just made their last
12467 pushing step to already (if possible) make their first falling step in the
12468 same game frame, which is bad); this code is also needed to use the famous
12469 "spring push bug" which is used in older levels and might be wanted to be
12470 used also in newer levels, but in this case the buggy pushing code is only
12471 affecting the "spring" element and no other elements */
12473 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12475 for (i = 0; i < MAX_PLAYERS; i++)
12477 struct PlayerInfo *player = &stored_player[i];
12478 int x = player->jx;
12479 int y = player->jy;
12481 if (player->active && player->is_pushing && player->is_moving &&
12483 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12484 Tile[x][y] == EL_SPRING))
12486 ContinueMoving(x, y);
12488 // continue moving after pushing (this is actually a bug)
12489 if (!IS_MOVING(x, y))
12490 Stop[x][y] = FALSE;
12495 SCAN_PLAYFIELD(x, y)
12497 Last[x][y] = Tile[x][y];
12499 ChangeCount[x][y] = 0;
12500 ChangeEvent[x][y] = -1;
12502 // this must be handled before main playfield loop
12503 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12506 if (MovDelay[x][y] <= 0)
12510 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12513 if (MovDelay[x][y] <= 0)
12515 int element = Store[x][y];
12516 int move_direction = MovDir[x][y];
12517 int player_index_bit = Store2[x][y];
12523 TEST_DrawLevelField(x, y);
12525 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12527 if (IS_ENVELOPE(element))
12528 local_player->show_envelope = element;
12533 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12535 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12537 Debug("game:playing:GameActions_RND", "This should never happen!");
12539 ChangePage[x][y] = -1;
12543 Stop[x][y] = FALSE;
12544 if (WasJustMoving[x][y] > 0)
12545 WasJustMoving[x][y]--;
12546 if (WasJustFalling[x][y] > 0)
12547 WasJustFalling[x][y]--;
12548 if (CheckCollision[x][y] > 0)
12549 CheckCollision[x][y]--;
12550 if (CheckImpact[x][y] > 0)
12551 CheckImpact[x][y]--;
12555 /* reset finished pushing action (not done in ContinueMoving() to allow
12556 continuous pushing animation for elements with zero push delay) */
12557 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12559 ResetGfxAnimation(x, y);
12560 TEST_DrawLevelField(x, y);
12564 if (IS_BLOCKED(x, y))
12568 Blocked2Moving(x, y, &oldx, &oldy);
12569 if (!IS_MOVING(oldx, oldy))
12571 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12572 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12573 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12574 Debug("game:playing:GameActions_RND", "This should never happen!");
12580 HandleMouseAction(&mouse_action, &mouse_action_last);
12582 SCAN_PLAYFIELD(x, y)
12584 element = Tile[x][y];
12585 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12586 last_gfx_frame = GfxFrame[x][y];
12588 if (element == EL_EMPTY)
12589 graphic = el2img(GfxElementEmpty[x][y]);
12591 ResetGfxFrame(x, y);
12593 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12594 DrawLevelGraphicAnimation(x, y, graphic);
12596 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12597 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12598 ResetRandomAnimationValue(x, y);
12600 SetRandomAnimationValue(x, y);
12602 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12604 if (IS_INACTIVE(element))
12606 if (IS_ANIMATED(graphic))
12607 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12612 // this may take place after moving, so 'element' may have changed
12613 if (IS_CHANGING(x, y) &&
12614 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12616 int page = element_info[element].event_page_nr[CE_DELAY];
12618 HandleElementChange(x, y, page);
12620 element = Tile[x][y];
12621 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12624 CheckNextToConditions(x, y);
12626 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12630 element = Tile[x][y];
12631 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12633 if (IS_ANIMATED(graphic) &&
12634 !IS_MOVING(x, y) &&
12636 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12638 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12639 TEST_DrawTwinkleOnField(x, y);
12641 else if (element == EL_ACID)
12644 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12646 else if ((element == EL_EXIT_OPEN ||
12647 element == EL_EM_EXIT_OPEN ||
12648 element == EL_SP_EXIT_OPEN ||
12649 element == EL_STEEL_EXIT_OPEN ||
12650 element == EL_EM_STEEL_EXIT_OPEN ||
12651 element == EL_SP_TERMINAL ||
12652 element == EL_SP_TERMINAL_ACTIVE ||
12653 element == EL_EXTRA_TIME ||
12654 element == EL_SHIELD_NORMAL ||
12655 element == EL_SHIELD_DEADLY) &&
12656 IS_ANIMATED(graphic))
12657 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12658 else if (IS_MOVING(x, y))
12659 ContinueMoving(x, y);
12660 else if (IS_ACTIVE_BOMB(element))
12661 CheckDynamite(x, y);
12662 else if (element == EL_AMOEBA_GROWING)
12663 AmoebaGrowing(x, y);
12664 else if (element == EL_AMOEBA_SHRINKING)
12665 AmoebaShrinking(x, y);
12667 #if !USE_NEW_AMOEBA_CODE
12668 else if (IS_AMOEBALIVE(element))
12669 AmoebaReproduce(x, y);
12672 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12674 else if (element == EL_EXIT_CLOSED)
12676 else if (element == EL_EM_EXIT_CLOSED)
12678 else if (element == EL_STEEL_EXIT_CLOSED)
12679 CheckExitSteel(x, y);
12680 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12681 CheckExitSteelEM(x, y);
12682 else if (element == EL_SP_EXIT_CLOSED)
12684 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12685 element == EL_EXPANDABLE_STEELWALL_GROWING)
12687 else if (element == EL_EXPANDABLE_WALL ||
12688 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12689 element == EL_EXPANDABLE_WALL_VERTICAL ||
12690 element == EL_EXPANDABLE_WALL_ANY ||
12691 element == EL_BD_EXPANDABLE_WALL ||
12692 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12693 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12694 element == EL_EXPANDABLE_STEELWALL_ANY)
12695 CheckWallGrowing(x, y);
12696 else if (element == EL_FLAMES)
12697 CheckForDragon(x, y);
12698 else if (element == EL_EXPLOSION)
12699 ; // drawing of correct explosion animation is handled separately
12700 else if (element == EL_ELEMENT_SNAPPING ||
12701 element == EL_DIAGONAL_SHRINKING ||
12702 element == EL_DIAGONAL_GROWING)
12704 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]);
12706 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12708 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12709 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12711 if (IS_BELT_ACTIVE(element))
12712 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12714 if (game.magic_wall_active)
12716 int jx = local_player->jx, jy = local_player->jy;
12718 // play the element sound at the position nearest to the player
12719 if ((element == EL_MAGIC_WALL_FULL ||
12720 element == EL_MAGIC_WALL_ACTIVE ||
12721 element == EL_MAGIC_WALL_EMPTYING ||
12722 element == EL_BD_MAGIC_WALL_FULL ||
12723 element == EL_BD_MAGIC_WALL_ACTIVE ||
12724 element == EL_BD_MAGIC_WALL_EMPTYING ||
12725 element == EL_DC_MAGIC_WALL_FULL ||
12726 element == EL_DC_MAGIC_WALL_ACTIVE ||
12727 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12728 ABS(x - jx) + ABS(y - jy) <
12729 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12737 #if USE_NEW_AMOEBA_CODE
12738 // new experimental amoeba growth stuff
12739 if (!(FrameCounter % 8))
12741 static unsigned int random = 1684108901;
12743 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12745 x = RND(lev_fieldx);
12746 y = RND(lev_fieldy);
12747 element = Tile[x][y];
12749 if (!IS_PLAYER(x, y) &&
12750 (element == EL_EMPTY ||
12751 CAN_GROW_INTO(element) ||
12752 element == EL_QUICKSAND_EMPTY ||
12753 element == EL_QUICKSAND_FAST_EMPTY ||
12754 element == EL_ACID_SPLASH_LEFT ||
12755 element == EL_ACID_SPLASH_RIGHT))
12757 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12758 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12759 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12760 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12761 Tile[x][y] = EL_AMOEBA_DROP;
12764 random = random * 129 + 1;
12769 game.explosions_delayed = FALSE;
12771 SCAN_PLAYFIELD(x, y)
12773 element = Tile[x][y];
12775 if (ExplodeField[x][y])
12776 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12777 else if (element == EL_EXPLOSION)
12778 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12780 ExplodeField[x][y] = EX_TYPE_NONE;
12783 game.explosions_delayed = TRUE;
12785 if (game.magic_wall_active)
12787 if (!(game.magic_wall_time_left % 4))
12789 int element = Tile[magic_wall_x][magic_wall_y];
12791 if (element == EL_BD_MAGIC_WALL_FULL ||
12792 element == EL_BD_MAGIC_WALL_ACTIVE ||
12793 element == EL_BD_MAGIC_WALL_EMPTYING)
12794 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12795 else if (element == EL_DC_MAGIC_WALL_FULL ||
12796 element == EL_DC_MAGIC_WALL_ACTIVE ||
12797 element == EL_DC_MAGIC_WALL_EMPTYING)
12798 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12800 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12803 if (game.magic_wall_time_left > 0)
12805 game.magic_wall_time_left--;
12807 if (!game.magic_wall_time_left)
12809 SCAN_PLAYFIELD(x, y)
12811 element = Tile[x][y];
12813 if (element == EL_MAGIC_WALL_ACTIVE ||
12814 element == EL_MAGIC_WALL_FULL)
12816 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12817 TEST_DrawLevelField(x, y);
12819 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12820 element == EL_BD_MAGIC_WALL_FULL)
12822 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12823 TEST_DrawLevelField(x, y);
12825 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12826 element == EL_DC_MAGIC_WALL_FULL)
12828 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12829 TEST_DrawLevelField(x, y);
12833 game.magic_wall_active = FALSE;
12838 if (game.light_time_left > 0)
12840 game.light_time_left--;
12842 if (game.light_time_left == 0)
12843 RedrawAllLightSwitchesAndInvisibleElements();
12846 if (game.timegate_time_left > 0)
12848 game.timegate_time_left--;
12850 if (game.timegate_time_left == 0)
12851 CloseAllOpenTimegates();
12854 if (game.lenses_time_left > 0)
12856 game.lenses_time_left--;
12858 if (game.lenses_time_left == 0)
12859 RedrawAllInvisibleElementsForLenses();
12862 if (game.magnify_time_left > 0)
12864 game.magnify_time_left--;
12866 if (game.magnify_time_left == 0)
12867 RedrawAllInvisibleElementsForMagnifier();
12870 for (i = 0; i < MAX_PLAYERS; i++)
12872 struct PlayerInfo *player = &stored_player[i];
12874 if (SHIELD_ON(player))
12876 if (player->shield_deadly_time_left)
12877 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12878 else if (player->shield_normal_time_left)
12879 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12883 #if USE_DELAYED_GFX_REDRAW
12884 SCAN_PLAYFIELD(x, y)
12886 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12888 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12889 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12891 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12892 DrawLevelField(x, y);
12894 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12895 DrawLevelFieldCrumbled(x, y);
12897 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12898 DrawLevelFieldCrumbledNeighbours(x, y);
12900 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12901 DrawTwinkleOnField(x, y);
12904 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12909 PlayAllPlayersSound();
12911 for (i = 0; i < MAX_PLAYERS; i++)
12913 struct PlayerInfo *player = &stored_player[i];
12915 if (player->show_envelope != 0 && (!player->active ||
12916 player->MovPos == 0))
12918 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12920 player->show_envelope = 0;
12924 // use random number generator in every frame to make it less predictable
12925 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12928 mouse_action_last = mouse_action;
12931 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12933 int min_x = x, min_y = y, max_x = x, max_y = y;
12934 int scr_fieldx = getScreenFieldSizeX();
12935 int scr_fieldy = getScreenFieldSizeY();
12938 for (i = 0; i < MAX_PLAYERS; i++)
12940 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12942 if (!stored_player[i].active || &stored_player[i] == player)
12945 min_x = MIN(min_x, jx);
12946 min_y = MIN(min_y, jy);
12947 max_x = MAX(max_x, jx);
12948 max_y = MAX(max_y, jy);
12951 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12954 static boolean AllPlayersInVisibleScreen(void)
12958 for (i = 0; i < MAX_PLAYERS; i++)
12960 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12962 if (!stored_player[i].active)
12965 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12972 void ScrollLevel(int dx, int dy)
12974 int scroll_offset = 2 * TILEX_VAR;
12977 BlitBitmap(drawto_field, drawto_field,
12978 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12979 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12980 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12981 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12982 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12983 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12987 x = (dx == 1 ? BX1 : BX2);
12988 for (y = BY1; y <= BY2; y++)
12989 DrawScreenField(x, y);
12994 y = (dy == 1 ? BY1 : BY2);
12995 for (x = BX1; x <= BX2; x++)
12996 DrawScreenField(x, y);
12999 redraw_mask |= REDRAW_FIELD;
13002 static boolean canFallDown(struct PlayerInfo *player)
13004 int jx = player->jx, jy = player->jy;
13006 return (IN_LEV_FIELD(jx, jy + 1) &&
13007 (IS_FREE(jx, jy + 1) ||
13008 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
13009 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
13010 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
13013 static boolean canPassField(int x, int y, int move_dir)
13015 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13016 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13017 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13018 int nextx = x + dx;
13019 int nexty = y + dy;
13020 int element = Tile[x][y];
13022 return (IS_PASSABLE_FROM(element, opposite_dir) &&
13023 !CAN_MOVE(element) &&
13024 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
13025 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
13026 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
13029 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
13031 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13032 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13033 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13037 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
13038 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
13039 (IS_DIGGABLE(Tile[newx][newy]) ||
13040 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
13041 canPassField(newx, newy, move_dir)));
13044 static void CheckGravityMovement(struct PlayerInfo *player)
13046 if (player->gravity && !player->programmed_action)
13048 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
13049 int move_dir_vertical = player->effective_action & MV_VERTICAL;
13050 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
13051 int jx = player->jx, jy = player->jy;
13052 boolean player_is_moving_to_valid_field =
13053 (!player_is_snapping &&
13054 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
13055 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
13056 boolean player_can_fall_down = canFallDown(player);
13058 if (player_can_fall_down &&
13059 !player_is_moving_to_valid_field)
13060 player->programmed_action = MV_DOWN;
13064 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
13066 return CheckGravityMovement(player);
13068 if (player->gravity && !player->programmed_action)
13070 int jx = player->jx, jy = player->jy;
13071 boolean field_under_player_is_free =
13072 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
13073 boolean player_is_standing_on_valid_field =
13074 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
13075 (IS_WALKABLE(Tile[jx][jy]) &&
13076 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
13078 if (field_under_player_is_free && !player_is_standing_on_valid_field)
13079 player->programmed_action = MV_DOWN;
13084 MovePlayerOneStep()
13085 -----------------------------------------------------------------------------
13086 dx, dy: direction (non-diagonal) to try to move the player to
13087 real_dx, real_dy: direction as read from input device (can be diagonal)
13090 boolean MovePlayerOneStep(struct PlayerInfo *player,
13091 int dx, int dy, int real_dx, int real_dy)
13093 int jx = player->jx, jy = player->jy;
13094 int new_jx = jx + dx, new_jy = jy + dy;
13096 boolean player_can_move = !player->cannot_move;
13098 if (!player->active || (!dx && !dy))
13099 return MP_NO_ACTION;
13101 player->MovDir = (dx < 0 ? MV_LEFT :
13102 dx > 0 ? MV_RIGHT :
13104 dy > 0 ? MV_DOWN : MV_NONE);
13106 if (!IN_LEV_FIELD(new_jx, new_jy))
13107 return MP_NO_ACTION;
13109 if (!player_can_move)
13111 if (player->MovPos == 0)
13113 player->is_moving = FALSE;
13114 player->is_digging = FALSE;
13115 player->is_collecting = FALSE;
13116 player->is_snapping = FALSE;
13117 player->is_pushing = FALSE;
13121 if (!network.enabled && game.centered_player_nr == -1 &&
13122 !AllPlayersInSight(player, new_jx, new_jy))
13123 return MP_NO_ACTION;
13125 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG);
13126 if (can_move != MP_MOVING)
13129 // check if DigField() has caused relocation of the player
13130 if (player->jx != jx || player->jy != jy)
13131 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
13133 StorePlayer[jx][jy] = 0;
13134 player->last_jx = jx;
13135 player->last_jy = jy;
13136 player->jx = new_jx;
13137 player->jy = new_jy;
13138 StorePlayer[new_jx][new_jy] = player->element_nr;
13140 if (player->move_delay_value_next != -1)
13142 player->move_delay_value = player->move_delay_value_next;
13143 player->move_delay_value_next = -1;
13147 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
13149 player->step_counter++;
13151 PlayerVisit[jx][jy] = FrameCounter;
13153 player->is_moving = TRUE;
13156 // should better be called in MovePlayer(), but this breaks some tapes
13157 ScrollPlayer(player, SCROLL_INIT);
13163 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
13165 int jx = player->jx, jy = player->jy;
13166 int old_jx = jx, old_jy = jy;
13167 int moved = MP_NO_ACTION;
13169 if (!player->active)
13174 if (player->MovPos == 0)
13176 player->is_moving = FALSE;
13177 player->is_digging = FALSE;
13178 player->is_collecting = FALSE;
13179 player->is_snapping = FALSE;
13180 player->is_pushing = FALSE;
13186 if (player->move_delay > 0)
13189 player->move_delay = -1; // set to "uninitialized" value
13191 // store if player is automatically moved to next field
13192 player->is_auto_moving = (player->programmed_action != MV_NONE);
13194 // remove the last programmed player action
13195 player->programmed_action = 0;
13197 if (player->MovPos)
13199 // should only happen if pre-1.2 tape recordings are played
13200 // this is only for backward compatibility
13202 int original_move_delay_value = player->move_delay_value;
13205 Debug("game:playing:MovePlayer",
13206 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13210 // scroll remaining steps with finest movement resolution
13211 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13213 while (player->MovPos)
13215 ScrollPlayer(player, SCROLL_GO_ON);
13216 ScrollScreen(NULL, SCROLL_GO_ON);
13218 AdvanceFrameAndPlayerCounters(player->index_nr);
13221 BackToFront_WithFrameDelay(0);
13224 player->move_delay_value = original_move_delay_value;
13227 player->is_active = FALSE;
13229 if (player->last_move_dir & MV_HORIZONTAL)
13231 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13232 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13236 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13237 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13240 if (!moved && !player->is_active)
13242 player->is_moving = FALSE;
13243 player->is_digging = FALSE;
13244 player->is_collecting = FALSE;
13245 player->is_snapping = FALSE;
13246 player->is_pushing = FALSE;
13252 if (moved & MP_MOVING && !ScreenMovPos &&
13253 (player->index_nr == game.centered_player_nr ||
13254 game.centered_player_nr == -1))
13256 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13258 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13260 // actual player has left the screen -- scroll in that direction
13261 if (jx != old_jx) // player has moved horizontally
13262 scroll_x += (jx - old_jx);
13263 else // player has moved vertically
13264 scroll_y += (jy - old_jy);
13268 int offset_raw = game.scroll_delay_value;
13270 if (jx != old_jx) // player has moved horizontally
13272 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13273 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13274 int new_scroll_x = jx - MIDPOSX + offset_x;
13276 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13277 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13278 scroll_x = new_scroll_x;
13280 // don't scroll over playfield boundaries
13281 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13283 // don't scroll more than one field at a time
13284 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13286 // don't scroll against the player's moving direction
13287 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13288 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13289 scroll_x = old_scroll_x;
13291 else // player has moved vertically
13293 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13294 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13295 int new_scroll_y = jy - MIDPOSY + offset_y;
13297 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13298 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13299 scroll_y = new_scroll_y;
13301 // don't scroll over playfield boundaries
13302 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13304 // don't scroll more than one field at a time
13305 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13307 // don't scroll against the player's moving direction
13308 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13309 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13310 scroll_y = old_scroll_y;
13314 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13316 if (!network.enabled && game.centered_player_nr == -1 &&
13317 !AllPlayersInVisibleScreen())
13319 scroll_x = old_scroll_x;
13320 scroll_y = old_scroll_y;
13324 ScrollScreen(player, SCROLL_INIT);
13325 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13330 player->StepFrame = 0;
13332 if (moved & MP_MOVING)
13334 if (old_jx != jx && old_jy == jy)
13335 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13336 else if (old_jx == jx && old_jy != jy)
13337 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13339 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13341 player->last_move_dir = player->MovDir;
13342 player->is_moving = TRUE;
13343 player->is_snapping = FALSE;
13344 player->is_switching = FALSE;
13345 player->is_dropping = FALSE;
13346 player->is_dropping_pressed = FALSE;
13347 player->drop_pressed_delay = 0;
13350 // should better be called here than above, but this breaks some tapes
13351 ScrollPlayer(player, SCROLL_INIT);
13356 CheckGravityMovementWhenNotMoving(player);
13358 player->is_moving = FALSE;
13360 /* at this point, the player is allowed to move, but cannot move right now
13361 (e.g. because of something blocking the way) -- ensure that the player
13362 is also allowed to move in the next frame (in old versions before 3.1.1,
13363 the player was forced to wait again for eight frames before next try) */
13365 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13366 player->move_delay = 0; // allow direct movement in the next frame
13369 if (player->move_delay == -1) // not yet initialized by DigField()
13370 player->move_delay = player->move_delay_value;
13372 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13374 TestIfPlayerTouchesBadThing(jx, jy);
13375 TestIfPlayerTouchesCustomElement(jx, jy);
13378 if (!player->active)
13379 RemovePlayer(player);
13384 void ScrollPlayer(struct PlayerInfo *player, int mode)
13386 int jx = player->jx, jy = player->jy;
13387 int last_jx = player->last_jx, last_jy = player->last_jy;
13388 int move_stepsize = TILEX / player->move_delay_value;
13390 if (!player->active)
13393 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13396 if (mode == SCROLL_INIT)
13398 player->actual_frame_counter.count = FrameCounter;
13399 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13401 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13402 Tile[last_jx][last_jy] == EL_EMPTY)
13404 int last_field_block_delay = 0; // start with no blocking at all
13405 int block_delay_adjustment = player->block_delay_adjustment;
13407 // if player blocks last field, add delay for exactly one move
13408 if (player->block_last_field)
13410 last_field_block_delay += player->move_delay_value;
13412 // when blocking enabled, prevent moving up despite gravity
13413 if (player->gravity && player->MovDir == MV_UP)
13414 block_delay_adjustment = -1;
13417 // add block delay adjustment (also possible when not blocking)
13418 last_field_block_delay += block_delay_adjustment;
13420 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13421 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13424 if (player->MovPos != 0) // player has not yet reached destination
13427 else if (!FrameReached(&player->actual_frame_counter))
13430 if (player->MovPos != 0)
13432 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13433 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13435 // before DrawPlayer() to draw correct player graphic for this case
13436 if (player->MovPos == 0)
13437 CheckGravityMovement(player);
13440 if (player->MovPos == 0) // player reached destination field
13442 if (player->move_delay_reset_counter > 0)
13444 player->move_delay_reset_counter--;
13446 if (player->move_delay_reset_counter == 0)
13448 // continue with normal speed after quickly moving through gate
13449 HALVE_PLAYER_SPEED(player);
13451 // be able to make the next move without delay
13452 player->move_delay = 0;
13456 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13457 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13458 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13459 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13460 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13461 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13462 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13463 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13465 ExitPlayer(player);
13467 if (game.players_still_needed == 0 &&
13468 (game.friends_still_needed == 0 ||
13469 IS_SP_ELEMENT(Tile[jx][jy])))
13473 player->last_jx = jx;
13474 player->last_jy = jy;
13476 // this breaks one level: "machine", level 000
13478 int move_direction = player->MovDir;
13479 int enter_side = MV_DIR_OPPOSITE(move_direction);
13480 int leave_side = move_direction;
13481 int old_jx = last_jx;
13482 int old_jy = last_jy;
13483 int old_element = Tile[old_jx][old_jy];
13484 int new_element = Tile[jx][jy];
13486 if (IS_CUSTOM_ELEMENT(old_element))
13487 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13489 player->index_bit, leave_side);
13491 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13492 CE_PLAYER_LEAVES_X,
13493 player->index_bit, leave_side);
13495 // needed because pushed element has not yet reached its destination,
13496 // so it would trigger a change event at its previous field location
13497 if (!player->is_pushing)
13499 if (IS_CUSTOM_ELEMENT(new_element))
13500 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13501 player->index_bit, enter_side);
13503 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13504 CE_PLAYER_ENTERS_X,
13505 player->index_bit, enter_side);
13508 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13509 CE_MOVE_OF_X, move_direction);
13512 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13514 TestIfPlayerTouchesBadThing(jx, jy);
13515 TestIfPlayerTouchesCustomElement(jx, jy);
13517 // needed because pushed element has not yet reached its destination,
13518 // so it would trigger a change event at its previous field location
13519 if (!player->is_pushing)
13520 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13522 if (level.finish_dig_collect &&
13523 (player->is_digging || player->is_collecting))
13525 int last_element = player->last_removed_element;
13526 int move_direction = player->MovDir;
13527 int enter_side = MV_DIR_OPPOSITE(move_direction);
13528 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13529 CE_PLAYER_COLLECTS_X);
13531 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13532 player->index_bit, enter_side);
13534 player->last_removed_element = EL_UNDEFINED;
13537 if (!player->active)
13538 RemovePlayer(player);
13541 if (level.use_step_counter)
13542 CheckLevelTime_StepCounter();
13544 if (tape.single_step && tape.recording && !tape.pausing &&
13545 !player->programmed_action)
13546 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13548 if (!player->programmed_action)
13549 CheckSaveEngineSnapshot(player);
13553 void ScrollScreen(struct PlayerInfo *player, int mode)
13555 static DelayCounter screen_frame_counter = { 0 };
13557 if (mode == SCROLL_INIT)
13559 // set scrolling step size according to actual player's moving speed
13560 ScrollStepSize = TILEX / player->move_delay_value;
13562 screen_frame_counter.count = FrameCounter;
13563 screen_frame_counter.value = 1;
13565 ScreenMovDir = player->MovDir;
13566 ScreenMovPos = player->MovPos;
13567 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13570 else if (!FrameReached(&screen_frame_counter))
13575 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13576 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13577 redraw_mask |= REDRAW_FIELD;
13580 ScreenMovDir = MV_NONE;
13583 void CheckNextToConditions(int x, int y)
13585 int element = Tile[x][y];
13587 if (IS_PLAYER(x, y))
13588 TestIfPlayerNextToCustomElement(x, y);
13590 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13591 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13592 TestIfElementNextToCustomElement(x, y);
13595 void TestIfPlayerNextToCustomElement(int x, int y)
13597 struct XY *xy = xy_topdown;
13598 static int trigger_sides[4][2] =
13600 // center side border side
13601 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13602 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13603 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13604 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13608 if (!IS_PLAYER(x, y))
13611 struct PlayerInfo *player = PLAYERINFO(x, y);
13613 if (player->is_moving)
13616 for (i = 0; i < NUM_DIRECTIONS; i++)
13618 int xx = x + xy[i].x;
13619 int yy = y + xy[i].y;
13620 int border_side = trigger_sides[i][1];
13621 int border_element;
13623 if (!IN_LEV_FIELD(xx, yy))
13626 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13627 continue; // center and border element not connected
13629 border_element = Tile[xx][yy];
13631 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13632 player->index_bit, border_side);
13633 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13634 CE_PLAYER_NEXT_TO_X,
13635 player->index_bit, border_side);
13637 /* use player element that is initially defined in the level playfield,
13638 not the player element that corresponds to the runtime player number
13639 (example: a level that contains EL_PLAYER_3 as the only player would
13640 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13642 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13643 CE_NEXT_TO_X, border_side);
13647 void TestIfPlayerTouchesCustomElement(int x, int y)
13649 struct XY *xy = xy_topdown;
13650 static int trigger_sides[4][2] =
13652 // center side border side
13653 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13654 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13655 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13656 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13658 static int touch_dir[4] =
13660 MV_LEFT | MV_RIGHT,
13665 int center_element = Tile[x][y]; // should always be non-moving!
13668 for (i = 0; i < NUM_DIRECTIONS; i++)
13670 int xx = x + xy[i].x;
13671 int yy = y + xy[i].y;
13672 int center_side = trigger_sides[i][0];
13673 int border_side = trigger_sides[i][1];
13674 int border_element;
13676 if (!IN_LEV_FIELD(xx, yy))
13679 if (IS_PLAYER(x, y)) // player found at center element
13681 struct PlayerInfo *player = PLAYERINFO(x, y);
13683 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13684 border_element = Tile[xx][yy]; // may be moving!
13685 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13686 border_element = Tile[xx][yy];
13687 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13688 border_element = MovingOrBlocked2Element(xx, yy);
13690 continue; // center and border element do not touch
13692 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13693 player->index_bit, border_side);
13694 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13695 CE_PLAYER_TOUCHES_X,
13696 player->index_bit, border_side);
13699 /* use player element that is initially defined in the level playfield,
13700 not the player element that corresponds to the runtime player number
13701 (example: a level that contains EL_PLAYER_3 as the only player would
13702 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13703 int player_element = PLAYERINFO(x, y)->initial_element;
13705 // as element "X" is the player here, check opposite (center) side
13706 CheckElementChangeBySide(xx, yy, border_element, player_element,
13707 CE_TOUCHING_X, center_side);
13710 else if (IS_PLAYER(xx, yy)) // player found at border element
13712 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13714 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13716 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13717 continue; // center and border element do not touch
13720 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13721 player->index_bit, center_side);
13722 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13723 CE_PLAYER_TOUCHES_X,
13724 player->index_bit, center_side);
13727 /* use player element that is initially defined in the level playfield,
13728 not the player element that corresponds to the runtime player number
13729 (example: a level that contains EL_PLAYER_3 as the only player would
13730 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13731 int player_element = PLAYERINFO(xx, yy)->initial_element;
13733 // as element "X" is the player here, check opposite (border) side
13734 CheckElementChangeBySide(x, y, center_element, player_element,
13735 CE_TOUCHING_X, border_side);
13743 void TestIfElementNextToCustomElement(int x, int y)
13745 struct XY *xy = xy_topdown;
13746 static int trigger_sides[4][2] =
13748 // center side border side
13749 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13750 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13751 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13752 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13754 int center_element = Tile[x][y]; // should always be non-moving!
13757 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13760 for (i = 0; i < NUM_DIRECTIONS; i++)
13762 int xx = x + xy[i].x;
13763 int yy = y + xy[i].y;
13764 int border_side = trigger_sides[i][1];
13765 int border_element;
13767 if (!IN_LEV_FIELD(xx, yy))
13770 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13771 continue; // center and border element not connected
13773 border_element = Tile[xx][yy];
13775 // check for change of center element (but change it only once)
13776 if (CheckElementChangeBySide(x, y, center_element, border_element,
13777 CE_NEXT_TO_X, border_side))
13782 void TestIfElementTouchesCustomElement(int x, int y)
13784 struct XY *xy = xy_topdown;
13785 static int trigger_sides[4][2] =
13787 // center side border side
13788 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13789 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13790 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13791 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13793 static int touch_dir[4] =
13795 MV_LEFT | MV_RIGHT,
13800 boolean change_center_element = FALSE;
13801 int center_element = Tile[x][y]; // should always be non-moving!
13802 int border_element_old[NUM_DIRECTIONS];
13805 for (i = 0; i < NUM_DIRECTIONS; i++)
13807 int xx = x + xy[i].x;
13808 int yy = y + xy[i].y;
13809 int border_element;
13811 border_element_old[i] = -1;
13813 if (!IN_LEV_FIELD(xx, yy))
13816 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13817 border_element = Tile[xx][yy]; // may be moving!
13818 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13819 border_element = Tile[xx][yy];
13820 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13821 border_element = MovingOrBlocked2Element(xx, yy);
13823 continue; // center and border element do not touch
13825 border_element_old[i] = border_element;
13828 for (i = 0; i < NUM_DIRECTIONS; i++)
13830 int xx = x + xy[i].x;
13831 int yy = y + xy[i].y;
13832 int center_side = trigger_sides[i][0];
13833 int border_element = border_element_old[i];
13835 if (border_element == -1)
13838 // check for change of border element
13839 CheckElementChangeBySide(xx, yy, border_element, center_element,
13840 CE_TOUCHING_X, center_side);
13842 // (center element cannot be player, so we don't have to check this here)
13845 for (i = 0; i < NUM_DIRECTIONS; i++)
13847 int xx = x + xy[i].x;
13848 int yy = y + xy[i].y;
13849 int border_side = trigger_sides[i][1];
13850 int border_element = border_element_old[i];
13852 if (border_element == -1)
13855 // check for change of center element (but change it only once)
13856 if (!change_center_element)
13857 change_center_element =
13858 CheckElementChangeBySide(x, y, center_element, border_element,
13859 CE_TOUCHING_X, border_side);
13861 if (IS_PLAYER(xx, yy))
13863 /* use player element that is initially defined in the level playfield,
13864 not the player element that corresponds to the runtime player number
13865 (example: a level that contains EL_PLAYER_3 as the only player would
13866 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13867 int player_element = PLAYERINFO(xx, yy)->initial_element;
13869 // as element "X" is the player here, check opposite (border) side
13870 CheckElementChangeBySide(x, y, center_element, player_element,
13871 CE_TOUCHING_X, border_side);
13876 void TestIfElementHitsCustomElement(int x, int y, int direction)
13878 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13879 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13880 int hitx = x + dx, hity = y + dy;
13881 int hitting_element = Tile[x][y];
13882 int touched_element;
13884 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13887 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13888 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13890 if (IN_LEV_FIELD(hitx, hity))
13892 int opposite_direction = MV_DIR_OPPOSITE(direction);
13893 int hitting_side = direction;
13894 int touched_side = opposite_direction;
13895 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13896 MovDir[hitx][hity] != direction ||
13897 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13903 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13904 CE_HITTING_X, touched_side);
13906 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13907 CE_HIT_BY_X, hitting_side);
13909 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13910 CE_HIT_BY_SOMETHING, opposite_direction);
13912 if (IS_PLAYER(hitx, hity))
13914 /* use player element that is initially defined in the level playfield,
13915 not the player element that corresponds to the runtime player number
13916 (example: a level that contains EL_PLAYER_3 as the only player would
13917 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13918 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13920 CheckElementChangeBySide(x, y, hitting_element, player_element,
13921 CE_HITTING_X, touched_side);
13926 // "hitting something" is also true when hitting the playfield border
13927 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13928 CE_HITTING_SOMETHING, direction);
13931 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13933 int i, kill_x = -1, kill_y = -1;
13935 int bad_element = -1;
13936 struct XY *test_xy = xy_topdown;
13937 static int test_dir[4] =
13945 for (i = 0; i < NUM_DIRECTIONS; i++)
13947 int test_x, test_y, test_move_dir, test_element;
13949 test_x = good_x + test_xy[i].x;
13950 test_y = good_y + test_xy[i].y;
13952 if (!IN_LEV_FIELD(test_x, test_y))
13956 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13958 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13960 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13961 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13963 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13964 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13968 bad_element = test_element;
13974 if (kill_x != -1 || kill_y != -1)
13976 if (IS_PLAYER(good_x, good_y))
13978 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13980 if (player->shield_deadly_time_left > 0 &&
13981 !IS_INDESTRUCTIBLE(bad_element))
13982 Bang(kill_x, kill_y);
13983 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13984 KillPlayer(player);
13987 Bang(good_x, good_y);
13991 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13993 int i, kill_x = -1, kill_y = -1;
13994 int bad_element = Tile[bad_x][bad_y];
13995 struct XY *test_xy = xy_topdown;
13996 static int touch_dir[4] =
13998 MV_LEFT | MV_RIGHT,
14003 static int test_dir[4] =
14011 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
14014 for (i = 0; i < NUM_DIRECTIONS; i++)
14016 int test_x, test_y, test_move_dir, test_element;
14018 test_x = bad_x + test_xy[i].x;
14019 test_y = bad_y + test_xy[i].y;
14021 if (!IN_LEV_FIELD(test_x, test_y))
14025 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14027 test_element = Tile[test_x][test_y];
14029 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
14030 2nd case: DONT_TOUCH style bad thing does not move away from good thing
14032 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
14033 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
14035 // good thing is player or penguin that does not move away
14036 if (IS_PLAYER(test_x, test_y))
14038 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14040 if (bad_element == EL_ROBOT && player->is_moving)
14041 continue; // robot does not kill player if he is moving
14043 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
14045 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
14046 continue; // center and border element do not touch
14054 else if (test_element == EL_PENGUIN)
14064 if (kill_x != -1 || kill_y != -1)
14066 if (IS_PLAYER(kill_x, kill_y))
14068 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14070 if (player->shield_deadly_time_left > 0 &&
14071 !IS_INDESTRUCTIBLE(bad_element))
14072 Bang(bad_x, bad_y);
14073 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14074 KillPlayer(player);
14077 Bang(kill_x, kill_y);
14081 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
14083 int bad_element = Tile[bad_x][bad_y];
14084 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
14085 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
14086 int test_x = bad_x + dx, test_y = bad_y + dy;
14087 int test_move_dir, test_element;
14088 int kill_x = -1, kill_y = -1;
14090 if (!IN_LEV_FIELD(test_x, test_y))
14094 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14096 test_element = Tile[test_x][test_y];
14098 if (test_move_dir != bad_move_dir)
14100 // good thing can be player or penguin that does not move away
14101 if (IS_PLAYER(test_x, test_y))
14103 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14105 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
14106 player as being hit when he is moving towards the bad thing, because
14107 the "get hit by" condition would be lost after the player stops) */
14108 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
14109 return; // player moves away from bad thing
14114 else if (test_element == EL_PENGUIN)
14121 if (kill_x != -1 || kill_y != -1)
14123 if (IS_PLAYER(kill_x, kill_y))
14125 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14127 if (player->shield_deadly_time_left > 0 &&
14128 !IS_INDESTRUCTIBLE(bad_element))
14129 Bang(bad_x, bad_y);
14130 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14131 KillPlayer(player);
14134 Bang(kill_x, kill_y);
14138 void TestIfPlayerTouchesBadThing(int x, int y)
14140 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14143 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
14145 TestIfGoodThingHitsBadThing(x, y, move_dir);
14148 void TestIfBadThingTouchesPlayer(int x, int y)
14150 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14153 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
14155 TestIfBadThingHitsGoodThing(x, y, move_dir);
14158 void TestIfFriendTouchesBadThing(int x, int y)
14160 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14163 void TestIfBadThingTouchesFriend(int x, int y)
14165 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14168 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
14170 int i, kill_x = bad_x, kill_y = bad_y;
14171 struct XY *xy = xy_topdown;
14173 for (i = 0; i < NUM_DIRECTIONS; i++)
14177 x = bad_x + xy[i].x;
14178 y = bad_y + xy[i].y;
14179 if (!IN_LEV_FIELD(x, y))
14182 element = Tile[x][y];
14183 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14184 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14192 if (kill_x != bad_x || kill_y != bad_y)
14193 Bang(bad_x, bad_y);
14196 void KillPlayer(struct PlayerInfo *player)
14198 int jx = player->jx, jy = player->jy;
14200 if (!player->active)
14204 Debug("game:playing:KillPlayer",
14205 "0: killed == %d, active == %d, reanimated == %d",
14206 player->killed, player->active, player->reanimated);
14209 /* the following code was introduced to prevent an infinite loop when calling
14211 -> CheckTriggeredElementChangeExt()
14212 -> ExecuteCustomElementAction()
14214 -> (infinitely repeating the above sequence of function calls)
14215 which occurs when killing the player while having a CE with the setting
14216 "kill player X when explosion of <player X>"; the solution using a new
14217 field "player->killed" was chosen for backwards compatibility, although
14218 clever use of the fields "player->active" etc. would probably also work */
14220 if (player->killed)
14224 player->killed = TRUE;
14226 // remove accessible field at the player's position
14227 RemoveField(jx, jy);
14229 // deactivate shield (else Bang()/Explode() would not work right)
14230 player->shield_normal_time_left = 0;
14231 player->shield_deadly_time_left = 0;
14234 Debug("game:playing:KillPlayer",
14235 "1: killed == %d, active == %d, reanimated == %d",
14236 player->killed, player->active, player->reanimated);
14242 Debug("game:playing:KillPlayer",
14243 "2: killed == %d, active == %d, reanimated == %d",
14244 player->killed, player->active, player->reanimated);
14247 if (player->reanimated) // killed player may have been reanimated
14248 player->killed = player->reanimated = FALSE;
14250 BuryPlayer(player);
14253 static void KillPlayerUnlessEnemyProtected(int x, int y)
14255 if (!PLAYER_ENEMY_PROTECTED(x, y))
14256 KillPlayer(PLAYERINFO(x, y));
14259 static void KillPlayerUnlessExplosionProtected(int x, int y)
14261 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14262 KillPlayer(PLAYERINFO(x, y));
14265 void BuryPlayer(struct PlayerInfo *player)
14267 int jx = player->jx, jy = player->jy;
14269 if (!player->active)
14272 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14274 RemovePlayer(player);
14276 player->buried = TRUE;
14278 if (game.all_players_gone)
14279 game.GameOver = TRUE;
14282 void RemovePlayer(struct PlayerInfo *player)
14284 int jx = player->jx, jy = player->jy;
14285 int i, found = FALSE;
14287 player->present = FALSE;
14288 player->active = FALSE;
14290 // required for some CE actions (even if the player is not active anymore)
14291 player->MovPos = 0;
14293 if (!ExplodeField[jx][jy])
14294 StorePlayer[jx][jy] = 0;
14296 if (player->is_moving)
14297 TEST_DrawLevelField(player->last_jx, player->last_jy);
14299 for (i = 0; i < MAX_PLAYERS; i++)
14300 if (stored_player[i].active)
14305 game.all_players_gone = TRUE;
14306 game.GameOver = TRUE;
14309 game.exit_x = game.robot_wheel_x = jx;
14310 game.exit_y = game.robot_wheel_y = jy;
14313 void ExitPlayer(struct PlayerInfo *player)
14315 DrawPlayer(player); // needed here only to cleanup last field
14316 RemovePlayer(player);
14318 if (game.players_still_needed > 0)
14319 game.players_still_needed--;
14322 static void SetFieldForSnapping(int x, int y, int element, int direction,
14323 int player_index_bit)
14325 struct ElementInfo *ei = &element_info[element];
14326 int direction_bit = MV_DIR_TO_BIT(direction);
14327 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14328 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14329 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14331 Tile[x][y] = EL_ELEMENT_SNAPPING;
14332 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14333 MovDir[x][y] = direction;
14334 Store[x][y] = element;
14335 Store2[x][y] = player_index_bit;
14337 ResetGfxAnimation(x, y);
14339 GfxElement[x][y] = element;
14340 GfxAction[x][y] = action;
14341 GfxDir[x][y] = direction;
14342 GfxFrame[x][y] = -1;
14345 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14346 int player_index_bit)
14348 TestIfElementTouchesCustomElement(x, y); // for empty space
14350 if (level.finish_dig_collect)
14352 int dig_side = MV_DIR_OPPOSITE(direction);
14353 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14354 CE_PLAYER_COLLECTS_X);
14356 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14357 player_index_bit, dig_side);
14358 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14359 player_index_bit, dig_side);
14364 =============================================================================
14365 checkDiagonalPushing()
14366 -----------------------------------------------------------------------------
14367 check if diagonal input device direction results in pushing of object
14368 (by checking if the alternative direction is walkable, diggable, ...)
14369 =============================================================================
14372 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14373 int x, int y, int real_dx, int real_dy)
14375 int jx, jy, dx, dy, xx, yy;
14377 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14380 // diagonal direction: check alternative direction
14385 xx = jx + (dx == 0 ? real_dx : 0);
14386 yy = jy + (dy == 0 ? real_dy : 0);
14388 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14392 =============================================================================
14394 -----------------------------------------------------------------------------
14395 x, y: field next to player (non-diagonal) to try to dig to
14396 real_dx, real_dy: direction as read from input device (can be diagonal)
14397 =============================================================================
14400 static int DigField(struct PlayerInfo *player,
14401 int oldx, int oldy, int x, int y,
14402 int real_dx, int real_dy, int mode)
14404 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14405 boolean player_was_pushing = player->is_pushing;
14406 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14407 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14408 int jx = oldx, jy = oldy;
14409 int dx = x - jx, dy = y - jy;
14410 int nextx = x + dx, nexty = y + dy;
14411 int move_direction = (dx == -1 ? MV_LEFT :
14412 dx == +1 ? MV_RIGHT :
14414 dy == +1 ? MV_DOWN : MV_NONE);
14415 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14416 int dig_side = MV_DIR_OPPOSITE(move_direction);
14417 int old_element = Tile[jx][jy];
14418 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14421 if (is_player) // function can also be called by EL_PENGUIN
14423 if (player->MovPos == 0)
14425 player->is_digging = FALSE;
14426 player->is_collecting = FALSE;
14429 if (player->MovPos == 0) // last pushing move finished
14430 player->is_pushing = FALSE;
14432 if (mode == DF_NO_PUSH) // player just stopped pushing
14434 player->is_switching = FALSE;
14435 player->push_delay = -1;
14437 return MP_NO_ACTION;
14440 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14441 old_element = Back[jx][jy];
14443 // in case of element dropped at player position, check background
14444 else if (Back[jx][jy] != EL_EMPTY &&
14445 game.engine_version >= VERSION_IDENT(2,2,0,0))
14446 old_element = Back[jx][jy];
14448 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14449 return MP_NO_ACTION; // field has no opening in this direction
14451 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction))
14452 return MP_NO_ACTION; // field has no opening in this direction
14454 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14458 Tile[jx][jy] = player->artwork_element;
14459 InitMovingField(jx, jy, MV_DOWN);
14460 Store[jx][jy] = EL_ACID;
14461 ContinueMoving(jx, jy);
14462 BuryPlayer(player);
14464 return MP_DONT_RUN_INTO;
14467 if (player_can_move && DONT_RUN_INTO(element))
14469 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14471 return MP_DONT_RUN_INTO;
14474 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14475 return MP_NO_ACTION;
14477 collect_count = element_info[element].collect_count_initial;
14479 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14480 return MP_NO_ACTION;
14482 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14483 player_can_move = player_can_move_or_snap;
14485 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14486 game.engine_version >= VERSION_IDENT(2,2,0,0))
14488 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14489 player->index_bit, dig_side);
14490 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14491 player->index_bit, dig_side);
14493 if (element == EL_DC_LANDMINE)
14496 if (Tile[x][y] != element) // field changed by snapping
14499 return MP_NO_ACTION;
14502 if (player->gravity && is_player && !player->is_auto_moving &&
14503 canFallDown(player) && move_direction != MV_DOWN &&
14504 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14505 return MP_NO_ACTION; // player cannot walk here due to gravity
14507 if (player_can_move &&
14508 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14510 int sound_element = SND_ELEMENT(element);
14511 int sound_action = ACTION_WALKING;
14513 if (IS_RND_GATE(element))
14515 if (!player->key[RND_GATE_NR(element)])
14516 return MP_NO_ACTION;
14518 else if (IS_RND_GATE_GRAY(element))
14520 if (!player->key[RND_GATE_GRAY_NR(element)])
14521 return MP_NO_ACTION;
14523 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14525 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14526 return MP_NO_ACTION;
14528 else if (element == EL_EXIT_OPEN ||
14529 element == EL_EM_EXIT_OPEN ||
14530 element == EL_EM_EXIT_OPENING ||
14531 element == EL_STEEL_EXIT_OPEN ||
14532 element == EL_EM_STEEL_EXIT_OPEN ||
14533 element == EL_EM_STEEL_EXIT_OPENING ||
14534 element == EL_SP_EXIT_OPEN ||
14535 element == EL_SP_EXIT_OPENING)
14537 sound_action = ACTION_PASSING; // player is passing exit
14539 else if (element == EL_EMPTY)
14541 sound_action = ACTION_MOVING; // nothing to walk on
14544 // play sound from background or player, whatever is available
14545 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14546 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14548 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14550 else if (player_can_move &&
14551 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14553 if (!ACCESS_FROM(element, opposite_direction))
14554 return MP_NO_ACTION; // field not accessible from this direction
14556 if (CAN_MOVE(element)) // only fixed elements can be passed!
14557 return MP_NO_ACTION;
14559 if (IS_EM_GATE(element))
14561 if (!player->key[EM_GATE_NR(element)])
14562 return MP_NO_ACTION;
14564 else if (IS_EM_GATE_GRAY(element))
14566 if (!player->key[EM_GATE_GRAY_NR(element)])
14567 return MP_NO_ACTION;
14569 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14571 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14572 return MP_NO_ACTION;
14574 else if (IS_EMC_GATE(element))
14576 if (!player->key[EMC_GATE_NR(element)])
14577 return MP_NO_ACTION;
14579 else if (IS_EMC_GATE_GRAY(element))
14581 if (!player->key[EMC_GATE_GRAY_NR(element)])
14582 return MP_NO_ACTION;
14584 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14586 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14587 return MP_NO_ACTION;
14589 else if (element == EL_DC_GATE_WHITE ||
14590 element == EL_DC_GATE_WHITE_GRAY ||
14591 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14593 if (player->num_white_keys == 0)
14594 return MP_NO_ACTION;
14596 player->num_white_keys--;
14598 else if (IS_SP_PORT(element))
14600 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14601 element == EL_SP_GRAVITY_PORT_RIGHT ||
14602 element == EL_SP_GRAVITY_PORT_UP ||
14603 element == EL_SP_GRAVITY_PORT_DOWN)
14604 player->gravity = !player->gravity;
14605 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14606 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14607 element == EL_SP_GRAVITY_ON_PORT_UP ||
14608 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14609 player->gravity = TRUE;
14610 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14611 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14612 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14613 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14614 player->gravity = FALSE;
14617 // automatically move to the next field with double speed
14618 player->programmed_action = move_direction;
14620 if (player->move_delay_reset_counter == 0)
14622 player->move_delay_reset_counter = 2; // two double speed steps
14624 DOUBLE_PLAYER_SPEED(player);
14627 PlayLevelSoundAction(x, y, ACTION_PASSING);
14629 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14633 if (mode != DF_SNAP)
14635 GfxElement[x][y] = GFX_ELEMENT(element);
14636 player->is_digging = TRUE;
14639 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14641 // use old behaviour for old levels (digging)
14642 if (!level.finish_dig_collect)
14644 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14645 player->index_bit, dig_side);
14647 // if digging triggered player relocation, finish digging tile
14648 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14649 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14652 if (mode == DF_SNAP)
14654 if (level.block_snap_field)
14655 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14657 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14659 // use old behaviour for old levels (snapping)
14660 if (!level.finish_dig_collect)
14661 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14662 player->index_bit, dig_side);
14665 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14669 if (is_player && mode != DF_SNAP)
14671 GfxElement[x][y] = element;
14672 player->is_collecting = TRUE;
14675 if (element == EL_SPEED_PILL)
14677 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14679 else if (element == EL_EXTRA_TIME && level.time > 0)
14681 TimeLeft += level.extra_time;
14683 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14685 DisplayGameControlValues();
14687 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14689 int shield_time = (element == EL_SHIELD_DEADLY ?
14690 level.shield_deadly_time :
14691 level.shield_normal_time);
14693 player->shield_normal_time_left += shield_time;
14694 if (element == EL_SHIELD_DEADLY)
14695 player->shield_deadly_time_left += shield_time;
14697 else if (element == EL_DYNAMITE ||
14698 element == EL_EM_DYNAMITE ||
14699 element == EL_SP_DISK_RED)
14701 if (player->inventory_size < MAX_INVENTORY_SIZE)
14702 player->inventory_element[player->inventory_size++] = element;
14704 DrawGameDoorValues();
14706 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14708 player->dynabomb_count++;
14709 player->dynabombs_left++;
14711 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14713 player->dynabomb_size++;
14715 else if (element == EL_DYNABOMB_INCREASE_POWER)
14717 player->dynabomb_xl = TRUE;
14719 else if (IS_KEY(element))
14721 player->key[KEY_NR(element)] = TRUE;
14723 DrawGameDoorValues();
14725 else if (element == EL_DC_KEY_WHITE)
14727 player->num_white_keys++;
14729 // display white keys?
14730 // DrawGameDoorValues();
14732 else if (IS_ENVELOPE(element))
14734 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14736 if (!wait_for_snapping)
14737 player->show_envelope = element;
14739 else if (element == EL_EMC_LENSES)
14741 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14743 RedrawAllInvisibleElementsForLenses();
14745 else if (element == EL_EMC_MAGNIFIER)
14747 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14749 RedrawAllInvisibleElementsForMagnifier();
14751 else if (IS_DROPPABLE(element) ||
14752 IS_THROWABLE(element)) // can be collected and dropped
14756 if (collect_count == 0)
14757 player->inventory_infinite_element = element;
14759 for (i = 0; i < collect_count; i++)
14760 if (player->inventory_size < MAX_INVENTORY_SIZE)
14761 player->inventory_element[player->inventory_size++] = element;
14763 DrawGameDoorValues();
14765 else if (collect_count > 0)
14767 game.gems_still_needed -= collect_count;
14768 if (game.gems_still_needed < 0)
14769 game.gems_still_needed = 0;
14771 game.snapshot.collected_item = TRUE;
14773 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14775 DisplayGameControlValues();
14778 RaiseScoreElement(element);
14779 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14781 // use old behaviour for old levels (collecting)
14782 if (!level.finish_dig_collect && is_player)
14784 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14785 player->index_bit, dig_side);
14787 // if collecting triggered player relocation, finish collecting tile
14788 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14789 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14792 if (mode == DF_SNAP)
14794 if (level.block_snap_field)
14795 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14797 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14799 // use old behaviour for old levels (snapping)
14800 if (!level.finish_dig_collect)
14801 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14802 player->index_bit, dig_side);
14805 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14807 if (mode == DF_SNAP && element != EL_BD_ROCK)
14808 return MP_NO_ACTION;
14810 if (CAN_FALL(element) && dy)
14811 return MP_NO_ACTION;
14813 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14814 !(element == EL_SPRING && level.use_spring_bug))
14815 return MP_NO_ACTION;
14817 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14818 ((move_direction & MV_VERTICAL &&
14819 ((element_info[element].move_pattern & MV_LEFT &&
14820 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14821 (element_info[element].move_pattern & MV_RIGHT &&
14822 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14823 (move_direction & MV_HORIZONTAL &&
14824 ((element_info[element].move_pattern & MV_UP &&
14825 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14826 (element_info[element].move_pattern & MV_DOWN &&
14827 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14828 return MP_NO_ACTION;
14830 // do not push elements already moving away faster than player
14831 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14832 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14833 return MP_NO_ACTION;
14835 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14837 if (player->push_delay_value == -1 || !player_was_pushing)
14838 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14840 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14842 if (player->push_delay_value == -1)
14843 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14845 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14847 if (!player->is_pushing)
14848 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14851 player->is_pushing = TRUE;
14852 player->is_active = TRUE;
14854 if (!(IN_LEV_FIELD(nextx, nexty) &&
14855 (IS_FREE(nextx, nexty) ||
14856 (IS_SB_ELEMENT(element) &&
14857 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14858 (IS_CUSTOM_ELEMENT(element) &&
14859 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14860 return MP_NO_ACTION;
14862 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14863 return MP_NO_ACTION;
14865 if (player->push_delay == -1) // new pushing; restart delay
14866 player->push_delay = 0;
14868 if (player->push_delay < player->push_delay_value &&
14869 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14870 element != EL_SPRING && element != EL_BALLOON)
14872 // make sure that there is no move delay before next try to push
14873 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14874 player->move_delay = 0;
14876 return MP_NO_ACTION;
14879 if (IS_CUSTOM_ELEMENT(element) &&
14880 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14882 if (!DigFieldByCE(nextx, nexty, element))
14883 return MP_NO_ACTION;
14886 if (IS_SB_ELEMENT(element))
14888 boolean sokoban_task_solved = FALSE;
14890 if (element == EL_SOKOBAN_FIELD_FULL)
14892 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14894 IncrementSokobanFieldsNeeded();
14895 IncrementSokobanObjectsNeeded();
14898 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14900 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14902 DecrementSokobanFieldsNeeded();
14903 DecrementSokobanObjectsNeeded();
14905 // sokoban object was pushed from empty field to sokoban field
14906 if (Back[x][y] == EL_EMPTY)
14907 sokoban_task_solved = TRUE;
14910 Tile[x][y] = EL_SOKOBAN_OBJECT;
14912 if (Back[x][y] == Back[nextx][nexty])
14913 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14914 else if (Back[x][y] != 0)
14915 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14918 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14921 if (sokoban_task_solved &&
14922 game.sokoban_fields_still_needed == 0 &&
14923 game.sokoban_objects_still_needed == 0 &&
14924 level.auto_exit_sokoban)
14926 game.players_still_needed = 0;
14930 PlaySound(SND_GAME_SOKOBAN_SOLVING);
14934 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14936 InitMovingField(x, y, move_direction);
14937 GfxAction[x][y] = ACTION_PUSHING;
14939 if (mode == DF_SNAP)
14940 ContinueMoving(x, y);
14942 MovPos[x][y] = (dx != 0 ? dx : dy);
14944 Pushed[x][y] = TRUE;
14945 Pushed[nextx][nexty] = TRUE;
14947 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14948 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14950 player->push_delay_value = -1; // get new value later
14952 // check for element change _after_ element has been pushed
14953 if (game.use_change_when_pushing_bug)
14955 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14956 player->index_bit, dig_side);
14957 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14958 player->index_bit, dig_side);
14961 else if (IS_SWITCHABLE(element))
14963 if (PLAYER_SWITCHING(player, x, y))
14965 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14966 player->index_bit, dig_side);
14971 player->is_switching = TRUE;
14972 player->switch_x = x;
14973 player->switch_y = y;
14975 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14977 if (element == EL_ROBOT_WHEEL)
14979 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14981 game.robot_wheel_x = x;
14982 game.robot_wheel_y = y;
14983 game.robot_wheel_active = TRUE;
14985 TEST_DrawLevelField(x, y);
14987 else if (element == EL_SP_TERMINAL)
14991 SCAN_PLAYFIELD(xx, yy)
14993 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14997 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14999 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
15001 ResetGfxAnimation(xx, yy);
15002 TEST_DrawLevelField(xx, yy);
15006 else if (IS_BELT_SWITCH(element))
15008 ToggleBeltSwitch(x, y);
15010 else if (element == EL_SWITCHGATE_SWITCH_UP ||
15011 element == EL_SWITCHGATE_SWITCH_DOWN ||
15012 element == EL_DC_SWITCHGATE_SWITCH_UP ||
15013 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
15015 ToggleSwitchgateSwitch();
15017 else if (element == EL_LIGHT_SWITCH ||
15018 element == EL_LIGHT_SWITCH_ACTIVE)
15020 ToggleLightSwitch(x, y);
15022 else if (element == EL_TIMEGATE_SWITCH ||
15023 element == EL_DC_TIMEGATE_SWITCH)
15025 ActivateTimegateSwitch(x, y);
15027 else if (element == EL_BALLOON_SWITCH_LEFT ||
15028 element == EL_BALLOON_SWITCH_RIGHT ||
15029 element == EL_BALLOON_SWITCH_UP ||
15030 element == EL_BALLOON_SWITCH_DOWN ||
15031 element == EL_BALLOON_SWITCH_NONE ||
15032 element == EL_BALLOON_SWITCH_ANY)
15034 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
15035 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
15036 element == EL_BALLOON_SWITCH_UP ? MV_UP :
15037 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
15038 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
15041 else if (element == EL_LAMP)
15043 Tile[x][y] = EL_LAMP_ACTIVE;
15044 game.lights_still_needed--;
15046 ResetGfxAnimation(x, y);
15047 TEST_DrawLevelField(x, y);
15049 else if (element == EL_TIME_ORB_FULL)
15051 Tile[x][y] = EL_TIME_ORB_EMPTY;
15053 if (level.time > 0 || level.use_time_orb_bug)
15055 TimeLeft += level.time_orb_time;
15056 game.no_level_time_limit = FALSE;
15058 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
15060 DisplayGameControlValues();
15063 ResetGfxAnimation(x, y);
15064 TEST_DrawLevelField(x, y);
15066 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
15067 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15071 game.ball_active = !game.ball_active;
15073 SCAN_PLAYFIELD(xx, yy)
15075 int e = Tile[xx][yy];
15077 if (game.ball_active)
15079 if (e == EL_EMC_MAGIC_BALL)
15080 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
15081 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
15082 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
15086 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
15087 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
15088 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15089 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
15094 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15095 player->index_bit, dig_side);
15097 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15098 player->index_bit, dig_side);
15100 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15101 player->index_bit, dig_side);
15107 if (!PLAYER_SWITCHING(player, x, y))
15109 player->is_switching = TRUE;
15110 player->switch_x = x;
15111 player->switch_y = y;
15113 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
15114 player->index_bit, dig_side);
15115 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15116 player->index_bit, dig_side);
15118 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
15119 player->index_bit, dig_side);
15120 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15121 player->index_bit, dig_side);
15124 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
15125 player->index_bit, dig_side);
15126 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15127 player->index_bit, dig_side);
15129 return MP_NO_ACTION;
15132 player->push_delay = -1;
15134 if (is_player) // function can also be called by EL_PENGUIN
15136 if (Tile[x][y] != element) // really digged/collected something
15138 player->is_collecting = !player->is_digging;
15139 player->is_active = TRUE;
15141 player->last_removed_element = element;
15148 static boolean DigFieldByCE(int x, int y, int digging_element)
15150 int element = Tile[x][y];
15152 if (!IS_FREE(x, y))
15154 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
15155 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
15158 // no element can dig solid indestructible elements
15159 if (IS_INDESTRUCTIBLE(element) &&
15160 !IS_DIGGABLE(element) &&
15161 !IS_COLLECTIBLE(element))
15164 if (AmoebaNr[x][y] &&
15165 (element == EL_AMOEBA_FULL ||
15166 element == EL_BD_AMOEBA ||
15167 element == EL_AMOEBA_GROWING))
15169 AmoebaCnt[AmoebaNr[x][y]]--;
15170 AmoebaCnt2[AmoebaNr[x][y]]--;
15173 if (IS_MOVING(x, y))
15174 RemoveMovingField(x, y);
15178 TEST_DrawLevelField(x, y);
15181 // if digged element was about to explode, prevent the explosion
15182 ExplodeField[x][y] = EX_TYPE_NONE;
15184 PlayLevelSoundAction(x, y, action);
15187 Store[x][y] = EL_EMPTY;
15189 // this makes it possible to leave the removed element again
15190 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15191 Store[x][y] = element;
15196 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15198 int jx = player->jx, jy = player->jy;
15199 int x = jx + dx, y = jy + dy;
15200 int snap_direction = (dx == -1 ? MV_LEFT :
15201 dx == +1 ? MV_RIGHT :
15203 dy == +1 ? MV_DOWN : MV_NONE);
15204 boolean can_continue_snapping = (level.continuous_snapping &&
15205 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15207 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15210 if (!player->active || !IN_LEV_FIELD(x, y))
15218 if (player->MovPos == 0)
15219 player->is_pushing = FALSE;
15221 player->is_snapping = FALSE;
15223 if (player->MovPos == 0)
15225 player->is_moving = FALSE;
15226 player->is_digging = FALSE;
15227 player->is_collecting = FALSE;
15233 // prevent snapping with already pressed snap key when not allowed
15234 if (player->is_snapping && !can_continue_snapping)
15237 player->MovDir = snap_direction;
15239 if (player->MovPos == 0)
15241 player->is_moving = FALSE;
15242 player->is_digging = FALSE;
15243 player->is_collecting = FALSE;
15246 player->is_dropping = FALSE;
15247 player->is_dropping_pressed = FALSE;
15248 player->drop_pressed_delay = 0;
15250 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15253 player->is_snapping = TRUE;
15254 player->is_active = TRUE;
15256 if (player->MovPos == 0)
15258 player->is_moving = FALSE;
15259 player->is_digging = FALSE;
15260 player->is_collecting = FALSE;
15263 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15264 TEST_DrawLevelField(player->last_jx, player->last_jy);
15266 TEST_DrawLevelField(x, y);
15271 static boolean DropElement(struct PlayerInfo *player)
15273 int old_element, new_element;
15274 int dropx = player->jx, dropy = player->jy;
15275 int drop_direction = player->MovDir;
15276 int drop_side = drop_direction;
15277 int drop_element = get_next_dropped_element(player);
15279 /* do not drop an element on top of another element; when holding drop key
15280 pressed without moving, dropped element must move away before the next
15281 element can be dropped (this is especially important if the next element
15282 is dynamite, which can be placed on background for historical reasons) */
15283 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15286 if (IS_THROWABLE(drop_element))
15288 dropx += GET_DX_FROM_DIR(drop_direction);
15289 dropy += GET_DY_FROM_DIR(drop_direction);
15291 if (!IN_LEV_FIELD(dropx, dropy))
15295 old_element = Tile[dropx][dropy]; // old element at dropping position
15296 new_element = drop_element; // default: no change when dropping
15298 // check if player is active, not moving and ready to drop
15299 if (!player->active || player->MovPos || player->drop_delay > 0)
15302 // check if player has anything that can be dropped
15303 if (new_element == EL_UNDEFINED)
15306 // only set if player has anything that can be dropped
15307 player->is_dropping_pressed = TRUE;
15309 // check if drop key was pressed long enough for EM style dynamite
15310 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15313 // check if anything can be dropped at the current position
15314 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15317 // collected custom elements can only be dropped on empty fields
15318 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15321 if (old_element != EL_EMPTY)
15322 Back[dropx][dropy] = old_element; // store old element on this field
15324 ResetGfxAnimation(dropx, dropy);
15325 ResetRandomAnimationValue(dropx, dropy);
15327 if (player->inventory_size > 0 ||
15328 player->inventory_infinite_element != EL_UNDEFINED)
15330 if (player->inventory_size > 0)
15332 player->inventory_size--;
15334 DrawGameDoorValues();
15336 if (new_element == EL_DYNAMITE)
15337 new_element = EL_DYNAMITE_ACTIVE;
15338 else if (new_element == EL_EM_DYNAMITE)
15339 new_element = EL_EM_DYNAMITE_ACTIVE;
15340 else if (new_element == EL_SP_DISK_RED)
15341 new_element = EL_SP_DISK_RED_ACTIVE;
15344 Tile[dropx][dropy] = new_element;
15346 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15347 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15348 el2img(Tile[dropx][dropy]), 0);
15350 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15352 // needed if previous element just changed to "empty" in the last frame
15353 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15355 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15356 player->index_bit, drop_side);
15357 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15359 player->index_bit, drop_side);
15361 TestIfElementTouchesCustomElement(dropx, dropy);
15363 else // player is dropping a dyna bomb
15365 player->dynabombs_left--;
15367 Tile[dropx][dropy] = new_element;
15369 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15370 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15371 el2img(Tile[dropx][dropy]), 0);
15373 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15376 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15377 InitField_WithBug1(dropx, dropy, FALSE);
15379 new_element = Tile[dropx][dropy]; // element might have changed
15381 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15382 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15384 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15385 MovDir[dropx][dropy] = drop_direction;
15387 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15389 // do not cause impact style collision by dropping elements that can fall
15390 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15393 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15394 player->is_dropping = TRUE;
15396 player->drop_pressed_delay = 0;
15397 player->is_dropping_pressed = FALSE;
15399 player->drop_x = dropx;
15400 player->drop_y = dropy;
15405 // ----------------------------------------------------------------------------
15406 // game sound playing functions
15407 // ----------------------------------------------------------------------------
15409 static int *loop_sound_frame = NULL;
15410 static int *loop_sound_volume = NULL;
15412 void InitPlayLevelSound(void)
15414 int num_sounds = getSoundListSize();
15416 checked_free(loop_sound_frame);
15417 checked_free(loop_sound_volume);
15419 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15420 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15423 static void PlayLevelSoundExt(int x, int y, int nr, boolean is_loop_sound)
15425 int sx = SCREENX(x), sy = SCREENY(y);
15426 int volume, stereo_position;
15427 int max_distance = 8;
15428 int type = (is_loop_sound ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15430 if ((!setup.sound_simple && !is_loop_sound) ||
15431 (!setup.sound_loops && is_loop_sound))
15434 if (!IN_LEV_FIELD(x, y) ||
15435 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15436 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15439 volume = SOUND_MAX_VOLUME;
15441 if (!IN_SCR_FIELD(sx, sy))
15443 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15444 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15446 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15449 stereo_position = (SOUND_MAX_LEFT +
15450 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15451 (SCR_FIELDX + 2 * max_distance));
15455 /* This assures that quieter loop sounds do not overwrite louder ones,
15456 while restarting sound volume comparison with each new game frame. */
15458 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15461 loop_sound_volume[nr] = volume;
15462 loop_sound_frame[nr] = FrameCounter;
15465 PlaySoundExt(nr, volume, stereo_position, type);
15468 static void PlayLevelSound(int x, int y, int nr)
15470 PlayLevelSoundExt(x, y, nr, IS_LOOP_SOUND(nr));
15473 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15475 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15476 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15477 y < LEVELY(BY1) ? LEVELY(BY1) :
15478 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15482 static void PlayLevelSoundAction(int x, int y, int action)
15484 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15487 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15489 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15491 if (sound_effect != SND_UNDEFINED)
15492 PlayLevelSound(x, y, sound_effect);
15495 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15498 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15500 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15501 PlayLevelSound(x, y, sound_effect);
15504 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15506 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15508 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15509 PlayLevelSound(x, y, sound_effect);
15512 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15514 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15516 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15517 StopSound(sound_effect);
15520 static int getLevelMusicNr(void)
15522 int level_pos = level_nr - leveldir_current->first_level;
15524 if (levelset.music[level_nr] != MUS_UNDEFINED)
15525 return levelset.music[level_nr]; // from config file
15527 return MAP_NOCONF_MUSIC(level_pos); // from music dir
15530 static void FadeLevelSounds(void)
15535 static void FadeLevelMusic(void)
15537 int music_nr = getLevelMusicNr();
15538 char *curr_music = getCurrentlyPlayingMusicFilename();
15539 char *next_music = getMusicInfoEntryFilename(music_nr);
15541 if (!strEqual(curr_music, next_music))
15545 void FadeLevelSoundsAndMusic(void)
15551 static void PlayLevelMusic(void)
15553 int music_nr = getLevelMusicNr();
15554 char *curr_music = getCurrentlyPlayingMusicFilename();
15555 char *next_music = getMusicInfoEntryFilename(music_nr);
15557 if (!strEqual(curr_music, next_music))
15558 PlayMusicLoop(music_nr);
15561 static int getSoundAction_BD(int sample)
15565 case GD_S_STONE_PUSHING:
15566 case GD_S_MEGA_STONE_PUSHING:
15567 case GD_S_FLYING_STONE_PUSHING:
15568 case GD_S_WAITING_STONE_PUSHING:
15569 case GD_S_CHASING_STONE_PUSHING:
15570 case GD_S_NUT_PUSHING:
15571 case GD_S_NITRO_PACK_PUSHING:
15572 case GD_S_BLADDER_PUSHING:
15573 case GD_S_BOX_PUSHING:
15574 return ACTION_PUSHING;
15576 case GD_S_STONE_FALLING:
15577 case GD_S_MEGA_STONE_FALLING:
15578 case GD_S_FLYING_STONE_FALLING:
15579 case GD_S_NUT_FALLING:
15580 case GD_S_DIRT_BALL_FALLING:
15581 case GD_S_DIRT_LOOSE_FALLING:
15582 case GD_S_NITRO_PACK_FALLING:
15583 case GD_S_FALLING_WALL_FALLING:
15584 return ACTION_FALLING;
15586 case GD_S_STONE_IMPACT:
15587 case GD_S_MEGA_STONE_IMPACT:
15588 case GD_S_FLYING_STONE_IMPACT:
15589 case GD_S_NUT_IMPACT:
15590 case GD_S_DIRT_BALL_IMPACT:
15591 case GD_S_DIRT_LOOSE_IMPACT:
15592 case GD_S_NITRO_PACK_IMPACT:
15593 case GD_S_FALLING_WALL_IMPACT:
15594 return ACTION_IMPACT;
15596 case GD_S_NUT_CRACKING:
15597 return ACTION_BREAKING;
15599 case GD_S_EXPANDING_WALL:
15600 case GD_S_WALL_REAPPEARING:
15603 case GD_S_ACID_SPREADING:
15604 return ACTION_GROWING;
15606 case GD_S_DIAMOND_COLLECTING:
15607 case GD_S_FLYING_DIAMOND_COLLECTING:
15608 case GD_S_SKELETON_COLLECTING:
15609 case GD_S_PNEUMATIC_COLLECTING:
15610 case GD_S_BOMB_COLLECTING:
15611 case GD_S_CLOCK_COLLECTING:
15612 case GD_S_SWEET_COLLECTING:
15613 case GD_S_KEY_COLLECTING:
15614 case GD_S_DIAMOND_KEY_COLLECTING:
15615 return ACTION_COLLECTING;
15617 case GD_S_BOMB_PLACING:
15618 case GD_S_REPLICATOR:
15619 return ACTION_DROPPING;
15621 case GD_S_BLADDER_MOVING:
15622 return ACTION_MOVING;
15624 case GD_S_BLADDER_SPENDER:
15625 case GD_S_BLADDER_CONVERTING:
15626 case GD_S_GRAVITY_CHANGING:
15627 return ACTION_CHANGING;
15629 case GD_S_BITER_EATING:
15630 return ACTION_EATING;
15632 case GD_S_DOOR_OPENING:
15633 case GD_S_CRACKING:
15634 return ACTION_OPENING;
15636 case GD_S_DIRT_WALKING:
15637 return ACTION_DIGGING;
15639 case GD_S_EMPTY_WALKING:
15640 return ACTION_WALKING;
15642 case GD_S_SWITCH_BITER:
15643 case GD_S_SWITCH_CREATURES:
15644 case GD_S_SWITCH_GRAVITY:
15645 case GD_S_SWITCH_EXPANDING:
15646 case GD_S_SWITCH_CONVEYOR:
15647 case GD_S_SWITCH_REPLICATOR:
15648 case GD_S_STIRRING:
15649 return ACTION_ACTIVATING;
15651 case GD_S_TELEPORTER:
15652 return ACTION_PASSING;
15654 case GD_S_EXPLODING:
15655 case GD_S_BOMB_EXPLODING:
15656 case GD_S_GHOST_EXPLODING:
15657 case GD_S_VOODOO_EXPLODING:
15658 case GD_S_NITRO_PACK_EXPLODING:
15659 return ACTION_EXPLODING;
15661 case GD_S_COVERING:
15663 case GD_S_MAGIC_WALL:
15664 case GD_S_PNEUMATIC_HAMMER:
15666 return ACTION_ACTIVE;
15668 case GD_S_DIAMOND_FALLING_RANDOM:
15669 case GD_S_DIAMOND_FALLING_1:
15670 case GD_S_DIAMOND_FALLING_2:
15671 case GD_S_DIAMOND_FALLING_3:
15672 case GD_S_DIAMOND_FALLING_4:
15673 case GD_S_DIAMOND_FALLING_5:
15674 case GD_S_DIAMOND_FALLING_6:
15675 case GD_S_DIAMOND_FALLING_7:
15676 case GD_S_DIAMOND_FALLING_8:
15677 case GD_S_DIAMOND_IMPACT_RANDOM:
15678 case GD_S_DIAMOND_IMPACT_1:
15679 case GD_S_DIAMOND_IMPACT_2:
15680 case GD_S_DIAMOND_IMPACT_3:
15681 case GD_S_DIAMOND_IMPACT_4:
15682 case GD_S_DIAMOND_IMPACT_5:
15683 case GD_S_DIAMOND_IMPACT_6:
15684 case GD_S_DIAMOND_IMPACT_7:
15685 case GD_S_DIAMOND_IMPACT_8:
15686 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15687 case GD_S_FLYING_DIAMOND_FALLING_1:
15688 case GD_S_FLYING_DIAMOND_FALLING_2:
15689 case GD_S_FLYING_DIAMOND_FALLING_3:
15690 case GD_S_FLYING_DIAMOND_FALLING_4:
15691 case GD_S_FLYING_DIAMOND_FALLING_5:
15692 case GD_S_FLYING_DIAMOND_FALLING_6:
15693 case GD_S_FLYING_DIAMOND_FALLING_7:
15694 case GD_S_FLYING_DIAMOND_FALLING_8:
15695 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15696 case GD_S_FLYING_DIAMOND_IMPACT_1:
15697 case GD_S_FLYING_DIAMOND_IMPACT_2:
15698 case GD_S_FLYING_DIAMOND_IMPACT_3:
15699 case GD_S_FLYING_DIAMOND_IMPACT_4:
15700 case GD_S_FLYING_DIAMOND_IMPACT_5:
15701 case GD_S_FLYING_DIAMOND_IMPACT_6:
15702 case GD_S_FLYING_DIAMOND_IMPACT_7:
15703 case GD_S_FLYING_DIAMOND_IMPACT_8:
15704 case GD_S_TIMEOUT_0:
15705 case GD_S_TIMEOUT_1:
15706 case GD_S_TIMEOUT_2:
15707 case GD_S_TIMEOUT_3:
15708 case GD_S_TIMEOUT_4:
15709 case GD_S_TIMEOUT_5:
15710 case GD_S_TIMEOUT_6:
15711 case GD_S_TIMEOUT_7:
15712 case GD_S_TIMEOUT_8:
15713 case GD_S_TIMEOUT_9:
15714 case GD_S_TIMEOUT_10:
15715 case GD_S_BONUS_LIFE:
15716 // trigger special post-processing (and force sound to be non-looping)
15717 return ACTION_OTHER;
15719 case GD_S_AMOEBA_MAGIC:
15720 case GD_S_FINISHED:
15721 // trigger special post-processing (and force sound to be looping)
15722 return ACTION_DEFAULT;
15725 return ACTION_DEFAULT;
15729 static int getSoundEffect_BD(int element_bd, int sample)
15731 int sound_action = getSoundAction_BD(sample);
15732 int sound_effect = element_info[SND_ELEMENT(element_bd)].sound[sound_action];
15736 if (sound_action != ACTION_OTHER &&
15737 sound_action != ACTION_DEFAULT)
15738 return sound_effect;
15740 // special post-processing for some sounds
15743 case GD_S_DIAMOND_FALLING_RANDOM:
15744 case GD_S_DIAMOND_FALLING_1:
15745 case GD_S_DIAMOND_FALLING_2:
15746 case GD_S_DIAMOND_FALLING_3:
15747 case GD_S_DIAMOND_FALLING_4:
15748 case GD_S_DIAMOND_FALLING_5:
15749 case GD_S_DIAMOND_FALLING_6:
15750 case GD_S_DIAMOND_FALLING_7:
15751 case GD_S_DIAMOND_FALLING_8:
15752 nr = (sample == GD_S_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15753 sample - GD_S_DIAMOND_FALLING_1);
15754 sound_effect = SND_BD_DIAMOND_FALLING_RANDOM_1 + nr;
15756 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15757 sound_effect = SND_BD_DIAMOND_FALLING;
15760 case GD_S_DIAMOND_IMPACT_RANDOM:
15761 case GD_S_DIAMOND_IMPACT_1:
15762 case GD_S_DIAMOND_IMPACT_2:
15763 case GD_S_DIAMOND_IMPACT_3:
15764 case GD_S_DIAMOND_IMPACT_4:
15765 case GD_S_DIAMOND_IMPACT_5:
15766 case GD_S_DIAMOND_IMPACT_6:
15767 case GD_S_DIAMOND_IMPACT_7:
15768 case GD_S_DIAMOND_IMPACT_8:
15769 nr = (sample == GD_S_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15770 sample - GD_S_DIAMOND_IMPACT_1);
15771 sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr;
15773 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15774 sound_effect = SND_BD_DIAMOND_IMPACT;
15777 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15778 case GD_S_FLYING_DIAMOND_FALLING_1:
15779 case GD_S_FLYING_DIAMOND_FALLING_2:
15780 case GD_S_FLYING_DIAMOND_FALLING_3:
15781 case GD_S_FLYING_DIAMOND_FALLING_4:
15782 case GD_S_FLYING_DIAMOND_FALLING_5:
15783 case GD_S_FLYING_DIAMOND_FALLING_6:
15784 case GD_S_FLYING_DIAMOND_FALLING_7:
15785 case GD_S_FLYING_DIAMOND_FALLING_8:
15786 nr = (sample == GD_S_FLYING_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15787 sample - GD_S_FLYING_DIAMOND_FALLING_1);
15788 sound_effect = SND_BD_FLYING_DIAMOND_FALLING_RANDOM_1 + nr;
15790 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15791 sound_effect = SND_BD_FLYING_DIAMOND_FALLING;
15794 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15795 case GD_S_FLYING_DIAMOND_IMPACT_1:
15796 case GD_S_FLYING_DIAMOND_IMPACT_2:
15797 case GD_S_FLYING_DIAMOND_IMPACT_3:
15798 case GD_S_FLYING_DIAMOND_IMPACT_4:
15799 case GD_S_FLYING_DIAMOND_IMPACT_5:
15800 case GD_S_FLYING_DIAMOND_IMPACT_6:
15801 case GD_S_FLYING_DIAMOND_IMPACT_7:
15802 case GD_S_FLYING_DIAMOND_IMPACT_8:
15803 nr = (sample == GD_S_FLYING_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15804 sample - GD_S_FLYING_DIAMOND_IMPACT_1);
15805 sound_effect = SND_BD_FLYING_DIAMOND_IMPACT_RANDOM_1 + nr;
15807 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15808 sound_effect = SND_BD_FLYING_DIAMOND_IMPACT;
15811 case GD_S_TIMEOUT_0:
15812 case GD_S_TIMEOUT_1:
15813 case GD_S_TIMEOUT_2:
15814 case GD_S_TIMEOUT_3:
15815 case GD_S_TIMEOUT_4:
15816 case GD_S_TIMEOUT_5:
15817 case GD_S_TIMEOUT_6:
15818 case GD_S_TIMEOUT_7:
15819 case GD_S_TIMEOUT_8:
15820 case GD_S_TIMEOUT_9:
15821 case GD_S_TIMEOUT_10:
15822 nr = sample - GD_S_TIMEOUT_0;
15823 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME_0 + nr;
15825 if (getSoundInfoEntryFilename(sound_effect) == NULL && sample != GD_S_TIMEOUT_0)
15826 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME;
15829 case GD_S_BONUS_LIFE:
15830 sound_effect = SND_GAME_HEALTH_BONUS;
15833 case GD_S_AMOEBA_MAGIC:
15834 sound_effect = SND_BD_AMOEBA_OTHER;
15837 case GD_S_FINISHED:
15838 sound_effect = SND_GAME_LEVELTIME_BONUS;
15842 sound_effect = SND_UNDEFINED;
15846 return sound_effect;
15849 void PlayLevelSound_BD(int xx, int yy, int element_bd, int sample)
15851 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15852 int sound_effect = getSoundEffect_BD(element, sample);
15853 int sound_action = getSoundAction_BD(sample);
15854 boolean is_loop_sound = IS_LOOP_SOUND(sound_effect);
15856 int x = xx - offset;
15857 int y = yy - offset;
15859 // some sound actions are always looping in native BD game engine
15860 if (sound_action == ACTION_DEFAULT)
15861 is_loop_sound = TRUE;
15863 // some sound actions are always non-looping in native BD game engine
15864 if (sound_action == ACTION_FALLING ||
15865 sound_action == ACTION_MOVING ||
15866 sound_action == ACTION_OTHER)
15867 is_loop_sound = FALSE;
15869 if (sound_effect != SND_UNDEFINED)
15870 PlayLevelSoundExt(x, y, sound_effect, is_loop_sound);
15873 void StopSound_BD(int element_bd, int sample)
15875 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15876 int sound_effect = getSoundEffect_BD(element, sample);
15878 if (sound_effect != SND_UNDEFINED)
15879 StopSound(sound_effect);
15882 boolean isSoundPlaying_BD(int element_bd, int sample)
15884 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15885 int sound_effect = getSoundEffect_BD(element, sample);
15887 if (sound_effect != SND_UNDEFINED)
15888 return isSoundPlaying(sound_effect);
15893 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15895 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15897 int x = xx - offset;
15898 int y = yy - offset;
15903 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15907 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15911 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15915 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15919 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15923 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15927 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15930 case SOUND_android_clone:
15931 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15934 case SOUND_android_move:
15935 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15939 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15943 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15947 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15950 case SOUND_eater_eat:
15951 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15955 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15958 case SOUND_collect:
15959 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15962 case SOUND_diamond:
15963 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15967 // !!! CHECK THIS !!!
15969 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15971 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15975 case SOUND_wonderfall:
15976 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15980 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15984 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15988 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15992 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15996 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16000 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
16004 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16008 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16011 case SOUND_exit_open:
16012 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
16015 case SOUND_exit_leave:
16016 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16019 case SOUND_dynamite:
16020 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16024 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16028 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
16032 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16036 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
16040 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
16044 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
16048 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
16053 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
16055 int element = map_element_SP_to_RND(element_sp);
16056 int action = map_action_SP_to_RND(action_sp);
16057 int offset = (setup.sp_show_border_elements ? 0 : 1);
16058 int x = xx - offset;
16059 int y = yy - offset;
16061 PlayLevelSoundElementAction(x, y, element, action);
16064 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
16066 int element = map_element_MM_to_RND(element_mm);
16067 int action = map_action_MM_to_RND(action_mm);
16069 int x = xx - offset;
16070 int y = yy - offset;
16072 if (!IS_MM_ELEMENT(element))
16073 element = EL_MM_DEFAULT;
16075 PlayLevelSoundElementAction(x, y, element, action);
16078 void PlaySound_MM(int sound_mm)
16080 int sound = map_sound_MM_to_RND(sound_mm);
16082 if (sound == SND_UNDEFINED)
16088 void PlaySoundLoop_MM(int sound_mm)
16090 int sound = map_sound_MM_to_RND(sound_mm);
16092 if (sound == SND_UNDEFINED)
16095 PlaySoundLoop(sound);
16098 void StopSound_MM(int sound_mm)
16100 int sound = map_sound_MM_to_RND(sound_mm);
16102 if (sound == SND_UNDEFINED)
16108 void RaiseScore(int value)
16110 game.score += value;
16112 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
16114 DisplayGameControlValues();
16117 void RaiseScoreElement(int element)
16122 case EL_BD_DIAMOND:
16123 case EL_EMERALD_YELLOW:
16124 case EL_EMERALD_RED:
16125 case EL_EMERALD_PURPLE:
16126 case EL_SP_INFOTRON:
16127 RaiseScore(level.score[SC_EMERALD]);
16130 RaiseScore(level.score[SC_DIAMOND]);
16133 RaiseScore(level.score[SC_CRYSTAL]);
16136 RaiseScore(level.score[SC_PEARL]);
16139 case EL_BD_BUTTERFLY:
16140 case EL_SP_ELECTRON:
16141 RaiseScore(level.score[SC_BUG]);
16144 case EL_BD_FIREFLY:
16145 case EL_SP_SNIKSNAK:
16146 RaiseScore(level.score[SC_SPACESHIP]);
16149 case EL_DARK_YAMYAM:
16150 RaiseScore(level.score[SC_YAMYAM]);
16153 RaiseScore(level.score[SC_ROBOT]);
16156 RaiseScore(level.score[SC_PACMAN]);
16159 RaiseScore(level.score[SC_NUT]);
16162 case EL_EM_DYNAMITE:
16163 case EL_SP_DISK_RED:
16164 case EL_DYNABOMB_INCREASE_NUMBER:
16165 case EL_DYNABOMB_INCREASE_SIZE:
16166 case EL_DYNABOMB_INCREASE_POWER:
16167 RaiseScore(level.score[SC_DYNAMITE]);
16169 case EL_SHIELD_NORMAL:
16170 case EL_SHIELD_DEADLY:
16171 RaiseScore(level.score[SC_SHIELD]);
16173 case EL_EXTRA_TIME:
16174 RaiseScore(level.extra_time_score);
16188 case EL_DC_KEY_WHITE:
16189 RaiseScore(level.score[SC_KEY]);
16192 RaiseScore(element_info[element].collect_score);
16197 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
16199 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
16203 // prevent short reactivation of overlay buttons while closing door
16204 SetOverlayActive(FALSE);
16205 UnmapGameButtons();
16207 // door may still be open due to skipped or envelope style request
16208 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
16211 if (network.enabled)
16213 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
16217 // when using BD game engine, cover screen before fading out
16218 if (!quick_quit && level.game_engine_type == GAME_ENGINE_TYPE_BD)
16219 game_bd.cover_screen = TRUE;
16222 FadeSkipNextFadeIn();
16224 SetGameStatus(GAME_MODE_MAIN);
16229 else // continue playing the game
16231 if (tape.playing && tape.deactivate_display)
16232 TapeDeactivateDisplayOff(TRUE);
16234 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
16236 if (tape.playing && tape.deactivate_display)
16237 TapeDeactivateDisplayOn();
16241 void RequestQuitGame(boolean escape_key_pressed)
16243 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
16244 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
16245 level_editor_test_game);
16246 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
16247 quick_quit || score_info_tape_play);
16249 RequestQuitGameExt(skip_request, quick_quit,
16250 "Do you really want to quit the game?");
16253 static char *getRestartGameMessage(void)
16255 boolean play_again = hasStartedNetworkGame();
16256 static char message[MAX_OUTPUT_LINESIZE];
16257 char *game_over_text = "Game over!";
16258 char *play_again_text = " Play it again?";
16260 if (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
16261 game_mm.game_over_message != NULL)
16262 game_over_text = game_mm.game_over_message;
16264 snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text,
16265 (play_again ? play_again_text : ""));
16270 static void RequestRestartGame(void)
16272 char *message = getRestartGameMessage();
16273 boolean has_started_game = hasStartedNetworkGame();
16274 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
16275 int door_state = DOOR_CLOSE_1;
16277 if (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game)
16279 CloseDoor(door_state);
16281 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16285 // if game was invoked from level editor, also close tape recorder door
16286 if (level_editor_test_game)
16287 door_state = DOOR_CLOSE_ALL;
16289 CloseDoor(door_state);
16291 SetGameStatus(GAME_MODE_MAIN);
16297 boolean CheckRestartGame(void)
16299 static int game_over_delay = 0;
16300 int game_over_delay_value = 50;
16301 boolean game_over = checkGameFailed();
16305 game_over_delay = game_over_delay_value;
16310 if (game_over_delay > 0)
16312 if (game_over_delay == game_over_delay_value / 2)
16313 PlaySound(SND_GAME_LOSING);
16320 // do not ask to play again if request dialog is already active
16321 if (game.request_active)
16324 // do not ask to play again if request dialog already handled
16325 if (game.RestartGameRequested)
16328 // do not ask to play again if game was never actually played
16329 if (!game.GamePlayed)
16332 // do not ask to play again if this was disabled in setup menu
16333 if (!setup.ask_on_game_over)
16336 game.RestartGameRequested = TRUE;
16338 RequestRestartGame();
16343 boolean checkGameRunning(void)
16345 if (game_status != GAME_MODE_PLAYING)
16348 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGameRunning_BD())
16354 boolean checkGamePlaying(void)
16356 if (game_status != GAME_MODE_PLAYING)
16359 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGamePlaying_BD())
16365 boolean checkGameSolved(void)
16367 // set for all game engines if level was solved
16368 return game.LevelSolved_GameEnd;
16371 boolean checkGameFailed(void)
16373 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
16374 return (game_bd.game_over && !game_bd.level_solved);
16375 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16376 return (game_em.game_over && !game_em.level_solved);
16377 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16378 return (game_sp.game_over && !game_sp.level_solved);
16379 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16380 return (game_mm.game_over && !game_mm.level_solved);
16381 else // GAME_ENGINE_TYPE_RND
16382 return (game.GameOver && !game.LevelSolved);
16385 boolean checkGameEnded(void)
16387 return (checkGameSolved() || checkGameFailed());
16391 // ----------------------------------------------------------------------------
16392 // random generator functions
16393 // ----------------------------------------------------------------------------
16395 unsigned int InitEngineRandom_RND(int seed)
16397 game.num_random_calls = 0;
16399 return InitEngineRandom(seed);
16402 unsigned int RND(int max)
16406 game.num_random_calls++;
16408 return GetEngineRandom(max);
16415 // ----------------------------------------------------------------------------
16416 // game engine snapshot handling functions
16417 // ----------------------------------------------------------------------------
16419 struct EngineSnapshotInfo
16421 // runtime values for custom element collect score
16422 int collect_score[NUM_CUSTOM_ELEMENTS];
16424 // runtime values for group element choice position
16425 int choice_pos[NUM_GROUP_ELEMENTS];
16427 // runtime values for belt position animations
16428 int belt_graphic[4][NUM_BELT_PARTS];
16429 int belt_anim_mode[4][NUM_BELT_PARTS];
16432 static struct EngineSnapshotInfo engine_snapshot_rnd;
16433 static char *snapshot_level_identifier = NULL;
16434 static int snapshot_level_nr = -1;
16436 static void SaveEngineSnapshotValues_RND(void)
16438 static int belt_base_active_element[4] =
16440 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
16441 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
16442 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
16443 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
16447 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16449 int element = EL_CUSTOM_START + i;
16451 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
16454 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16456 int element = EL_GROUP_START + i;
16458 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
16461 for (i = 0; i < 4; i++)
16463 for (j = 0; j < NUM_BELT_PARTS; j++)
16465 int element = belt_base_active_element[i] + j;
16466 int graphic = el2img(element);
16467 int anim_mode = graphic_info[graphic].anim_mode;
16469 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
16470 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
16475 static void LoadEngineSnapshotValues_RND(void)
16477 unsigned int num_random_calls = game.num_random_calls;
16480 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16482 int element = EL_CUSTOM_START + i;
16484 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
16487 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16489 int element = EL_GROUP_START + i;
16491 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
16494 for (i = 0; i < 4; i++)
16496 for (j = 0; j < NUM_BELT_PARTS; j++)
16498 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
16499 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
16501 graphic_info[graphic].anim_mode = anim_mode;
16505 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16507 InitRND(tape.random_seed);
16508 for (i = 0; i < num_random_calls; i++)
16512 if (game.num_random_calls != num_random_calls)
16514 Error("number of random calls out of sync");
16515 Error("number of random calls should be %d", num_random_calls);
16516 Error("number of random calls is %d", game.num_random_calls);
16518 Fail("this should not happen -- please debug");
16522 void FreeEngineSnapshotSingle(void)
16524 FreeSnapshotSingle();
16526 setString(&snapshot_level_identifier, NULL);
16527 snapshot_level_nr = -1;
16530 void FreeEngineSnapshotList(void)
16532 FreeSnapshotList();
16535 static ListNode *SaveEngineSnapshotBuffers(void)
16537 ListNode *buffers = NULL;
16539 // copy some special values to a structure better suited for the snapshot
16541 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16542 SaveEngineSnapshotValues_RND();
16543 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16544 SaveEngineSnapshotValues_EM();
16545 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16546 SaveEngineSnapshotValues_SP(&buffers);
16547 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16548 SaveEngineSnapshotValues_MM();
16550 // save values stored in special snapshot structure
16552 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16553 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
16554 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16555 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
16556 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16557 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
16558 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16559 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
16561 // save further RND engine values
16563 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
16564 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
16565 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
16567 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
16568 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
16569 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
16570 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16571 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTimeFrames));
16572 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16574 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16575 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16576 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16578 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16580 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16581 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16583 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16584 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16585 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16586 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16587 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16588 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16589 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16590 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16591 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16592 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16593 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16594 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16595 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16596 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16597 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16598 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16599 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16600 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16602 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16603 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16605 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16606 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16607 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16609 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16610 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16612 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16613 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16614 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16615 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16616 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16617 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16619 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16620 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16623 ListNode *node = engine_snapshot_list_rnd;
16626 while (node != NULL)
16628 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16633 Debug("game:playing:SaveEngineSnapshotBuffers",
16634 "size of engine snapshot: %d bytes", num_bytes);
16640 void SaveEngineSnapshotSingle(void)
16642 ListNode *buffers = SaveEngineSnapshotBuffers();
16644 // finally save all snapshot buffers to single snapshot
16645 SaveSnapshotSingle(buffers);
16647 // save level identification information
16648 setString(&snapshot_level_identifier, leveldir_current->identifier);
16649 snapshot_level_nr = level_nr;
16652 boolean CheckSaveEngineSnapshotToList(void)
16654 boolean save_snapshot =
16655 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16656 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16657 game.snapshot.changed_action) ||
16658 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16659 game.snapshot.collected_item));
16661 game.snapshot.changed_action = FALSE;
16662 game.snapshot.collected_item = FALSE;
16663 game.snapshot.save_snapshot = save_snapshot;
16665 return save_snapshot;
16668 void SaveEngineSnapshotToList(void)
16670 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16674 ListNode *buffers = SaveEngineSnapshotBuffers();
16676 // finally save all snapshot buffers to snapshot list
16677 SaveSnapshotToList(buffers);
16680 void SaveEngineSnapshotToListInitial(void)
16682 FreeEngineSnapshotList();
16684 SaveEngineSnapshotToList();
16687 static void LoadEngineSnapshotValues(void)
16689 // restore special values from snapshot structure
16691 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16692 LoadEngineSnapshotValues_RND();
16693 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16694 LoadEngineSnapshotValues_EM();
16695 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16696 LoadEngineSnapshotValues_SP();
16697 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16698 LoadEngineSnapshotValues_MM();
16701 void LoadEngineSnapshotSingle(void)
16703 LoadSnapshotSingle();
16705 LoadEngineSnapshotValues();
16708 static void LoadEngineSnapshot_Undo(int steps)
16710 LoadSnapshotFromList_Older(steps);
16712 LoadEngineSnapshotValues();
16715 static void LoadEngineSnapshot_Redo(int steps)
16717 LoadSnapshotFromList_Newer(steps);
16719 LoadEngineSnapshotValues();
16722 boolean CheckEngineSnapshotSingle(void)
16724 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16725 snapshot_level_nr == level_nr);
16728 boolean CheckEngineSnapshotList(void)
16730 return CheckSnapshotList();
16734 // ---------- new game button stuff -------------------------------------------
16741 boolean *setup_value;
16742 boolean allowed_on_tape;
16743 boolean is_touch_button;
16745 } gamebutton_info[NUM_GAME_BUTTONS] =
16748 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16749 GAME_CTRL_ID_STOP, NULL,
16750 TRUE, FALSE, "stop game"
16753 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16754 GAME_CTRL_ID_PAUSE, NULL,
16755 TRUE, FALSE, "pause game"
16758 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16759 GAME_CTRL_ID_PLAY, NULL,
16760 TRUE, FALSE, "play game"
16763 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16764 GAME_CTRL_ID_UNDO, NULL,
16765 TRUE, FALSE, "undo step"
16768 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16769 GAME_CTRL_ID_REDO, NULL,
16770 TRUE, FALSE, "redo step"
16773 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16774 GAME_CTRL_ID_SAVE, NULL,
16775 TRUE, FALSE, "save game"
16778 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16779 GAME_CTRL_ID_PAUSE2, NULL,
16780 TRUE, FALSE, "pause game"
16783 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16784 GAME_CTRL_ID_LOAD, NULL,
16785 TRUE, FALSE, "load game"
16788 IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart,
16789 GAME_CTRL_ID_RESTART, NULL,
16790 TRUE, FALSE, "restart game"
16793 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16794 GAME_CTRL_ID_PANEL_STOP, NULL,
16795 FALSE, FALSE, "stop game"
16798 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16799 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16800 FALSE, FALSE, "pause game"
16803 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16804 GAME_CTRL_ID_PANEL_PLAY, NULL,
16805 FALSE, FALSE, "play game"
16808 IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart,
16809 GAME_CTRL_ID_PANEL_RESTART, NULL,
16810 FALSE, FALSE, "restart game"
16813 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16814 GAME_CTRL_ID_TOUCH_STOP, NULL,
16815 FALSE, TRUE, "stop game"
16818 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16819 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16820 FALSE, TRUE, "pause game"
16823 IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart,
16824 GAME_CTRL_ID_TOUCH_RESTART, NULL,
16825 FALSE, TRUE, "restart game"
16828 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16829 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16830 TRUE, FALSE, "background music on/off"
16833 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16834 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16835 TRUE, FALSE, "sound loops on/off"
16838 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16839 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16840 TRUE, FALSE, "normal sounds on/off"
16843 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16844 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16845 FALSE, FALSE, "background music on/off"
16848 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16849 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16850 FALSE, FALSE, "sound loops on/off"
16853 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16854 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16855 FALSE, FALSE, "normal sounds on/off"
16859 void CreateGameButtons(void)
16863 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16865 int graphic = gamebutton_info[i].graphic;
16866 struct GraphicInfo *gfx = &graphic_info[graphic];
16867 struct XY *pos = gamebutton_info[i].pos;
16868 struct GadgetInfo *gi;
16871 unsigned int event_mask;
16872 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16873 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16874 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16875 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16876 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16877 int gd_x = gfx->src_x;
16878 int gd_y = gfx->src_y;
16879 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16880 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16881 int gd_xa = gfx->src_x + gfx->active_xoffset;
16882 int gd_ya = gfx->src_y + gfx->active_yoffset;
16883 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16884 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16885 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16886 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16889 // do not use touch buttons if overlay touch buttons are disabled
16890 if (is_touch_button && !setup.touch.overlay_buttons)
16893 if (gfx->bitmap == NULL)
16895 game_gadget[id] = NULL;
16900 if (id == GAME_CTRL_ID_STOP ||
16901 id == GAME_CTRL_ID_PANEL_STOP ||
16902 id == GAME_CTRL_ID_TOUCH_STOP ||
16903 id == GAME_CTRL_ID_PLAY ||
16904 id == GAME_CTRL_ID_PANEL_PLAY ||
16905 id == GAME_CTRL_ID_SAVE ||
16906 id == GAME_CTRL_ID_LOAD ||
16907 id == GAME_CTRL_ID_RESTART ||
16908 id == GAME_CTRL_ID_PANEL_RESTART ||
16909 id == GAME_CTRL_ID_TOUCH_RESTART)
16911 button_type = GD_TYPE_NORMAL_BUTTON;
16913 event_mask = GD_EVENT_RELEASED;
16915 else if (id == GAME_CTRL_ID_UNDO ||
16916 id == GAME_CTRL_ID_REDO)
16918 button_type = GD_TYPE_NORMAL_BUTTON;
16920 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16924 button_type = GD_TYPE_CHECK_BUTTON;
16925 checked = (gamebutton_info[i].setup_value != NULL ?
16926 *gamebutton_info[i].setup_value : FALSE);
16927 event_mask = GD_EVENT_PRESSED;
16930 gi = CreateGadget(GDI_CUSTOM_ID, id,
16931 GDI_IMAGE_ID, graphic,
16932 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16935 GDI_WIDTH, gfx->width,
16936 GDI_HEIGHT, gfx->height,
16937 GDI_TYPE, button_type,
16938 GDI_STATE, GD_BUTTON_UNPRESSED,
16939 GDI_CHECKED, checked,
16940 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16941 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16942 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16943 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16944 GDI_DIRECT_DRAW, FALSE,
16945 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16946 GDI_EVENT_MASK, event_mask,
16947 GDI_CALLBACK_ACTION, HandleGameButtons,
16951 Fail("cannot create gadget");
16953 game_gadget[id] = gi;
16957 void FreeGameButtons(void)
16961 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16962 FreeGadget(game_gadget[i]);
16965 static void UnmapGameButtonsAtSamePosition(int id)
16969 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16971 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16972 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16973 UnmapGadget(game_gadget[i]);
16976 static void UnmapGameButtonsAtSamePosition_All(void)
16978 if (setup.show_load_save_buttons)
16980 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16981 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16982 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16984 else if (setup.show_undo_redo_buttons)
16986 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16987 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16988 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16992 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16993 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16994 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16996 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16997 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16998 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
17002 void MapLoadSaveButtons(void)
17004 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
17005 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
17007 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
17008 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
17011 void MapUndoRedoButtons(void)
17013 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
17014 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
17016 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
17017 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
17020 void ModifyPauseButtons(void)
17024 GAME_CTRL_ID_PAUSE,
17025 GAME_CTRL_ID_PAUSE2,
17026 GAME_CTRL_ID_PANEL_PAUSE,
17027 GAME_CTRL_ID_TOUCH_PAUSE,
17032 // do not redraw pause button on closed door (may happen when restarting game)
17033 if (!(GetDoorState() & DOOR_OPEN_1))
17036 for (i = 0; ids[i] > -1; i++)
17037 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
17040 static void MapGameButtonsExt(boolean on_tape)
17044 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17046 if ((i == GAME_CTRL_ID_UNDO ||
17047 i == GAME_CTRL_ID_REDO) &&
17048 game_status != GAME_MODE_PLAYING)
17051 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17052 MapGadget(game_gadget[i]);
17055 UnmapGameButtonsAtSamePosition_All();
17057 RedrawGameButtons();
17060 static void UnmapGameButtonsExt(boolean on_tape)
17064 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17065 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17066 UnmapGadget(game_gadget[i]);
17069 static void RedrawGameButtonsExt(boolean on_tape)
17073 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17074 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17075 RedrawGadget(game_gadget[i]);
17078 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
17083 gi->checked = state;
17086 static void RedrawSoundButtonGadget(int id)
17088 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
17089 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
17090 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
17091 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
17092 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
17093 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
17096 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
17097 RedrawGadget(game_gadget[id2]);
17100 void MapGameButtons(void)
17102 MapGameButtonsExt(FALSE);
17105 void UnmapGameButtons(void)
17107 UnmapGameButtonsExt(FALSE);
17110 void RedrawGameButtons(void)
17112 RedrawGameButtonsExt(FALSE);
17115 void MapGameButtonsOnTape(void)
17117 MapGameButtonsExt(TRUE);
17120 void UnmapGameButtonsOnTape(void)
17122 UnmapGameButtonsExt(TRUE);
17125 void RedrawGameButtonsOnTape(void)
17127 RedrawGameButtonsExt(TRUE);
17130 static void GameUndoRedoExt(void)
17132 ClearPlayerAction();
17134 tape.pausing = TRUE;
17137 UpdateAndDisplayGameControlValues();
17139 DrawCompleteVideoDisplay();
17140 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
17141 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
17142 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
17144 ModifyPauseButtons();
17149 static void GameUndo(int steps)
17151 if (!CheckEngineSnapshotList())
17154 int tape_property_bits = tape.property_bits;
17156 LoadEngineSnapshot_Undo(steps);
17158 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17163 static void GameRedo(int steps)
17165 if (!CheckEngineSnapshotList())
17168 int tape_property_bits = tape.property_bits;
17170 LoadEngineSnapshot_Redo(steps);
17172 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17177 static void HandleGameButtonsExt(int id, int button)
17179 static boolean game_undo_executed = FALSE;
17180 int steps = BUTTON_STEPSIZE(button);
17181 boolean handle_game_buttons =
17182 (game_status == GAME_MODE_PLAYING ||
17183 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
17185 if (!handle_game_buttons)
17190 case GAME_CTRL_ID_STOP:
17191 case GAME_CTRL_ID_PANEL_STOP:
17192 case GAME_CTRL_ID_TOUCH_STOP:
17197 case GAME_CTRL_ID_PAUSE:
17198 case GAME_CTRL_ID_PAUSE2:
17199 case GAME_CTRL_ID_PANEL_PAUSE:
17200 case GAME_CTRL_ID_TOUCH_PAUSE:
17201 if (network.enabled && game_status == GAME_MODE_PLAYING)
17204 SendToServer_ContinuePlaying();
17206 SendToServer_PausePlaying();
17209 TapeTogglePause(TAPE_TOGGLE_MANUAL);
17211 game_undo_executed = FALSE;
17215 case GAME_CTRL_ID_PLAY:
17216 case GAME_CTRL_ID_PANEL_PLAY:
17217 if (game_status == GAME_MODE_MAIN)
17219 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
17221 else if (tape.pausing)
17223 if (network.enabled)
17224 SendToServer_ContinuePlaying();
17226 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
17230 case GAME_CTRL_ID_UNDO:
17231 // Important: When using "save snapshot when collecting an item" mode,
17232 // load last (current) snapshot for first "undo" after pressing "pause"
17233 // (else the last-but-one snapshot would be loaded, because the snapshot
17234 // pointer already points to the last snapshot when pressing "pause",
17235 // which is fine for "every step/move" mode, but not for "every collect")
17236 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
17237 !game_undo_executed)
17240 game_undo_executed = TRUE;
17245 case GAME_CTRL_ID_REDO:
17249 case GAME_CTRL_ID_SAVE:
17253 case GAME_CTRL_ID_LOAD:
17257 case GAME_CTRL_ID_RESTART:
17258 case GAME_CTRL_ID_PANEL_RESTART:
17259 case GAME_CTRL_ID_TOUCH_RESTART:
17264 case SOUND_CTRL_ID_MUSIC:
17265 case SOUND_CTRL_ID_PANEL_MUSIC:
17266 if (setup.sound_music)
17268 setup.sound_music = FALSE;
17272 else if (audio.music_available)
17274 setup.sound = setup.sound_music = TRUE;
17276 SetAudioMode(setup.sound);
17278 if (game_status == GAME_MODE_PLAYING)
17282 RedrawSoundButtonGadget(id);
17286 case SOUND_CTRL_ID_LOOPS:
17287 case SOUND_CTRL_ID_PANEL_LOOPS:
17288 if (setup.sound_loops)
17289 setup.sound_loops = FALSE;
17290 else if (audio.loops_available)
17292 setup.sound = setup.sound_loops = TRUE;
17294 SetAudioMode(setup.sound);
17297 RedrawSoundButtonGadget(id);
17301 case SOUND_CTRL_ID_SIMPLE:
17302 case SOUND_CTRL_ID_PANEL_SIMPLE:
17303 if (setup.sound_simple)
17304 setup.sound_simple = FALSE;
17305 else if (audio.sound_available)
17307 setup.sound = setup.sound_simple = TRUE;
17309 SetAudioMode(setup.sound);
17312 RedrawSoundButtonGadget(id);
17321 static void HandleGameButtons(struct GadgetInfo *gi)
17323 HandleGameButtonsExt(gi->custom_id, gi->event.button);
17326 void HandleSoundButtonKeys(Key key)
17328 if (key == setup.shortcut.sound_simple)
17329 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
17330 else if (key == setup.shortcut.sound_loops)
17331 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
17332 else if (key == setup.shortcut.sound_music)
17333 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);