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);
3680 // do not cover screen before fading out when starting from main menu
3681 game_bd.cover_screen = FALSE;
3684 if (level_editor_test_game)
3685 FadeSkipNextFadeOut();
3687 FadeSetEnterScreen();
3690 fade_mask = REDRAW_ALL;
3692 FadeLevelSoundsAndMusic();
3694 ExpireSoundLoops(TRUE);
3700 // force restarting global animations displayed during game play
3701 RestartGlobalAnimsByStatus(GAME_MODE_PSEUDO_RESTARTING);
3703 // this is required for "transforming" fade modes like cross-fading
3704 // (else global animations will be stopped, but not restarted here)
3705 SetAnimStatusBeforeFading(GAME_MODE_PSEUDO_RESTARTING);
3707 SetGameStatus(GAME_MODE_PLAYING);
3710 if (level_editor_test_game)
3711 FadeSkipNextFadeIn();
3713 // needed if different viewport properties defined for playing
3714 ChangeViewportPropertiesIfNeeded();
3718 DrawCompleteVideoDisplay();
3720 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3723 InitGameControlValues();
3727 // initialize tape actions from game when recording tape
3728 tape.use_key_actions = game.use_key_actions;
3729 tape.use_mouse_actions = game.use_mouse_actions;
3731 // initialize visible playfield size when recording tape (for team mode)
3732 tape.scr_fieldx = SCR_FIELDX;
3733 tape.scr_fieldy = SCR_FIELDY;
3736 // don't play tapes over network
3737 network_playing = (network.enabled && !tape.playing);
3739 for (i = 0; i < MAX_PLAYERS; i++)
3741 struct PlayerInfo *player = &stored_player[i];
3743 player->index_nr = i;
3744 player->index_bit = (1 << i);
3745 player->element_nr = EL_PLAYER_1 + i;
3747 player->present = FALSE;
3748 player->active = FALSE;
3749 player->mapped = FALSE;
3751 player->killed = FALSE;
3752 player->reanimated = FALSE;
3753 player->buried = FALSE;
3756 player->effective_action = 0;
3757 player->programmed_action = 0;
3758 player->snap_action = 0;
3760 player->mouse_action.lx = 0;
3761 player->mouse_action.ly = 0;
3762 player->mouse_action.button = 0;
3763 player->mouse_action.button_hint = 0;
3765 player->effective_mouse_action.lx = 0;
3766 player->effective_mouse_action.ly = 0;
3767 player->effective_mouse_action.button = 0;
3768 player->effective_mouse_action.button_hint = 0;
3770 for (j = 0; j < MAX_NUM_KEYS; j++)
3771 player->key[j] = FALSE;
3773 player->num_white_keys = 0;
3775 player->dynabomb_count = 0;
3776 player->dynabomb_size = 1;
3777 player->dynabombs_left = 0;
3778 player->dynabomb_xl = FALSE;
3780 player->MovDir = initial_move_dir;
3783 player->GfxDir = initial_move_dir;
3784 player->GfxAction = ACTION_DEFAULT;
3786 player->StepFrame = 0;
3788 player->initial_element = player->element_nr;
3789 player->artwork_element =
3790 (level.use_artwork_element[i] ? level.artwork_element[i] :
3791 player->element_nr);
3792 player->use_murphy = FALSE;
3794 player->block_last_field = FALSE; // initialized in InitPlayerField()
3795 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3797 player->gravity = level.initial_player_gravity[i];
3799 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3801 player->actual_frame_counter.count = 0;
3802 player->actual_frame_counter.value = 1;
3804 player->step_counter = 0;
3806 player->last_move_dir = initial_move_dir;
3808 player->is_active = FALSE;
3810 player->is_waiting = FALSE;
3811 player->is_moving = FALSE;
3812 player->is_auto_moving = FALSE;
3813 player->is_digging = FALSE;
3814 player->is_snapping = FALSE;
3815 player->is_collecting = FALSE;
3816 player->is_pushing = FALSE;
3817 player->is_switching = FALSE;
3818 player->is_dropping = FALSE;
3819 player->is_dropping_pressed = FALSE;
3821 player->is_bored = FALSE;
3822 player->is_sleeping = FALSE;
3824 player->was_waiting = TRUE;
3825 player->was_moving = FALSE;
3826 player->was_snapping = FALSE;
3827 player->was_dropping = FALSE;
3829 player->force_dropping = FALSE;
3831 player->frame_counter_bored = -1;
3832 player->frame_counter_sleeping = -1;
3834 player->anim_delay_counter = 0;
3835 player->post_delay_counter = 0;
3837 player->dir_waiting = initial_move_dir;
3838 player->action_waiting = ACTION_DEFAULT;
3839 player->last_action_waiting = ACTION_DEFAULT;
3840 player->special_action_bored = ACTION_DEFAULT;
3841 player->special_action_sleeping = ACTION_DEFAULT;
3843 player->switch_x = -1;
3844 player->switch_y = -1;
3846 player->drop_x = -1;
3847 player->drop_y = -1;
3849 player->show_envelope = 0;
3851 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3853 player->push_delay = -1; // initialized when pushing starts
3854 player->push_delay_value = game.initial_push_delay_value;
3856 player->drop_delay = 0;
3857 player->drop_pressed_delay = 0;
3859 player->last_jx = -1;
3860 player->last_jy = -1;
3864 player->shield_normal_time_left = 0;
3865 player->shield_deadly_time_left = 0;
3867 player->last_removed_element = EL_UNDEFINED;
3869 player->inventory_infinite_element = EL_UNDEFINED;
3870 player->inventory_size = 0;
3872 if (level.use_initial_inventory[i])
3874 for (j = 0; j < level.initial_inventory_size[i]; j++)
3876 int element = level.initial_inventory_content[i][j];
3877 int collect_count = element_info[element].collect_count_initial;
3880 if (!IS_CUSTOM_ELEMENT(element))
3883 if (collect_count == 0)
3884 player->inventory_infinite_element = element;
3886 for (k = 0; k < collect_count; k++)
3887 if (player->inventory_size < MAX_INVENTORY_SIZE)
3888 player->inventory_element[player->inventory_size++] = element;
3892 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3893 SnapField(player, 0, 0);
3895 map_player_action[i] = i;
3898 network_player_action_received = FALSE;
3900 // initial null action
3901 if (network_playing)
3902 SendToServer_MovePlayer(MV_NONE);
3907 TimeLeft = level.time;
3912 ScreenMovDir = MV_NONE;
3916 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3918 game.robot_wheel_x = -1;
3919 game.robot_wheel_y = -1;
3924 game.all_players_gone = FALSE;
3926 game.LevelSolved = FALSE;
3927 game.GameOver = FALSE;
3929 game.GamePlayed = !tape.playing;
3931 game.LevelSolved_GameWon = FALSE;
3932 game.LevelSolved_GameEnd = FALSE;
3933 game.LevelSolved_SaveTape = FALSE;
3934 game.LevelSolved_SaveScore = FALSE;
3936 game.LevelSolved_CountingTime = 0;
3937 game.LevelSolved_CountingScore = 0;
3938 game.LevelSolved_CountingHealth = 0;
3940 game.RestartGameRequested = FALSE;
3942 game.panel.active = TRUE;
3944 game.no_level_time_limit = (level.time == 0);
3945 game.time_limit = (leveldir_current->time_limit && setup.time_limit);
3947 game.yamyam_content_nr = 0;
3948 game.robot_wheel_active = FALSE;
3949 game.magic_wall_active = FALSE;
3950 game.magic_wall_time_left = 0;
3951 game.light_time_left = 0;
3952 game.timegate_time_left = 0;
3953 game.switchgate_pos = 0;
3954 game.wind_direction = level.wind_direction_initial;
3956 game.time_final = 0;
3957 game.score_time_final = 0;
3960 game.score_final = 0;
3962 game.health = MAX_HEALTH;
3963 game.health_final = MAX_HEALTH;
3965 game.gems_still_needed = level.gems_needed;
3966 game.sokoban_fields_still_needed = 0;
3967 game.sokoban_objects_still_needed = 0;
3968 game.lights_still_needed = 0;
3969 game.players_still_needed = 0;
3970 game.friends_still_needed = 0;
3972 game.lenses_time_left = 0;
3973 game.magnify_time_left = 0;
3975 game.ball_active = level.ball_active_initial;
3976 game.ball_content_nr = 0;
3978 game.explosions_delayed = TRUE;
3980 // special case: set custom artwork setting to initial value
3981 game.use_masked_elements = game.use_masked_elements_initial;
3983 for (i = 0; i < NUM_BELTS; i++)
3985 game.belt_dir[i] = MV_NONE;
3986 game.belt_dir_nr[i] = 3; // not moving, next moving left
3989 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3990 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3992 #if DEBUG_INIT_PLAYER
3993 DebugPrintPlayerStatus("Player status at level initialization");
3996 SCAN_PLAYFIELD(x, y)
3998 Tile[x][y] = Last[x][y] = level.field[x][y];
3999 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
4000 ChangeDelay[x][y] = 0;
4001 ChangePage[x][y] = -1;
4002 CustomValue[x][y] = 0; // initialized in InitField()
4003 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
4005 WasJustMoving[x][y] = 0;
4006 WasJustFalling[x][y] = 0;
4007 CheckCollision[x][y] = 0;
4008 CheckImpact[x][y] = 0;
4010 Pushed[x][y] = FALSE;
4012 ChangeCount[x][y] = 0;
4013 ChangeEvent[x][y] = -1;
4015 ExplodePhase[x][y] = 0;
4016 ExplodeDelay[x][y] = 0;
4017 ExplodeField[x][y] = EX_TYPE_NONE;
4019 RunnerVisit[x][y] = 0;
4020 PlayerVisit[x][y] = 0;
4023 GfxRandom[x][y] = INIT_GFX_RANDOM();
4024 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
4025 GfxElement[x][y] = EL_UNDEFINED;
4026 GfxElementEmpty[x][y] = EL_EMPTY;
4027 GfxAction[x][y] = ACTION_DEFAULT;
4028 GfxDir[x][y] = MV_NONE;
4029 GfxRedraw[x][y] = GFX_REDRAW_NONE;
4032 SCAN_PLAYFIELD(x, y)
4034 InitFieldForEngine(x, y);
4036 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
4038 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
4041 InitField(x, y, TRUE);
4043 ResetGfxAnimation(x, y);
4048 // required if level does not contain any "empty space" element
4049 if (element_info[EL_EMPTY].use_gfx_element)
4050 game.use_masked_elements = TRUE;
4052 for (i = 0; i < MAX_PLAYERS; i++)
4054 struct PlayerInfo *player = &stored_player[i];
4056 // set number of special actions for bored and sleeping animation
4057 player->num_special_action_bored =
4058 get_num_special_action(player->artwork_element,
4059 ACTION_BORING_1, ACTION_BORING_LAST);
4060 player->num_special_action_sleeping =
4061 get_num_special_action(player->artwork_element,
4062 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
4065 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
4066 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
4068 // initialize type of slippery elements
4069 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4071 if (!IS_CUSTOM_ELEMENT(i))
4073 // default: elements slip down either to the left or right randomly
4074 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
4076 // SP style elements prefer to slip down on the left side
4077 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
4078 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4080 // BD style elements prefer to slip down on the left side
4081 if (game.emulation == EMU_BOULDERDASH)
4082 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4086 // initialize explosion and ignition delay
4087 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4089 if (!IS_CUSTOM_ELEMENT(i))
4092 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
4093 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
4094 game.emulation == EMU_SUPAPLEX ? 3 : 2);
4095 int last_phase = (num_phase + 1) * delay;
4096 int half_phase = (num_phase / 2) * delay;
4098 element_info[i].explosion_delay = last_phase - 1;
4099 element_info[i].ignition_delay = half_phase;
4101 if (i == EL_BLACK_ORB)
4102 element_info[i].ignition_delay = 1;
4106 // correct non-moving belts to start moving left
4107 for (i = 0; i < NUM_BELTS; i++)
4108 if (game.belt_dir[i] == MV_NONE)
4109 game.belt_dir_nr[i] = 3; // not moving, next moving left
4111 #if USE_NEW_PLAYER_ASSIGNMENTS
4112 // use preferred player also in local single-player mode
4113 if (!network.enabled && !game.team_mode)
4115 int new_index_nr = setup.network_player_nr;
4117 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
4119 for (i = 0; i < MAX_PLAYERS; i++)
4120 stored_player[i].connected_locally = FALSE;
4122 stored_player[new_index_nr].connected_locally = TRUE;
4126 for (i = 0; i < MAX_PLAYERS; i++)
4128 stored_player[i].connected = FALSE;
4130 // in network game mode, the local player might not be the first player
4131 if (stored_player[i].connected_locally)
4132 local_player = &stored_player[i];
4135 if (!network.enabled)
4136 local_player->connected = TRUE;
4140 for (i = 0; i < MAX_PLAYERS; i++)
4141 stored_player[i].connected = tape.player_participates[i];
4143 else if (network.enabled)
4145 // add team mode players connected over the network (needed for correct
4146 // assignment of player figures from level to locally playing players)
4148 for (i = 0; i < MAX_PLAYERS; i++)
4149 if (stored_player[i].connected_network)
4150 stored_player[i].connected = TRUE;
4152 else if (game.team_mode)
4154 // try to guess locally connected team mode players (needed for correct
4155 // assignment of player figures from level to locally playing players)
4157 for (i = 0; i < MAX_PLAYERS; i++)
4158 if (setup.input[i].use_joystick ||
4159 setup.input[i].key.left != KSYM_UNDEFINED)
4160 stored_player[i].connected = TRUE;
4163 #if DEBUG_INIT_PLAYER
4164 DebugPrintPlayerStatus("Player status after level initialization");
4167 #if DEBUG_INIT_PLAYER
4168 Debug("game:init:player", "Reassigning players ...");
4171 // check if any connected player was not found in playfield
4172 for (i = 0; i < MAX_PLAYERS; i++)
4174 struct PlayerInfo *player = &stored_player[i];
4176 if (player->connected && !player->present)
4178 struct PlayerInfo *field_player = NULL;
4180 #if DEBUG_INIT_PLAYER
4181 Debug("game:init:player",
4182 "- looking for field player for player %d ...", i + 1);
4185 // assign first free player found that is present in the playfield
4187 // first try: look for unmapped playfield player that is not connected
4188 for (j = 0; j < MAX_PLAYERS; j++)
4189 if (field_player == NULL &&
4190 stored_player[j].present &&
4191 !stored_player[j].mapped &&
4192 !stored_player[j].connected)
4193 field_player = &stored_player[j];
4195 // second try: look for *any* unmapped playfield player
4196 for (j = 0; j < MAX_PLAYERS; j++)
4197 if (field_player == NULL &&
4198 stored_player[j].present &&
4199 !stored_player[j].mapped)
4200 field_player = &stored_player[j];
4202 if (field_player != NULL)
4204 int jx = field_player->jx, jy = field_player->jy;
4206 #if DEBUG_INIT_PLAYER
4207 Debug("game:init:player", "- found player %d",
4208 field_player->index_nr + 1);
4211 player->present = FALSE;
4212 player->active = FALSE;
4214 field_player->present = TRUE;
4215 field_player->active = TRUE;
4218 player->initial_element = field_player->initial_element;
4219 player->artwork_element = field_player->artwork_element;
4221 player->block_last_field = field_player->block_last_field;
4222 player->block_delay_adjustment = field_player->block_delay_adjustment;
4225 StorePlayer[jx][jy] = field_player->element_nr;
4227 field_player->jx = field_player->last_jx = jx;
4228 field_player->jy = field_player->last_jy = jy;
4230 if (local_player == player)
4231 local_player = field_player;
4233 map_player_action[field_player->index_nr] = i;
4235 field_player->mapped = TRUE;
4237 #if DEBUG_INIT_PLAYER
4238 Debug("game:init:player", "- map_player_action[%d] == %d",
4239 field_player->index_nr + 1, i + 1);
4244 if (player->connected && player->present)
4245 player->mapped = TRUE;
4248 #if DEBUG_INIT_PLAYER
4249 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4254 // check if any connected player was not found in playfield
4255 for (i = 0; i < MAX_PLAYERS; i++)
4257 struct PlayerInfo *player = &stored_player[i];
4259 if (player->connected && !player->present)
4261 for (j = 0; j < MAX_PLAYERS; j++)
4263 struct PlayerInfo *field_player = &stored_player[j];
4264 int jx = field_player->jx, jy = field_player->jy;
4266 // assign first free player found that is present in the playfield
4267 if (field_player->present && !field_player->connected)
4269 player->present = TRUE;
4270 player->active = TRUE;
4272 field_player->present = FALSE;
4273 field_player->active = FALSE;
4275 player->initial_element = field_player->initial_element;
4276 player->artwork_element = field_player->artwork_element;
4278 player->block_last_field = field_player->block_last_field;
4279 player->block_delay_adjustment = field_player->block_delay_adjustment;
4281 StorePlayer[jx][jy] = player->element_nr;
4283 player->jx = player->last_jx = jx;
4284 player->jy = player->last_jy = jy;
4294 Debug("game:init:player", "local_player->present == %d",
4295 local_player->present);
4298 // set focus to local player for network games, else to all players
4299 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4300 game.centered_player_nr_next = game.centered_player_nr;
4301 game.set_centered_player = FALSE;
4302 game.set_centered_player_wrap = FALSE;
4304 if (network_playing && tape.recording)
4306 // store client dependent player focus when recording network games
4307 tape.centered_player_nr_next = game.centered_player_nr_next;
4308 tape.set_centered_player = TRUE;
4313 // when playing a tape, eliminate all players who do not participate
4315 #if USE_NEW_PLAYER_ASSIGNMENTS
4317 if (!game.team_mode)
4319 for (i = 0; i < MAX_PLAYERS; i++)
4321 if (stored_player[i].active &&
4322 !tape.player_participates[map_player_action[i]])
4324 struct PlayerInfo *player = &stored_player[i];
4325 int jx = player->jx, jy = player->jy;
4327 #if DEBUG_INIT_PLAYER
4328 Debug("game:init:player", "Removing player %d at (%d, %d)",
4332 player->active = FALSE;
4333 StorePlayer[jx][jy] = 0;
4334 Tile[jx][jy] = EL_EMPTY;
4341 for (i = 0; i < MAX_PLAYERS; i++)
4343 if (stored_player[i].active &&
4344 !tape.player_participates[i])
4346 struct PlayerInfo *player = &stored_player[i];
4347 int jx = player->jx, jy = player->jy;
4349 player->active = FALSE;
4350 StorePlayer[jx][jy] = 0;
4351 Tile[jx][jy] = EL_EMPTY;
4356 else if (!network.enabled && !game.team_mode) // && !tape.playing
4358 // when in single player mode, eliminate all but the local player
4360 for (i = 0; i < MAX_PLAYERS; i++)
4362 struct PlayerInfo *player = &stored_player[i];
4364 if (player->active && player != local_player)
4366 int jx = player->jx, jy = player->jy;
4368 player->active = FALSE;
4369 player->present = FALSE;
4371 StorePlayer[jx][jy] = 0;
4372 Tile[jx][jy] = EL_EMPTY;
4377 for (i = 0; i < MAX_PLAYERS; i++)
4378 if (stored_player[i].active)
4379 game.players_still_needed++;
4381 if (level.solved_by_one_player)
4382 game.players_still_needed = 1;
4384 // when recording the game, store which players take part in the game
4387 #if USE_NEW_PLAYER_ASSIGNMENTS
4388 for (i = 0; i < MAX_PLAYERS; i++)
4389 if (stored_player[i].connected)
4390 tape.player_participates[i] = TRUE;
4392 for (i = 0; i < MAX_PLAYERS; i++)
4393 if (stored_player[i].active)
4394 tape.player_participates[i] = TRUE;
4398 #if DEBUG_INIT_PLAYER
4399 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4402 if (BorderElement == EL_EMPTY)
4405 SBX_Right = lev_fieldx - SCR_FIELDX;
4407 SBY_Lower = lev_fieldy - SCR_FIELDY;
4412 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4414 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4417 if (full_lev_fieldx <= SCR_FIELDX)
4418 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4419 if (full_lev_fieldy <= SCR_FIELDY)
4420 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4422 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4424 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4427 // if local player not found, look for custom element that might create
4428 // the player (make some assumptions about the right custom element)
4429 if (!local_player->present)
4431 int start_x = 0, start_y = 0;
4432 int found_rating = 0;
4433 int found_element = EL_UNDEFINED;
4434 int player_nr = local_player->index_nr;
4436 SCAN_PLAYFIELD(x, y)
4438 int element = Tile[x][y];
4443 if (level.use_start_element[player_nr] &&
4444 level.start_element[player_nr] == element &&
4451 found_element = element;
4454 if (!IS_CUSTOM_ELEMENT(element))
4457 if (CAN_CHANGE(element))
4459 for (i = 0; i < element_info[element].num_change_pages; i++)
4461 // check for player created from custom element as single target
4462 content = element_info[element].change_page[i].target_element;
4463 is_player = IS_PLAYER_ELEMENT(content);
4465 if (is_player && (found_rating < 3 ||
4466 (found_rating == 3 && element < found_element)))
4472 found_element = element;
4477 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4479 // check for player created from custom element as explosion content
4480 content = element_info[element].content.e[xx][yy];
4481 is_player = IS_PLAYER_ELEMENT(content);
4483 if (is_player && (found_rating < 2 ||
4484 (found_rating == 2 && element < found_element)))
4486 start_x = x + xx - 1;
4487 start_y = y + yy - 1;
4490 found_element = element;
4493 if (!CAN_CHANGE(element))
4496 for (i = 0; i < element_info[element].num_change_pages; i++)
4498 // check for player created from custom element as extended target
4500 element_info[element].change_page[i].target_content.e[xx][yy];
4502 is_player = IS_PLAYER_ELEMENT(content);
4504 if (is_player && (found_rating < 1 ||
4505 (found_rating == 1 && element < found_element)))
4507 start_x = x + xx - 1;
4508 start_y = y + yy - 1;
4511 found_element = element;
4517 scroll_x = SCROLL_POSITION_X(start_x);
4518 scroll_y = SCROLL_POSITION_Y(start_y);
4522 scroll_x = SCROLL_POSITION_X(local_player->jx);
4523 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4526 if (game.forced_scroll_x != ARG_UNDEFINED_VALUE)
4527 scroll_x = game.forced_scroll_x;
4528 if (game.forced_scroll_y != ARG_UNDEFINED_VALUE)
4529 scroll_y = game.forced_scroll_y;
4531 // !!! FIX THIS (START) !!!
4532 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
4534 InitGameEngine_BD();
4536 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4538 InitGameEngine_EM();
4540 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4542 InitGameEngine_SP();
4544 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4546 InitGameEngine_MM();
4550 DrawLevel(REDRAW_FIELD);
4553 // after drawing the level, correct some elements
4554 if (game.timegate_time_left == 0)
4555 CloseAllOpenTimegates();
4558 // blit playfield from scroll buffer to normal back buffer for fading in
4559 BlitScreenToBitmap(backbuffer);
4560 // !!! FIX THIS (END) !!!
4562 DrawMaskedBorder(fade_mask);
4567 // full screen redraw is required at this point in the following cases:
4568 // - special editor door undrawn when game was started from level editor
4569 // - drawing area (playfield) was changed and has to be removed completely
4570 redraw_mask = REDRAW_ALL;
4574 if (!game.restart_level)
4576 // copy default game door content to main double buffer
4578 // !!! CHECK AGAIN !!!
4579 SetPanelBackground();
4580 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4581 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4584 SetPanelBackground();
4585 SetDrawBackgroundMask(REDRAW_DOOR_1);
4587 UpdateAndDisplayGameControlValues();
4589 if (!game.restart_level)
4595 CreateGameButtons();
4600 // copy actual game door content to door double buffer for OpenDoor()
4601 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4603 OpenDoor(DOOR_OPEN_ALL);
4605 KeyboardAutoRepeatOffUnlessAutoplay();
4607 #if DEBUG_INIT_PLAYER
4608 DebugPrintPlayerStatus("Player status (final)");
4617 if (!game.restart_level && !tape.playing)
4619 LevelStats_incPlayed(level_nr);
4621 SaveLevelSetup_SeriesInfo();
4624 game.restart_level = FALSE;
4625 game.request_active = FALSE;
4626 game.envelope_active = FALSE;
4627 game.any_door_active = FALSE;
4629 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4630 InitGameActions_MM();
4632 SaveEngineSnapshotToListInitial();
4634 if (!game.restart_level)
4636 PlaySound(SND_GAME_STARTING);
4638 if (setup.sound_music)
4642 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4645 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4646 int actual_player_x, int actual_player_y)
4648 // this is used for non-R'n'D game engines to update certain engine values
4650 // needed to determine if sounds are played within the visible screen area
4651 scroll_x = actual_scroll_x;
4652 scroll_y = actual_scroll_y;
4654 // needed to get player position for "follow finger" playing input method
4655 local_player->jx = actual_player_x;
4656 local_player->jy = actual_player_y;
4659 void InitMovDir(int x, int y)
4661 int i, element = Tile[x][y];
4662 static int xy[4][2] =
4669 static int direction[3][4] =
4671 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4672 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4673 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4682 Tile[x][y] = EL_BUG;
4683 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4686 case EL_SPACESHIP_RIGHT:
4687 case EL_SPACESHIP_UP:
4688 case EL_SPACESHIP_LEFT:
4689 case EL_SPACESHIP_DOWN:
4690 Tile[x][y] = EL_SPACESHIP;
4691 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4694 case EL_BD_BUTTERFLY_RIGHT:
4695 case EL_BD_BUTTERFLY_UP:
4696 case EL_BD_BUTTERFLY_LEFT:
4697 case EL_BD_BUTTERFLY_DOWN:
4698 Tile[x][y] = EL_BD_BUTTERFLY;
4699 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4702 case EL_BD_FIREFLY_RIGHT:
4703 case EL_BD_FIREFLY_UP:
4704 case EL_BD_FIREFLY_LEFT:
4705 case EL_BD_FIREFLY_DOWN:
4706 Tile[x][y] = EL_BD_FIREFLY;
4707 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4710 case EL_PACMAN_RIGHT:
4712 case EL_PACMAN_LEFT:
4713 case EL_PACMAN_DOWN:
4714 Tile[x][y] = EL_PACMAN;
4715 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4718 case EL_YAMYAM_LEFT:
4719 case EL_YAMYAM_RIGHT:
4721 case EL_YAMYAM_DOWN:
4722 Tile[x][y] = EL_YAMYAM;
4723 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4726 case EL_SP_SNIKSNAK:
4727 MovDir[x][y] = MV_UP;
4730 case EL_SP_ELECTRON:
4731 MovDir[x][y] = MV_LEFT;
4738 Tile[x][y] = EL_MOLE;
4739 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4742 case EL_SPRING_LEFT:
4743 case EL_SPRING_RIGHT:
4744 Tile[x][y] = EL_SPRING;
4745 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4749 if (IS_CUSTOM_ELEMENT(element))
4751 struct ElementInfo *ei = &element_info[element];
4752 int move_direction_initial = ei->move_direction_initial;
4753 int move_pattern = ei->move_pattern;
4755 if (move_direction_initial == MV_START_PREVIOUS)
4757 if (MovDir[x][y] != MV_NONE)
4760 move_direction_initial = MV_START_AUTOMATIC;
4763 if (move_direction_initial == MV_START_RANDOM)
4764 MovDir[x][y] = 1 << RND(4);
4765 else if (move_direction_initial & MV_ANY_DIRECTION)
4766 MovDir[x][y] = move_direction_initial;
4767 else if (move_pattern == MV_ALL_DIRECTIONS ||
4768 move_pattern == MV_TURNING_LEFT ||
4769 move_pattern == MV_TURNING_RIGHT ||
4770 move_pattern == MV_TURNING_LEFT_RIGHT ||
4771 move_pattern == MV_TURNING_RIGHT_LEFT ||
4772 move_pattern == MV_TURNING_RANDOM)
4773 MovDir[x][y] = 1 << RND(4);
4774 else if (move_pattern == MV_HORIZONTAL)
4775 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4776 else if (move_pattern == MV_VERTICAL)
4777 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4778 else if (move_pattern & MV_ANY_DIRECTION)
4779 MovDir[x][y] = element_info[element].move_pattern;
4780 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4781 move_pattern == MV_ALONG_RIGHT_SIDE)
4783 // use random direction as default start direction
4784 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4785 MovDir[x][y] = 1 << RND(4);
4787 for (i = 0; i < NUM_DIRECTIONS; i++)
4789 int x1 = x + xy[i][0];
4790 int y1 = y + xy[i][1];
4792 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4794 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4795 MovDir[x][y] = direction[0][i];
4797 MovDir[x][y] = direction[1][i];
4806 MovDir[x][y] = 1 << RND(4);
4808 if (element != EL_BUG &&
4809 element != EL_SPACESHIP &&
4810 element != EL_BD_BUTTERFLY &&
4811 element != EL_BD_FIREFLY)
4814 for (i = 0; i < NUM_DIRECTIONS; i++)
4816 int x1 = x + xy[i][0];
4817 int y1 = y + xy[i][1];
4819 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4821 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4823 MovDir[x][y] = direction[0][i];
4826 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4827 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4829 MovDir[x][y] = direction[1][i];
4838 GfxDir[x][y] = MovDir[x][y];
4841 void InitAmoebaNr(int x, int y)
4844 int group_nr = AmoebaNeighbourNr(x, y);
4848 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4850 if (AmoebaCnt[i] == 0)
4858 AmoebaNr[x][y] = group_nr;
4859 AmoebaCnt[group_nr]++;
4860 AmoebaCnt2[group_nr]++;
4863 static void LevelSolved_SetFinalGameValues(void)
4865 game.time_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.time_played :
4866 game.no_level_time_limit ? TimePlayed : TimeLeft);
4867 game.score_time_final = (level.use_step_counter ? TimePlayed :
4868 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4870 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.score :
4871 level.game_engine_type == GAME_ENGINE_TYPE_EM ? game_em.lev->score :
4872 level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.score :
4875 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4876 MM_HEALTH(game_mm.laser_overload_value) :
4879 game.LevelSolved_CountingTime = game.time_final;
4880 game.LevelSolved_CountingScore = game.score_final;
4881 game.LevelSolved_CountingHealth = game.health_final;
4884 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4886 game.LevelSolved_CountingTime = time;
4887 game.LevelSolved_CountingScore = score;
4888 game.LevelSolved_CountingHealth = health;
4890 game_panel_controls[GAME_PANEL_TIME].value = time;
4891 game_panel_controls[GAME_PANEL_SCORE].value = score;
4892 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4894 DisplayGameControlValues();
4897 static void LevelSolved(void)
4899 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4900 game.players_still_needed > 0)
4903 game.LevelSolved = TRUE;
4904 game.GameOver = TRUE;
4908 // needed here to display correct panel values while player walks into exit
4909 LevelSolved_SetFinalGameValues();
4912 static boolean AdvanceToNextLevel(void)
4914 if (setup.increment_levels &&
4915 level_nr < leveldir_current->last_level &&
4918 level_nr++; // advance to next level
4919 TapeErase(); // start with empty tape
4921 if (setup.auto_play_next_level)
4923 scores.continue_playing = TRUE;
4924 scores.next_level_nr = level_nr;
4926 LoadLevel(level_nr);
4928 SaveLevelSetup_SeriesInfo();
4939 static int time_count_steps;
4940 static int time, time_final;
4941 static float score, score_final; // needed for time score < 10 for 10 seconds
4942 static int health, health_final;
4943 static int game_over_delay_1 = 0;
4944 static int game_over_delay_2 = 0;
4945 static int game_over_delay_3 = 0;
4946 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4947 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4949 if (!game.LevelSolved_GameWon)
4953 // do not start end game actions before the player stops moving (to exit)
4954 if (local_player->active && local_player->MovPos)
4957 // calculate final game values after player finished walking into exit
4958 LevelSolved_SetFinalGameValues();
4960 game.LevelSolved_GameWon = TRUE;
4961 game.LevelSolved_SaveTape = tape.recording;
4962 game.LevelSolved_SaveScore = !tape.playing;
4966 LevelStats_incSolved(level_nr);
4968 SaveLevelSetup_SeriesInfo();
4971 if (tape.auto_play) // tape might already be stopped here
4972 tape.auto_play_level_solved = TRUE;
4976 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4977 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4978 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4980 time = time_final = game.time_final;
4981 score = score_final = game.score_final;
4982 health = health_final = game.health_final;
4984 // update game panel values before (delayed) counting of score (if any)
4985 LevelSolved_DisplayFinalGameValues(time, score, health);
4987 // if level has time score defined, calculate new final game values
4990 int time_final_max = 999;
4991 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4992 int time_frames = 0;
4993 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4994 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4999 time_frames = time_frames_left;
5001 else if (game.no_level_time_limit && TimePlayed < time_final_max)
5003 time_final = time_final_max;
5004 time_frames = time_frames_final_max - time_frames_played;
5007 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
5009 time_count_steps = MAX(1, ABS(time_final - time) / 100);
5011 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
5013 // keep previous values (final values already processed here)
5015 score_final = score;
5017 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
5020 score_final += health * time_score;
5023 game.score_final = score_final;
5024 game.health_final = health_final;
5027 // if not counting score after game, immediately update game panel values
5028 if (level_editor_test_game || !setup.count_score_after_game ||
5029 level.game_engine_type == GAME_ENGINE_TYPE_BD)
5032 score = score_final;
5034 LevelSolved_DisplayFinalGameValues(time, score, health);
5037 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
5039 // check if last player has left the level
5040 if (game.exit_x >= 0 &&
5043 int x = game.exit_x;
5044 int y = game.exit_y;
5045 int element = Tile[x][y];
5047 // close exit door after last player
5048 if ((game.all_players_gone &&
5049 (element == EL_EXIT_OPEN ||
5050 element == EL_SP_EXIT_OPEN ||
5051 element == EL_STEEL_EXIT_OPEN)) ||
5052 element == EL_EM_EXIT_OPEN ||
5053 element == EL_EM_STEEL_EXIT_OPEN)
5057 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
5058 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
5059 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
5060 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
5061 EL_EM_STEEL_EXIT_CLOSING);
5063 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
5066 // player disappears
5067 DrawLevelField(x, y);
5070 for (i = 0; i < MAX_PLAYERS; i++)
5072 struct PlayerInfo *player = &stored_player[i];
5074 if (player->present)
5076 RemovePlayer(player);
5078 // player disappears
5079 DrawLevelField(player->jx, player->jy);
5084 PlaySound(SND_GAME_WINNING);
5087 if (setup.count_score_after_game)
5089 if (time != time_final)
5091 if (game_over_delay_1 > 0)
5093 game_over_delay_1--;
5098 int time_to_go = ABS(time_final - time);
5099 int time_count_dir = (time < time_final ? +1 : -1);
5101 if (time_to_go < time_count_steps)
5102 time_count_steps = 1;
5104 time += time_count_steps * time_count_dir;
5105 score += time_count_steps * time_score;
5107 // set final score to correct rounding differences after counting score
5108 if (time == time_final)
5109 score = score_final;
5111 LevelSolved_DisplayFinalGameValues(time, score, health);
5113 if (time == time_final)
5114 StopSound(SND_GAME_LEVELTIME_BONUS);
5115 else if (setup.sound_loops)
5116 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5118 PlaySound(SND_GAME_LEVELTIME_BONUS);
5123 if (health != health_final)
5125 if (game_over_delay_2 > 0)
5127 game_over_delay_2--;
5132 int health_count_dir = (health < health_final ? +1 : -1);
5134 health += health_count_dir;
5135 score += time_score;
5137 LevelSolved_DisplayFinalGameValues(time, score, health);
5139 if (health == health_final)
5140 StopSound(SND_GAME_LEVELTIME_BONUS);
5141 else if (setup.sound_loops)
5142 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5144 PlaySound(SND_GAME_LEVELTIME_BONUS);
5150 game.panel.active = FALSE;
5152 if (game_over_delay_3 > 0)
5154 game_over_delay_3--;
5164 // used instead of "level_nr" (needed for network games)
5165 int last_level_nr = levelset.level_nr;
5166 boolean tape_saved = FALSE;
5167 boolean game_over = checkGameFailed();
5169 // Important note: This function is not only called after "GameWon()", but also after
5170 // "game over" (if automatically asking for restarting the game is disabled in setup)
5172 // do not handle game end if game over and automatically asking for game restart
5173 if (game_over && setup.ask_on_game_over)
5176 // do not handle game end if request dialog is already active
5177 if (checkRequestActive())
5180 if (game.LevelSolved)
5181 game.LevelSolved_GameEnd = TRUE;
5183 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5185 // make sure that request dialog to save tape does not open door again
5186 if (!global.use_envelope_request)
5187 CloseDoor(DOOR_CLOSE_1);
5190 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5192 // set unique basename for score tape (also saved in high score table)
5193 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5196 // if no tape is to be saved, close both doors simultaneously
5197 CloseDoor(DOOR_CLOSE_ALL);
5199 if (level_editor_test_game || score_info_tape_play)
5201 SetGameStatus(GAME_MODE_MAIN);
5208 if (!game.GamePlayed || (!game.LevelSolved_SaveScore && !level.bd_intermission))
5210 SetGameStatus(GAME_MODE_MAIN);
5217 if (level_nr == leveldir_current->handicap_level)
5219 leveldir_current->handicap_level++;
5221 SaveLevelSetup_SeriesInfo();
5224 // save score and score tape before potentially erasing tape below
5225 if (game.LevelSolved_SaveScore)
5226 NewHighScore(last_level_nr, tape_saved);
5228 // increment and load next level (if possible and not configured otherwise)
5229 AdvanceToNextLevel();
5231 if (game.LevelSolved_SaveScore && scores.last_added >= 0 && setup.show_scores_after_game)
5233 SetGameStatus(GAME_MODE_SCORES);
5235 DrawHallOfFame(last_level_nr);
5237 else if (scores.continue_playing)
5239 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5243 SetGameStatus(GAME_MODE_MAIN);
5249 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5250 boolean one_score_entry_per_name)
5254 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5257 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5259 struct ScoreEntry *entry = &list->entry[i];
5260 boolean score_is_better = (new_entry->score > entry->score);
5261 boolean score_is_equal = (new_entry->score == entry->score);
5262 boolean time_is_better = (new_entry->time < entry->time);
5263 boolean time_is_equal = (new_entry->time == entry->time);
5264 boolean better_by_score = (score_is_better ||
5265 (score_is_equal && time_is_better));
5266 boolean better_by_time = (time_is_better ||
5267 (time_is_equal && score_is_better));
5268 boolean is_better = (level.rate_time_over_score ? better_by_time :
5270 boolean entry_is_empty = (entry->score == 0 &&
5273 // prevent adding server score entries if also existing in local score file
5274 // (special case: historic score entries have an empty tape basename entry)
5275 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5276 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5278 // add fields from server score entry not stored in local score entry
5279 // (currently, this means setting platform, version and country fields;
5280 // in rare cases, this may also correct an invalid score value, as
5281 // historic scores might have been truncated to 16-bit values locally)
5282 *entry = *new_entry;
5287 if (is_better || entry_is_empty)
5289 // player has made it to the hall of fame
5291 if (i < MAX_SCORE_ENTRIES - 1)
5293 int m = MAX_SCORE_ENTRIES - 1;
5296 if (one_score_entry_per_name)
5298 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5299 if (strEqual(list->entry[l].name, new_entry->name))
5302 if (m == i) // player's new highscore overwrites his old one
5306 for (l = m; l > i; l--)
5307 list->entry[l] = list->entry[l - 1];
5312 *entry = *new_entry;
5316 else if (one_score_entry_per_name &&
5317 strEqual(entry->name, new_entry->name))
5319 // player already in high score list with better score or time
5325 // special case: new score is beyond the last high score list position
5326 return MAX_SCORE_ENTRIES;
5329 void NewHighScore(int level_nr, boolean tape_saved)
5331 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5332 boolean one_per_name = FALSE;
5334 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5335 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5337 new_entry.score = game.score_final;
5338 new_entry.time = game.score_time_final;
5340 LoadScore(level_nr);
5342 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5344 if (scores.last_added >= MAX_SCORE_ENTRIES)
5346 scores.last_added = MAX_SCORE_ENTRIES - 1;
5347 scores.force_last_added = TRUE;
5349 scores.entry[scores.last_added] = new_entry;
5351 // store last added local score entry (before merging server scores)
5352 scores.last_added_local = scores.last_added;
5357 if (scores.last_added < 0)
5360 SaveScore(level_nr);
5362 // store last added local score entry (before merging server scores)
5363 scores.last_added_local = scores.last_added;
5365 if (!game.LevelSolved_SaveTape)
5368 SaveScoreTape(level_nr);
5370 if (setup.ask_for_using_api_server)
5372 setup.use_api_server =
5373 Request("Upload your score and tape to the high score server?", REQ_ASK);
5375 if (!setup.use_api_server)
5376 Request("Not using high score server! Use setup menu to enable again!",
5379 runtime.use_api_server = setup.use_api_server;
5381 // after asking for using API server once, do not ask again
5382 setup.ask_for_using_api_server = FALSE;
5384 SaveSetup_ServerSetup();
5387 SaveServerScore(level_nr, tape_saved);
5390 void MergeServerScore(void)
5392 struct ScoreEntry last_added_entry;
5393 boolean one_per_name = FALSE;
5396 if (scores.last_added >= 0)
5397 last_added_entry = scores.entry[scores.last_added];
5399 for (i = 0; i < server_scores.num_entries; i++)
5401 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5403 if (pos >= 0 && pos <= scores.last_added)
5404 scores.last_added++;
5407 if (scores.last_added >= MAX_SCORE_ENTRIES)
5409 scores.last_added = MAX_SCORE_ENTRIES - 1;
5410 scores.force_last_added = TRUE;
5412 scores.entry[scores.last_added] = last_added_entry;
5416 static int getElementMoveStepsizeExt(int x, int y, int direction)
5418 int element = Tile[x][y];
5419 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5420 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5421 int horiz_move = (dx != 0);
5422 int sign = (horiz_move ? dx : dy);
5423 int step = sign * element_info[element].move_stepsize;
5425 // special values for move stepsize for spring and things on conveyor belt
5428 if (CAN_FALL(element) &&
5429 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5430 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5431 else if (element == EL_SPRING)
5432 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5438 static int getElementMoveStepsize(int x, int y)
5440 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5443 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5445 if (player->GfxAction != action || player->GfxDir != dir)
5447 player->GfxAction = action;
5448 player->GfxDir = dir;
5450 player->StepFrame = 0;
5454 static void ResetGfxFrame(int x, int y)
5456 // profiling showed that "autotest" spends 10~20% of its time in this function
5457 if (DrawingDeactivatedField())
5460 int element = Tile[x][y];
5461 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5463 if (graphic_info[graphic].anim_global_sync)
5464 GfxFrame[x][y] = FrameCounter;
5465 else if (graphic_info[graphic].anim_global_anim_sync)
5466 GfxFrame[x][y] = getGlobalAnimSyncFrame();
5467 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5468 GfxFrame[x][y] = CustomValue[x][y];
5469 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5470 GfxFrame[x][y] = element_info[element].collect_score;
5471 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5472 GfxFrame[x][y] = ChangeDelay[x][y];
5475 static void ResetGfxAnimation(int x, int y)
5477 GfxAction[x][y] = ACTION_DEFAULT;
5478 GfxDir[x][y] = MovDir[x][y];
5481 ResetGfxFrame(x, y);
5484 static void ResetRandomAnimationValue(int x, int y)
5486 GfxRandom[x][y] = INIT_GFX_RANDOM();
5489 static void InitMovingField(int x, int y, int direction)
5491 int element = Tile[x][y];
5492 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5493 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5496 boolean is_moving_before, is_moving_after;
5498 // check if element was/is moving or being moved before/after mode change
5499 is_moving_before = (WasJustMoving[x][y] != 0);
5500 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5502 // reset animation only for moving elements which change direction of moving
5503 // or which just started or stopped moving
5504 // (else CEs with property "can move" / "not moving" are reset each frame)
5505 if (is_moving_before != is_moving_after ||
5506 direction != MovDir[x][y])
5507 ResetGfxAnimation(x, y);
5509 MovDir[x][y] = direction;
5510 GfxDir[x][y] = direction;
5512 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5513 direction == MV_DOWN && CAN_FALL(element) ?
5514 ACTION_FALLING : ACTION_MOVING);
5516 // this is needed for CEs with property "can move" / "not moving"
5518 if (is_moving_after)
5520 if (Tile[newx][newy] == EL_EMPTY)
5521 Tile[newx][newy] = EL_BLOCKED;
5523 MovDir[newx][newy] = MovDir[x][y];
5525 CustomValue[newx][newy] = CustomValue[x][y];
5527 GfxFrame[newx][newy] = GfxFrame[x][y];
5528 GfxRandom[newx][newy] = GfxRandom[x][y];
5529 GfxAction[newx][newy] = GfxAction[x][y];
5530 GfxDir[newx][newy] = GfxDir[x][y];
5534 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5536 int direction = MovDir[x][y];
5537 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5538 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5544 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5546 int direction = MovDir[x][y];
5547 int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0);
5548 int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0);
5550 *comes_from_x = oldx;
5551 *comes_from_y = oldy;
5554 static int MovingOrBlocked2Element(int x, int y)
5556 int element = Tile[x][y];
5558 if (element == EL_BLOCKED)
5562 Blocked2Moving(x, y, &oldx, &oldy);
5564 return Tile[oldx][oldy];
5570 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5572 // like MovingOrBlocked2Element(), but if element is moving
5573 // and (x, y) is the field the moving element is just leaving,
5574 // return EL_BLOCKED instead of the element value
5575 int element = Tile[x][y];
5577 if (IS_MOVING(x, y))
5579 if (element == EL_BLOCKED)
5583 Blocked2Moving(x, y, &oldx, &oldy);
5584 return Tile[oldx][oldy];
5593 static void RemoveField(int x, int y)
5595 Tile[x][y] = EL_EMPTY;
5601 CustomValue[x][y] = 0;
5604 ChangeDelay[x][y] = 0;
5605 ChangePage[x][y] = -1;
5606 Pushed[x][y] = FALSE;
5608 GfxElement[x][y] = EL_UNDEFINED;
5609 GfxAction[x][y] = ACTION_DEFAULT;
5610 GfxDir[x][y] = MV_NONE;
5613 static void RemoveMovingField(int x, int y)
5615 int oldx = x, oldy = y, newx = x, newy = y;
5616 int element = Tile[x][y];
5617 int next_element = EL_UNDEFINED;
5619 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5622 if (IS_MOVING(x, y))
5624 Moving2Blocked(x, y, &newx, &newy);
5626 if (Tile[newx][newy] != EL_BLOCKED)
5628 // element is moving, but target field is not free (blocked), but
5629 // already occupied by something different (example: acid pool);
5630 // in this case, only remove the moving field, but not the target
5632 RemoveField(oldx, oldy);
5634 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5636 TEST_DrawLevelField(oldx, oldy);
5641 else if (element == EL_BLOCKED)
5643 Blocked2Moving(x, y, &oldx, &oldy);
5644 if (!IS_MOVING(oldx, oldy))
5648 if (element == EL_BLOCKED &&
5649 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5650 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5651 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5652 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5653 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5654 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5655 next_element = get_next_element(Tile[oldx][oldy]);
5657 RemoveField(oldx, oldy);
5658 RemoveField(newx, newy);
5660 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5662 if (next_element != EL_UNDEFINED)
5663 Tile[oldx][oldy] = next_element;
5665 TEST_DrawLevelField(oldx, oldy);
5666 TEST_DrawLevelField(newx, newy);
5669 void DrawDynamite(int x, int y)
5671 int sx = SCREENX(x), sy = SCREENY(y);
5672 int graphic = el2img(Tile[x][y]);
5675 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5678 if (IS_WALKABLE_INSIDE(Back[x][y]))
5682 DrawLevelElement(x, y, Back[x][y]);
5683 else if (Store[x][y])
5684 DrawLevelElement(x, y, Store[x][y]);
5685 else if (game.use_masked_elements)
5686 DrawLevelElement(x, y, EL_EMPTY);
5688 frame = getGraphicAnimationFrameXY(graphic, x, y);
5690 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5691 DrawGraphicThruMask(sx, sy, graphic, frame);
5693 DrawGraphic(sx, sy, graphic, frame);
5696 static void CheckDynamite(int x, int y)
5698 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5702 if (MovDelay[x][y] != 0)
5705 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5711 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5716 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5718 boolean num_checked_players = 0;
5721 for (i = 0; i < MAX_PLAYERS; i++)
5723 if (stored_player[i].active)
5725 int sx = stored_player[i].jx;
5726 int sy = stored_player[i].jy;
5728 if (num_checked_players == 0)
5735 *sx1 = MIN(*sx1, sx);
5736 *sy1 = MIN(*sy1, sy);
5737 *sx2 = MAX(*sx2, sx);
5738 *sy2 = MAX(*sy2, sy);
5741 num_checked_players++;
5746 static boolean checkIfAllPlayersFitToScreen_RND(void)
5748 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5750 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5752 return (sx2 - sx1 < SCR_FIELDX &&
5753 sy2 - sy1 < SCR_FIELDY);
5756 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5758 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5760 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5762 *sx = (sx1 + sx2) / 2;
5763 *sy = (sy1 + sy2) / 2;
5766 static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
5767 boolean center_screen, boolean quick_relocation)
5769 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5770 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5771 boolean no_delay = (tape.warp_forward);
5772 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5773 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5774 int new_scroll_x, new_scroll_y;
5776 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5778 // case 1: quick relocation inside visible screen (without scrolling)
5785 if (!level.shifted_relocation || center_screen)
5787 // relocation _with_ centering of screen
5789 new_scroll_x = SCROLL_POSITION_X(x);
5790 new_scroll_y = SCROLL_POSITION_Y(y);
5794 // relocation _without_ centering of screen
5796 // apply distance between old and new player position to scroll position
5797 int shifted_scroll_x = scroll_x + (x - old_x);
5798 int shifted_scroll_y = scroll_y + (y - old_y);
5800 // make sure that shifted scroll position does not scroll beyond screen
5801 new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
5802 new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
5804 // special case for teleporting from one end of the playfield to the other
5805 // (this kludge prevents the destination area to be shifted by half a tile
5806 // against the source destination for even screen width or screen height;
5807 // probably most useful when used with high "game.forced_scroll_delay_value"
5808 // in combination with "game.forced_scroll_x" and "game.forced_scroll_y")
5809 if (quick_relocation)
5811 if (EVEN(SCR_FIELDX))
5813 // relocate (teleport) between left and right border (half or full)
5814 if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1)
5815 new_scroll_x = SBX_Right;
5816 else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right)
5817 new_scroll_x = SBX_Right - 1;
5818 else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1)
5819 new_scroll_x = SBX_Left;
5820 else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left)
5821 new_scroll_x = SBX_Left + 1;
5824 if (EVEN(SCR_FIELDY))
5826 // relocate (teleport) between top and bottom border (half or full)
5827 if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1)
5828 new_scroll_y = SBY_Lower;
5829 else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower)
5830 new_scroll_y = SBY_Lower - 1;
5831 else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1)
5832 new_scroll_y = SBY_Upper;
5833 else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper)
5834 new_scroll_y = SBY_Upper + 1;
5839 if (quick_relocation)
5841 // case 2: quick relocation (redraw without visible scrolling)
5843 scroll_x = new_scroll_x;
5844 scroll_y = new_scroll_y;
5851 // case 3: visible relocation (with scrolling to new position)
5853 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5855 SetVideoFrameDelay(wait_delay_value);
5857 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5859 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5860 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5862 if (dx == 0 && dy == 0) // no scrolling needed at all
5868 // set values for horizontal/vertical screen scrolling (half tile size)
5869 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5870 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5871 int pos_x = dx * TILEX / 2;
5872 int pos_y = dy * TILEY / 2;
5873 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5874 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5876 ScrollLevel(dx, dy);
5879 // scroll in two steps of half tile size to make things smoother
5880 BlitScreenToBitmapExt_RND(window, fx, fy);
5882 // scroll second step to align at full tile size
5883 BlitScreenToBitmap(window);
5889 SetVideoFrameDelay(frame_delay_value_old);
5892 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5894 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5895 int player_nr = GET_PLAYER_NR(el_player);
5896 struct PlayerInfo *player = &stored_player[player_nr];
5897 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5898 boolean no_delay = (tape.warp_forward);
5899 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5900 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5901 int old_jx = player->jx;
5902 int old_jy = player->jy;
5903 int old_element = Tile[old_jx][old_jy];
5904 int element = Tile[jx][jy];
5905 boolean player_relocated = (old_jx != jx || old_jy != jy);
5907 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5908 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5909 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5910 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5911 int leave_side_horiz = move_dir_horiz;
5912 int leave_side_vert = move_dir_vert;
5913 int enter_side = enter_side_horiz | enter_side_vert;
5914 int leave_side = leave_side_horiz | leave_side_vert;
5916 if (player->buried) // do not reanimate dead player
5919 if (!player_relocated) // no need to relocate the player
5922 if (IS_PLAYER(jx, jy)) // player already placed at new position
5924 RemoveField(jx, jy); // temporarily remove newly placed player
5925 DrawLevelField(jx, jy);
5928 if (player->present)
5930 while (player->MovPos)
5932 ScrollPlayer(player, SCROLL_GO_ON);
5933 ScrollScreen(NULL, SCROLL_GO_ON);
5935 AdvanceFrameAndPlayerCounters(player->index_nr);
5939 BackToFront_WithFrameDelay(wait_delay_value);
5942 DrawPlayer(player); // needed here only to cleanup last field
5943 DrawLevelField(player->jx, player->jy); // remove player graphic
5945 player->is_moving = FALSE;
5948 if (IS_CUSTOM_ELEMENT(old_element))
5949 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5951 player->index_bit, leave_side);
5953 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5955 player->index_bit, leave_side);
5957 Tile[jx][jy] = el_player;
5958 InitPlayerField(jx, jy, el_player, TRUE);
5960 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5961 possible that the relocation target field did not contain a player element,
5962 but a walkable element, to which the new player was relocated -- in this
5963 case, restore that (already initialized!) element on the player field */
5964 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5966 Tile[jx][jy] = element; // restore previously existing element
5969 // only visually relocate centered player
5970 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy,
5971 FALSE, level.instant_relocation);
5973 TestIfPlayerTouchesBadThing(jx, jy);
5974 TestIfPlayerTouchesCustomElement(jx, jy);
5976 if (IS_CUSTOM_ELEMENT(element))
5977 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5978 player->index_bit, enter_side);
5980 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5981 player->index_bit, enter_side);
5983 if (player->is_switching)
5985 /* ensure that relocation while still switching an element does not cause
5986 a new element to be treated as also switched directly after relocation
5987 (this is important for teleporter switches that teleport the player to
5988 a place where another teleporter switch is in the same direction, which
5989 would then incorrectly be treated as immediately switched before the
5990 direction key that caused the switch was released) */
5992 player->switch_x += jx - old_jx;
5993 player->switch_y += jy - old_jy;
5997 static void Explode(int ex, int ey, int phase, int mode)
6003 if (game.explosions_delayed)
6005 ExplodeField[ex][ey] = mode;
6009 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
6011 int center_element = Tile[ex][ey];
6012 int ce_value = CustomValue[ex][ey];
6013 int ce_score = element_info[center_element].collect_score;
6014 int artwork_element, explosion_element; // set these values later
6016 // remove things displayed in background while burning dynamite
6017 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
6020 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
6022 // put moving element to center field (and let it explode there)
6023 center_element = MovingOrBlocked2Element(ex, ey);
6024 RemoveMovingField(ex, ey);
6025 Tile[ex][ey] = center_element;
6028 // now "center_element" is finally determined -- set related values now
6029 artwork_element = center_element; // for custom player artwork
6030 explosion_element = center_element; // for custom player artwork
6032 if (IS_PLAYER(ex, ey))
6034 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
6036 artwork_element = stored_player[player_nr].artwork_element;
6038 if (level.use_explosion_element[player_nr])
6040 explosion_element = level.explosion_element[player_nr];
6041 artwork_element = explosion_element;
6045 if (mode == EX_TYPE_NORMAL ||
6046 mode == EX_TYPE_CENTER ||
6047 mode == EX_TYPE_CROSS)
6048 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
6050 last_phase = element_info[explosion_element].explosion_delay + 1;
6052 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
6054 int xx = x - ex + 1;
6055 int yy = y - ey + 1;
6058 if (!IN_LEV_FIELD(x, y) ||
6059 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
6060 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
6063 element = Tile[x][y];
6065 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
6067 element = MovingOrBlocked2Element(x, y);
6069 if (!IS_EXPLOSION_PROOF(element))
6070 RemoveMovingField(x, y);
6073 // indestructible elements can only explode in center (but not flames)
6074 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
6075 mode == EX_TYPE_BORDER)) ||
6076 element == EL_FLAMES)
6079 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
6080 behaviour, for example when touching a yamyam that explodes to rocks
6081 with active deadly shield, a rock is created under the player !!! */
6082 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
6084 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
6085 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
6086 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
6088 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
6091 if (IS_ACTIVE_BOMB(element))
6093 // re-activate things under the bomb like gate or penguin
6094 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
6101 // save walkable background elements while explosion on same tile
6102 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
6103 (x != ex || y != ey || mode == EX_TYPE_BORDER))
6104 Back[x][y] = element;
6106 // ignite explodable elements reached by other explosion
6107 if (element == EL_EXPLOSION)
6108 element = Store2[x][y];
6110 if (AmoebaNr[x][y] &&
6111 (element == EL_AMOEBA_FULL ||
6112 element == EL_BD_AMOEBA ||
6113 element == EL_AMOEBA_GROWING))
6115 AmoebaCnt[AmoebaNr[x][y]]--;
6116 AmoebaCnt2[AmoebaNr[x][y]]--;
6121 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
6123 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
6125 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
6127 if (PLAYERINFO(ex, ey)->use_murphy)
6128 Store[x][y] = EL_EMPTY;
6131 // !!! check this case -- currently needed for rnd_rado_negundo_v,
6132 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
6133 else if (IS_PLAYER_ELEMENT(center_element))
6134 Store[x][y] = EL_EMPTY;
6135 else if (center_element == EL_YAMYAM)
6136 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
6137 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
6138 Store[x][y] = element_info[center_element].content.e[xx][yy];
6140 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
6141 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
6142 // otherwise) -- FIX THIS !!!
6143 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
6144 Store[x][y] = element_info[element].content.e[1][1];
6146 else if (!CAN_EXPLODE(element))
6147 Store[x][y] = element_info[element].content.e[1][1];
6150 Store[x][y] = EL_EMPTY;
6152 if (IS_CUSTOM_ELEMENT(center_element))
6153 Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value :
6154 Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score :
6155 Store[x][y] >= EL_PREV_CE_8 &&
6156 Store[x][y] <= EL_NEXT_CE_8 ?
6157 RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) :
6160 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
6161 center_element == EL_AMOEBA_TO_DIAMOND)
6162 Store2[x][y] = element;
6164 Tile[x][y] = EL_EXPLOSION;
6165 GfxElement[x][y] = artwork_element;
6167 ExplodePhase[x][y] = 1;
6168 ExplodeDelay[x][y] = last_phase;
6173 if (center_element == EL_YAMYAM)
6174 game.yamyam_content_nr =
6175 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
6187 GfxFrame[x][y] = 0; // restart explosion animation
6189 last_phase = ExplodeDelay[x][y];
6191 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
6193 // this can happen if the player leaves an explosion just in time
6194 if (GfxElement[x][y] == EL_UNDEFINED)
6195 GfxElement[x][y] = EL_EMPTY;
6197 border_element = Store2[x][y];
6198 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6199 border_element = StorePlayer[x][y];
6201 if (phase == element_info[border_element].ignition_delay ||
6202 phase == last_phase)
6204 boolean border_explosion = FALSE;
6206 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6207 !PLAYER_EXPLOSION_PROTECTED(x, y))
6209 KillPlayerUnlessExplosionProtected(x, y);
6210 border_explosion = TRUE;
6212 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6214 Tile[x][y] = Store2[x][y];
6217 border_explosion = TRUE;
6219 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6221 AmoebaToDiamond(x, y);
6223 border_explosion = TRUE;
6226 // if an element just explodes due to another explosion (chain-reaction),
6227 // do not immediately end the new explosion when it was the last frame of
6228 // the explosion (as it would be done in the following "if"-statement!)
6229 if (border_explosion && phase == last_phase)
6233 // this can happen if the player was just killed by an explosion
6234 if (GfxElement[x][y] == EL_UNDEFINED)
6235 GfxElement[x][y] = EL_EMPTY;
6237 if (phase == last_phase)
6241 element = Tile[x][y] = Store[x][y];
6242 Store[x][y] = Store2[x][y] = 0;
6243 GfxElement[x][y] = EL_UNDEFINED;
6245 // player can escape from explosions and might therefore be still alive
6246 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6247 element <= EL_PLAYER_IS_EXPLODING_4)
6249 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6250 int explosion_element = EL_PLAYER_1 + player_nr;
6251 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6252 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6254 if (level.use_explosion_element[player_nr])
6255 explosion_element = level.explosion_element[player_nr];
6257 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6258 element_info[explosion_element].content.e[xx][yy]);
6261 // restore probably existing indestructible background element
6262 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6263 element = Tile[x][y] = Back[x][y];
6266 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6267 GfxDir[x][y] = MV_NONE;
6268 ChangeDelay[x][y] = 0;
6269 ChangePage[x][y] = -1;
6271 CustomValue[x][y] = 0;
6273 InitField_WithBug2(x, y, FALSE);
6275 TEST_DrawLevelField(x, y);
6277 TestIfElementTouchesCustomElement(x, y);
6279 if (GFX_CRUMBLED(element))
6280 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6282 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6283 StorePlayer[x][y] = 0;
6285 if (IS_PLAYER_ELEMENT(element))
6286 RelocatePlayer(x, y, element);
6288 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6290 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6291 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6294 TEST_DrawLevelFieldCrumbled(x, y);
6296 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6298 DrawLevelElement(x, y, Back[x][y]);
6299 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6301 else if (IS_WALKABLE_UNDER(Back[x][y]))
6303 DrawLevelGraphic(x, y, graphic, frame);
6304 DrawLevelElementThruMask(x, y, Back[x][y]);
6306 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6307 DrawLevelGraphic(x, y, graphic, frame);
6311 static void DynaExplode(int ex, int ey)
6314 int dynabomb_element = Tile[ex][ey];
6315 int dynabomb_size = 1;
6316 boolean dynabomb_xl = FALSE;
6317 struct PlayerInfo *player;
6318 struct XY *xy = xy_topdown;
6320 if (IS_ACTIVE_BOMB(dynabomb_element))
6322 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6323 dynabomb_size = player->dynabomb_size;
6324 dynabomb_xl = player->dynabomb_xl;
6325 player->dynabombs_left++;
6328 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6330 for (i = 0; i < NUM_DIRECTIONS; i++)
6332 for (j = 1; j <= dynabomb_size; j++)
6334 int x = ex + j * xy[i].x;
6335 int y = ey + j * xy[i].y;
6338 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6341 element = Tile[x][y];
6343 // do not restart explosions of fields with active bombs
6344 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6347 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6349 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6350 !IS_DIGGABLE(element) && !dynabomb_xl)
6356 void Bang(int x, int y)
6358 int element = MovingOrBlocked2Element(x, y);
6359 int explosion_type = EX_TYPE_NORMAL;
6361 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6363 struct PlayerInfo *player = PLAYERINFO(x, y);
6365 element = Tile[x][y] = player->initial_element;
6367 if (level.use_explosion_element[player->index_nr])
6369 int explosion_element = level.explosion_element[player->index_nr];
6371 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6372 explosion_type = EX_TYPE_CROSS;
6373 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6374 explosion_type = EX_TYPE_CENTER;
6382 case EL_BD_BUTTERFLY:
6385 case EL_DARK_YAMYAM:
6389 RaiseScoreElement(element);
6392 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6393 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6394 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6395 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6396 case EL_DYNABOMB_INCREASE_NUMBER:
6397 case EL_DYNABOMB_INCREASE_SIZE:
6398 case EL_DYNABOMB_INCREASE_POWER:
6399 explosion_type = EX_TYPE_DYNA;
6402 case EL_DC_LANDMINE:
6403 explosion_type = EX_TYPE_CENTER;
6408 case EL_LAMP_ACTIVE:
6409 case EL_AMOEBA_TO_DIAMOND:
6410 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6411 explosion_type = EX_TYPE_CENTER;
6415 if (element_info[element].explosion_type == EXPLODES_CROSS)
6416 explosion_type = EX_TYPE_CROSS;
6417 else if (element_info[element].explosion_type == EXPLODES_1X1)
6418 explosion_type = EX_TYPE_CENTER;
6422 if (explosion_type == EX_TYPE_DYNA)
6425 Explode(x, y, EX_PHASE_START, explosion_type);
6427 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6430 static void SplashAcid(int x, int y)
6432 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6433 (!IN_LEV_FIELD(x - 1, y - 2) ||
6434 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6435 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6437 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6438 (!IN_LEV_FIELD(x + 1, y - 2) ||
6439 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6440 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6442 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6445 static void InitBeltMovement(void)
6447 static int belt_base_element[4] =
6449 EL_CONVEYOR_BELT_1_LEFT,
6450 EL_CONVEYOR_BELT_2_LEFT,
6451 EL_CONVEYOR_BELT_3_LEFT,
6452 EL_CONVEYOR_BELT_4_LEFT
6454 static int belt_base_active_element[4] =
6456 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6457 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6458 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6459 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6464 // set frame order for belt animation graphic according to belt direction
6465 for (i = 0; i < NUM_BELTS; i++)
6469 for (j = 0; j < NUM_BELT_PARTS; j++)
6471 int element = belt_base_active_element[belt_nr] + j;
6472 int graphic_1 = el2img(element);
6473 int graphic_2 = el2panelimg(element);
6475 if (game.belt_dir[i] == MV_LEFT)
6477 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6478 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6482 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6483 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6488 SCAN_PLAYFIELD(x, y)
6490 int element = Tile[x][y];
6492 for (i = 0; i < NUM_BELTS; i++)
6494 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6496 int e_belt_nr = getBeltNrFromBeltElement(element);
6499 if (e_belt_nr == belt_nr)
6501 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6503 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6510 static void ToggleBeltSwitch(int x, int y)
6512 static int belt_base_element[4] =
6514 EL_CONVEYOR_BELT_1_LEFT,
6515 EL_CONVEYOR_BELT_2_LEFT,
6516 EL_CONVEYOR_BELT_3_LEFT,
6517 EL_CONVEYOR_BELT_4_LEFT
6519 static int belt_base_active_element[4] =
6521 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6522 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6523 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6524 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6526 static int belt_base_switch_element[4] =
6528 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6529 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6530 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6531 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6533 static int belt_move_dir[4] =
6541 int element = Tile[x][y];
6542 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6543 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6544 int belt_dir = belt_move_dir[belt_dir_nr];
6547 if (!IS_BELT_SWITCH(element))
6550 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6551 game.belt_dir[belt_nr] = belt_dir;
6553 if (belt_dir_nr == 3)
6556 // set frame order for belt animation graphic according to belt direction
6557 for (i = 0; i < NUM_BELT_PARTS; i++)
6559 int element = belt_base_active_element[belt_nr] + i;
6560 int graphic_1 = el2img(element);
6561 int graphic_2 = el2panelimg(element);
6563 if (belt_dir == MV_LEFT)
6565 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6566 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6570 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6571 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6575 SCAN_PLAYFIELD(xx, yy)
6577 int element = Tile[xx][yy];
6579 if (IS_BELT_SWITCH(element))
6581 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6583 if (e_belt_nr == belt_nr)
6585 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6586 TEST_DrawLevelField(xx, yy);
6589 else if (IS_BELT(element) && belt_dir != MV_NONE)
6591 int e_belt_nr = getBeltNrFromBeltElement(element);
6593 if (e_belt_nr == belt_nr)
6595 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6597 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6598 TEST_DrawLevelField(xx, yy);
6601 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6603 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6605 if (e_belt_nr == belt_nr)
6607 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6609 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6610 TEST_DrawLevelField(xx, yy);
6616 static void ToggleSwitchgateSwitch(void)
6620 game.switchgate_pos = !game.switchgate_pos;
6622 SCAN_PLAYFIELD(xx, yy)
6624 int element = Tile[xx][yy];
6626 if (element == EL_SWITCHGATE_SWITCH_UP)
6628 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6629 TEST_DrawLevelField(xx, yy);
6631 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6633 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6634 TEST_DrawLevelField(xx, yy);
6636 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6638 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6639 TEST_DrawLevelField(xx, yy);
6641 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6643 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6644 TEST_DrawLevelField(xx, yy);
6646 else if (element == EL_SWITCHGATE_OPEN ||
6647 element == EL_SWITCHGATE_OPENING)
6649 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6651 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6653 else if (element == EL_SWITCHGATE_CLOSED ||
6654 element == EL_SWITCHGATE_CLOSING)
6656 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6658 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6663 static int getInvisibleActiveFromInvisibleElement(int element)
6665 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6666 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6667 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6671 static int getInvisibleFromInvisibleActiveElement(int element)
6673 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6674 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6675 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6679 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6683 SCAN_PLAYFIELD(x, y)
6685 int element = Tile[x][y];
6687 if (element == EL_LIGHT_SWITCH &&
6688 game.light_time_left > 0)
6690 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6691 TEST_DrawLevelField(x, y);
6693 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6694 game.light_time_left == 0)
6696 Tile[x][y] = EL_LIGHT_SWITCH;
6697 TEST_DrawLevelField(x, y);
6699 else if (element == EL_EMC_DRIPPER &&
6700 game.light_time_left > 0)
6702 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6703 TEST_DrawLevelField(x, y);
6705 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6706 game.light_time_left == 0)
6708 Tile[x][y] = EL_EMC_DRIPPER;
6709 TEST_DrawLevelField(x, y);
6711 else if (element == EL_INVISIBLE_STEELWALL ||
6712 element == EL_INVISIBLE_WALL ||
6713 element == EL_INVISIBLE_SAND)
6715 if (game.light_time_left > 0)
6716 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6718 TEST_DrawLevelField(x, y);
6720 // uncrumble neighbour fields, if needed
6721 if (element == EL_INVISIBLE_SAND)
6722 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6724 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6725 element == EL_INVISIBLE_WALL_ACTIVE ||
6726 element == EL_INVISIBLE_SAND_ACTIVE)
6728 if (game.light_time_left == 0)
6729 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6731 TEST_DrawLevelField(x, y);
6733 // re-crumble neighbour fields, if needed
6734 if (element == EL_INVISIBLE_SAND)
6735 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6740 static void RedrawAllInvisibleElementsForLenses(void)
6744 SCAN_PLAYFIELD(x, y)
6746 int element = Tile[x][y];
6748 if (element == EL_EMC_DRIPPER &&
6749 game.lenses_time_left > 0)
6751 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6752 TEST_DrawLevelField(x, y);
6754 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6755 game.lenses_time_left == 0)
6757 Tile[x][y] = EL_EMC_DRIPPER;
6758 TEST_DrawLevelField(x, y);
6760 else if (element == EL_INVISIBLE_STEELWALL ||
6761 element == EL_INVISIBLE_WALL ||
6762 element == EL_INVISIBLE_SAND)
6764 if (game.lenses_time_left > 0)
6765 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6767 TEST_DrawLevelField(x, y);
6769 // uncrumble neighbour fields, if needed
6770 if (element == EL_INVISIBLE_SAND)
6771 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6773 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6774 element == EL_INVISIBLE_WALL_ACTIVE ||
6775 element == EL_INVISIBLE_SAND_ACTIVE)
6777 if (game.lenses_time_left == 0)
6778 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6780 TEST_DrawLevelField(x, y);
6782 // re-crumble neighbour fields, if needed
6783 if (element == EL_INVISIBLE_SAND)
6784 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6789 static void RedrawAllInvisibleElementsForMagnifier(void)
6793 SCAN_PLAYFIELD(x, y)
6795 int element = Tile[x][y];
6797 if (element == EL_EMC_FAKE_GRASS &&
6798 game.magnify_time_left > 0)
6800 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6801 TEST_DrawLevelField(x, y);
6803 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6804 game.magnify_time_left == 0)
6806 Tile[x][y] = EL_EMC_FAKE_GRASS;
6807 TEST_DrawLevelField(x, y);
6809 else if (IS_GATE_GRAY(element) &&
6810 game.magnify_time_left > 0)
6812 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6813 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6814 IS_EM_GATE_GRAY(element) ?
6815 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6816 IS_EMC_GATE_GRAY(element) ?
6817 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6818 IS_DC_GATE_GRAY(element) ?
6819 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6821 TEST_DrawLevelField(x, y);
6823 else if (IS_GATE_GRAY_ACTIVE(element) &&
6824 game.magnify_time_left == 0)
6826 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6827 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6828 IS_EM_GATE_GRAY_ACTIVE(element) ?
6829 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6830 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6831 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6832 IS_DC_GATE_GRAY_ACTIVE(element) ?
6833 EL_DC_GATE_WHITE_GRAY :
6835 TEST_DrawLevelField(x, y);
6840 static void ToggleLightSwitch(int x, int y)
6842 int element = Tile[x][y];
6844 game.light_time_left =
6845 (element == EL_LIGHT_SWITCH ?
6846 level.time_light * FRAMES_PER_SECOND : 0);
6848 RedrawAllLightSwitchesAndInvisibleElements();
6851 static void ActivateTimegateSwitch(int x, int y)
6855 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6857 SCAN_PLAYFIELD(xx, yy)
6859 int element = Tile[xx][yy];
6861 if (element == EL_TIMEGATE_CLOSED ||
6862 element == EL_TIMEGATE_CLOSING)
6864 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6865 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6869 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6871 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6872 TEST_DrawLevelField(xx, yy);
6878 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6879 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6882 static void Impact(int x, int y)
6884 boolean last_line = (y == lev_fieldy - 1);
6885 boolean object_hit = FALSE;
6886 boolean impact = (last_line || object_hit);
6887 int element = Tile[x][y];
6888 int smashed = EL_STEELWALL;
6890 if (!last_line) // check if element below was hit
6892 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6895 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6896 MovDir[x][y + 1] != MV_DOWN ||
6897 MovPos[x][y + 1] <= TILEY / 2));
6899 // do not smash moving elements that left the smashed field in time
6900 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6901 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6904 #if USE_QUICKSAND_IMPACT_BUGFIX
6905 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6907 RemoveMovingField(x, y + 1);
6908 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6909 Tile[x][y + 2] = EL_ROCK;
6910 TEST_DrawLevelField(x, y + 2);
6915 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6917 RemoveMovingField(x, y + 1);
6918 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6919 Tile[x][y + 2] = EL_ROCK;
6920 TEST_DrawLevelField(x, y + 2);
6927 smashed = MovingOrBlocked2Element(x, y + 1);
6929 impact = (last_line || object_hit);
6932 if (!last_line && smashed == EL_ACID) // element falls into acid
6934 SplashAcid(x, y + 1);
6938 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6939 // only reset graphic animation if graphic really changes after impact
6941 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6943 ResetGfxAnimation(x, y);
6944 TEST_DrawLevelField(x, y);
6947 if (impact && CAN_EXPLODE_IMPACT(element))
6952 else if (impact && element == EL_PEARL &&
6953 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6955 ResetGfxAnimation(x, y);
6957 Tile[x][y] = EL_PEARL_BREAKING;
6958 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6961 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6963 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6968 if (impact && element == EL_AMOEBA_DROP)
6970 if (object_hit && IS_PLAYER(x, y + 1))
6971 KillPlayerUnlessEnemyProtected(x, y + 1);
6972 else if (object_hit && smashed == EL_PENGUIN)
6976 Tile[x][y] = EL_AMOEBA_GROWING;
6977 Store[x][y] = EL_AMOEBA_WET;
6979 ResetRandomAnimationValue(x, y);
6984 if (object_hit) // check which object was hit
6986 if ((CAN_PASS_MAGIC_WALL(element) &&
6987 (smashed == EL_MAGIC_WALL ||
6988 smashed == EL_BD_MAGIC_WALL)) ||
6989 (CAN_PASS_DC_MAGIC_WALL(element) &&
6990 smashed == EL_DC_MAGIC_WALL))
6993 int activated_magic_wall =
6994 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6995 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6996 EL_DC_MAGIC_WALL_ACTIVE);
6998 // activate magic wall / mill
6999 SCAN_PLAYFIELD(xx, yy)
7001 if (Tile[xx][yy] == smashed)
7002 Tile[xx][yy] = activated_magic_wall;
7005 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
7006 game.magic_wall_active = TRUE;
7008 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
7009 SND_MAGIC_WALL_ACTIVATING :
7010 smashed == EL_BD_MAGIC_WALL ?
7011 SND_BD_MAGIC_WALL_ACTIVATING :
7012 SND_DC_MAGIC_WALL_ACTIVATING));
7015 if (IS_PLAYER(x, y + 1))
7017 if (CAN_SMASH_PLAYER(element))
7019 KillPlayerUnlessEnemyProtected(x, y + 1);
7023 else if (smashed == EL_PENGUIN)
7025 if (CAN_SMASH_PLAYER(element))
7031 else if (element == EL_BD_DIAMOND)
7033 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
7039 else if (((element == EL_SP_INFOTRON ||
7040 element == EL_SP_ZONK) &&
7041 (smashed == EL_SP_SNIKSNAK ||
7042 smashed == EL_SP_ELECTRON ||
7043 smashed == EL_SP_DISK_ORANGE)) ||
7044 (element == EL_SP_INFOTRON &&
7045 smashed == EL_SP_DISK_YELLOW))
7050 else if (CAN_SMASH_EVERYTHING(element))
7052 if (IS_CLASSIC_ENEMY(smashed) ||
7053 CAN_EXPLODE_SMASHED(smashed))
7058 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
7060 if (smashed == EL_LAMP ||
7061 smashed == EL_LAMP_ACTIVE)
7066 else if (smashed == EL_NUT)
7068 Tile[x][y + 1] = EL_NUT_BREAKING;
7069 PlayLevelSound(x, y, SND_NUT_BREAKING);
7070 RaiseScoreElement(EL_NUT);
7073 else if (smashed == EL_PEARL)
7075 ResetGfxAnimation(x, y);
7077 Tile[x][y + 1] = EL_PEARL_BREAKING;
7078 PlayLevelSound(x, y, SND_PEARL_BREAKING);
7081 else if (smashed == EL_DIAMOND)
7083 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
7084 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
7087 else if (IS_BELT_SWITCH(smashed))
7089 ToggleBeltSwitch(x, y + 1);
7091 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
7092 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
7093 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
7094 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
7096 ToggleSwitchgateSwitch();
7098 else if (smashed == EL_LIGHT_SWITCH ||
7099 smashed == EL_LIGHT_SWITCH_ACTIVE)
7101 ToggleLightSwitch(x, y + 1);
7105 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7107 CheckElementChangeBySide(x, y + 1, smashed, element,
7108 CE_SWITCHED, CH_SIDE_TOP);
7109 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
7115 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7120 // play sound of magic wall / mill
7122 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7123 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
7124 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
7126 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7127 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
7128 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7129 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
7130 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7131 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
7136 // play sound of object that hits the ground
7137 if (last_line || object_hit)
7138 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
7141 static void TurnRoundExt(int x, int y)
7153 { 0, 0 }, { 0, 0 }, { 0, 0 },
7158 int left, right, back;
7162 { MV_DOWN, MV_UP, MV_RIGHT },
7163 { MV_UP, MV_DOWN, MV_LEFT },
7165 { MV_LEFT, MV_RIGHT, MV_DOWN },
7169 { MV_RIGHT, MV_LEFT, MV_UP }
7172 int element = Tile[x][y];
7173 int move_pattern = element_info[element].move_pattern;
7175 int old_move_dir = MovDir[x][y];
7176 int left_dir = turn[old_move_dir].left;
7177 int right_dir = turn[old_move_dir].right;
7178 int back_dir = turn[old_move_dir].back;
7180 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
7181 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
7182 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
7183 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
7185 int left_x = x + left_dx, left_y = y + left_dy;
7186 int right_x = x + right_dx, right_y = y + right_dy;
7187 int move_x = x + move_dx, move_y = y + move_dy;
7191 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
7193 TestIfBadThingTouchesOtherBadThing(x, y);
7195 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
7196 MovDir[x][y] = right_dir;
7197 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7198 MovDir[x][y] = left_dir;
7200 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7202 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7205 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7207 TestIfBadThingTouchesOtherBadThing(x, y);
7209 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7210 MovDir[x][y] = left_dir;
7211 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7212 MovDir[x][y] = right_dir;
7214 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7216 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7219 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7221 TestIfBadThingTouchesOtherBadThing(x, y);
7223 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7224 MovDir[x][y] = left_dir;
7225 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7226 MovDir[x][y] = right_dir;
7228 if (MovDir[x][y] != old_move_dir)
7231 else if (element == EL_YAMYAM)
7233 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7234 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7236 if (can_turn_left && can_turn_right)
7237 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7238 else if (can_turn_left)
7239 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7240 else if (can_turn_right)
7241 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7243 MovDir[x][y] = back_dir;
7245 MovDelay[x][y] = 16 + 16 * RND(3);
7247 else if (element == EL_DARK_YAMYAM)
7249 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7251 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7254 if (can_turn_left && can_turn_right)
7255 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7256 else if (can_turn_left)
7257 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7258 else if (can_turn_right)
7259 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7261 MovDir[x][y] = back_dir;
7263 MovDelay[x][y] = 16 + 16 * RND(3);
7265 else if (element == EL_PACMAN)
7267 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7268 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7270 if (can_turn_left && can_turn_right)
7271 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7272 else if (can_turn_left)
7273 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7274 else if (can_turn_right)
7275 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7277 MovDir[x][y] = back_dir;
7279 MovDelay[x][y] = 6 + RND(40);
7281 else if (element == EL_PIG)
7283 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7284 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7285 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7286 boolean should_turn_left, should_turn_right, should_move_on;
7288 int rnd = RND(rnd_value);
7290 should_turn_left = (can_turn_left &&
7292 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7293 y + back_dy + left_dy)));
7294 should_turn_right = (can_turn_right &&
7296 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7297 y + back_dy + right_dy)));
7298 should_move_on = (can_move_on &&
7301 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7302 y + move_dy + left_dy) ||
7303 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7304 y + move_dy + right_dy)));
7306 if (should_turn_left || should_turn_right || should_move_on)
7308 if (should_turn_left && should_turn_right && should_move_on)
7309 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7310 rnd < 2 * rnd_value / 3 ? right_dir :
7312 else if (should_turn_left && should_turn_right)
7313 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7314 else if (should_turn_left && should_move_on)
7315 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7316 else if (should_turn_right && should_move_on)
7317 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7318 else if (should_turn_left)
7319 MovDir[x][y] = left_dir;
7320 else if (should_turn_right)
7321 MovDir[x][y] = right_dir;
7322 else if (should_move_on)
7323 MovDir[x][y] = old_move_dir;
7325 else if (can_move_on && rnd > rnd_value / 8)
7326 MovDir[x][y] = old_move_dir;
7327 else if (can_turn_left && can_turn_right)
7328 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7329 else if (can_turn_left && rnd > rnd_value / 8)
7330 MovDir[x][y] = left_dir;
7331 else if (can_turn_right && rnd > rnd_value/8)
7332 MovDir[x][y] = right_dir;
7334 MovDir[x][y] = back_dir;
7336 xx = x + move_xy[MovDir[x][y]].dx;
7337 yy = y + move_xy[MovDir[x][y]].dy;
7339 if (!IN_LEV_FIELD(xx, yy) ||
7340 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7341 MovDir[x][y] = old_move_dir;
7345 else if (element == EL_DRAGON)
7347 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7348 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7349 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7351 int rnd = RND(rnd_value);
7353 if (can_move_on && rnd > rnd_value / 8)
7354 MovDir[x][y] = old_move_dir;
7355 else if (can_turn_left && can_turn_right)
7356 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7357 else if (can_turn_left && rnd > rnd_value / 8)
7358 MovDir[x][y] = left_dir;
7359 else if (can_turn_right && rnd > rnd_value / 8)
7360 MovDir[x][y] = right_dir;
7362 MovDir[x][y] = back_dir;
7364 xx = x + move_xy[MovDir[x][y]].dx;
7365 yy = y + move_xy[MovDir[x][y]].dy;
7367 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7368 MovDir[x][y] = old_move_dir;
7372 else if (element == EL_MOLE)
7374 boolean can_move_on =
7375 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7376 IS_AMOEBOID(Tile[move_x][move_y]) ||
7377 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7380 boolean can_turn_left =
7381 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7382 IS_AMOEBOID(Tile[left_x][left_y])));
7384 boolean can_turn_right =
7385 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7386 IS_AMOEBOID(Tile[right_x][right_y])));
7388 if (can_turn_left && can_turn_right)
7389 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7390 else if (can_turn_left)
7391 MovDir[x][y] = left_dir;
7393 MovDir[x][y] = right_dir;
7396 if (MovDir[x][y] != old_move_dir)
7399 else if (element == EL_BALLOON)
7401 MovDir[x][y] = game.wind_direction;
7404 else if (element == EL_SPRING)
7406 if (MovDir[x][y] & MV_HORIZONTAL)
7408 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7409 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7411 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7412 ResetGfxAnimation(move_x, move_y);
7413 TEST_DrawLevelField(move_x, move_y);
7415 MovDir[x][y] = back_dir;
7417 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7418 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7419 MovDir[x][y] = MV_NONE;
7424 else if (element == EL_ROBOT ||
7425 element == EL_SATELLITE ||
7426 element == EL_PENGUIN ||
7427 element == EL_EMC_ANDROID)
7429 int attr_x = -1, attr_y = -1;
7431 if (game.all_players_gone)
7433 attr_x = game.exit_x;
7434 attr_y = game.exit_y;
7440 for (i = 0; i < MAX_PLAYERS; i++)
7442 struct PlayerInfo *player = &stored_player[i];
7443 int jx = player->jx, jy = player->jy;
7445 if (!player->active)
7449 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7457 if (element == EL_ROBOT &&
7458 game.robot_wheel_x >= 0 &&
7459 game.robot_wheel_y >= 0 &&
7460 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7461 game.engine_version < VERSION_IDENT(3,1,0,0)))
7463 attr_x = game.robot_wheel_x;
7464 attr_y = game.robot_wheel_y;
7467 if (element == EL_PENGUIN)
7470 struct XY *xy = xy_topdown;
7472 for (i = 0; i < NUM_DIRECTIONS; i++)
7474 int ex = x + xy[i].x;
7475 int ey = y + xy[i].y;
7477 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7478 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7479 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7480 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7489 MovDir[x][y] = MV_NONE;
7491 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7492 else if (attr_x > x)
7493 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7495 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7496 else if (attr_y > y)
7497 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7499 if (element == EL_ROBOT)
7503 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7504 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7505 Moving2Blocked(x, y, &newx, &newy);
7507 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7508 MovDelay[x][y] = 8 + 8 * !RND(3);
7510 MovDelay[x][y] = 16;
7512 else if (element == EL_PENGUIN)
7518 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7520 boolean first_horiz = RND(2);
7521 int new_move_dir = MovDir[x][y];
7524 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7525 Moving2Blocked(x, y, &newx, &newy);
7527 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7531 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7532 Moving2Blocked(x, y, &newx, &newy);
7534 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7537 MovDir[x][y] = old_move_dir;
7541 else if (element == EL_SATELLITE)
7547 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7549 boolean first_horiz = RND(2);
7550 int new_move_dir = MovDir[x][y];
7553 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7554 Moving2Blocked(x, y, &newx, &newy);
7556 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7560 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7561 Moving2Blocked(x, y, &newx, &newy);
7563 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7566 MovDir[x][y] = old_move_dir;
7570 else if (element == EL_EMC_ANDROID)
7572 static int check_pos[16] =
7574 -1, // 0 => (invalid)
7577 -1, // 3 => (invalid)
7579 0, // 5 => MV_LEFT | MV_UP
7580 2, // 6 => MV_RIGHT | MV_UP
7581 -1, // 7 => (invalid)
7583 6, // 9 => MV_LEFT | MV_DOWN
7584 4, // 10 => MV_RIGHT | MV_DOWN
7585 -1, // 11 => (invalid)
7586 -1, // 12 => (invalid)
7587 -1, // 13 => (invalid)
7588 -1, // 14 => (invalid)
7589 -1, // 15 => (invalid)
7597 { -1, -1, MV_LEFT | MV_UP },
7599 { +1, -1, MV_RIGHT | MV_UP },
7600 { +1, 0, MV_RIGHT },
7601 { +1, +1, MV_RIGHT | MV_DOWN },
7603 { -1, +1, MV_LEFT | MV_DOWN },
7606 int start_pos, check_order;
7607 boolean can_clone = FALSE;
7610 // check if there is any free field around current position
7611 for (i = 0; i < 8; i++)
7613 int newx = x + check_xy[i].dx;
7614 int newy = y + check_xy[i].dy;
7616 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7624 if (can_clone) // randomly find an element to clone
7628 start_pos = check_pos[RND(8)];
7629 check_order = (RND(2) ? -1 : +1);
7631 for (i = 0; i < 8; i++)
7633 int pos_raw = start_pos + i * check_order;
7634 int pos = (pos_raw + 8) % 8;
7635 int newx = x + check_xy[pos].dx;
7636 int newy = y + check_xy[pos].dy;
7638 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7640 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7641 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7643 Store[x][y] = Tile[newx][newy];
7652 if (can_clone) // randomly find a direction to move
7656 start_pos = check_pos[RND(8)];
7657 check_order = (RND(2) ? -1 : +1);
7659 for (i = 0; i < 8; i++)
7661 int pos_raw = start_pos + i * check_order;
7662 int pos = (pos_raw + 8) % 8;
7663 int newx = x + check_xy[pos].dx;
7664 int newy = y + check_xy[pos].dy;
7665 int new_move_dir = check_xy[pos].dir;
7667 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7669 MovDir[x][y] = new_move_dir;
7670 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7679 if (can_clone) // cloning and moving successful
7682 // cannot clone -- try to move towards player
7684 start_pos = check_pos[MovDir[x][y] & 0x0f];
7685 check_order = (RND(2) ? -1 : +1);
7687 for (i = 0; i < 3; i++)
7689 // first check start_pos, then previous/next or (next/previous) pos
7690 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7691 int pos = (pos_raw + 8) % 8;
7692 int newx = x + check_xy[pos].dx;
7693 int newy = y + check_xy[pos].dy;
7694 int new_move_dir = check_xy[pos].dir;
7696 if (IS_PLAYER(newx, newy))
7699 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7701 MovDir[x][y] = new_move_dir;
7702 MovDelay[x][y] = level.android_move_time * 8 + 1;
7709 else if (move_pattern == MV_TURNING_LEFT ||
7710 move_pattern == MV_TURNING_RIGHT ||
7711 move_pattern == MV_TURNING_LEFT_RIGHT ||
7712 move_pattern == MV_TURNING_RIGHT_LEFT ||
7713 move_pattern == MV_TURNING_RANDOM ||
7714 move_pattern == MV_ALL_DIRECTIONS)
7716 boolean can_turn_left =
7717 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7718 boolean can_turn_right =
7719 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y);
7721 if (element_info[element].move_stepsize == 0) // "not moving"
7724 if (move_pattern == MV_TURNING_LEFT)
7725 MovDir[x][y] = left_dir;
7726 else if (move_pattern == MV_TURNING_RIGHT)
7727 MovDir[x][y] = right_dir;
7728 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7729 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7730 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7731 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7732 else if (move_pattern == MV_TURNING_RANDOM)
7733 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7734 can_turn_right && !can_turn_left ? right_dir :
7735 RND(2) ? left_dir : right_dir);
7736 else if (can_turn_left && can_turn_right)
7737 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7738 else if (can_turn_left)
7739 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7740 else if (can_turn_right)
7741 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7743 MovDir[x][y] = back_dir;
7745 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7747 else if (move_pattern == MV_HORIZONTAL ||
7748 move_pattern == MV_VERTICAL)
7750 if (move_pattern & old_move_dir)
7751 MovDir[x][y] = back_dir;
7752 else if (move_pattern == MV_HORIZONTAL)
7753 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7754 else if (move_pattern == MV_VERTICAL)
7755 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7757 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7759 else if (move_pattern & MV_ANY_DIRECTION)
7761 MovDir[x][y] = move_pattern;
7762 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7764 else if (move_pattern & MV_WIND_DIRECTION)
7766 MovDir[x][y] = game.wind_direction;
7767 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7769 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7771 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7772 MovDir[x][y] = left_dir;
7773 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7774 MovDir[x][y] = right_dir;
7776 if (MovDir[x][y] != old_move_dir)
7777 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7779 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7781 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7782 MovDir[x][y] = right_dir;
7783 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7784 MovDir[x][y] = left_dir;
7786 if (MovDir[x][y] != old_move_dir)
7787 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7789 else if (move_pattern == MV_TOWARDS_PLAYER ||
7790 move_pattern == MV_AWAY_FROM_PLAYER)
7792 int attr_x = -1, attr_y = -1;
7794 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7796 if (game.all_players_gone)
7798 attr_x = game.exit_x;
7799 attr_y = game.exit_y;
7805 for (i = 0; i < MAX_PLAYERS; i++)
7807 struct PlayerInfo *player = &stored_player[i];
7808 int jx = player->jx, jy = player->jy;
7810 if (!player->active)
7814 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7822 MovDir[x][y] = MV_NONE;
7824 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7825 else if (attr_x > x)
7826 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7828 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7829 else if (attr_y > y)
7830 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7832 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7834 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7836 boolean first_horiz = RND(2);
7837 int new_move_dir = MovDir[x][y];
7839 if (element_info[element].move_stepsize == 0) // "not moving"
7841 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7842 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7848 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7849 Moving2Blocked(x, y, &newx, &newy);
7851 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7855 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7856 Moving2Blocked(x, y, &newx, &newy);
7858 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7861 MovDir[x][y] = old_move_dir;
7864 else if (move_pattern == MV_WHEN_PUSHED ||
7865 move_pattern == MV_WHEN_DROPPED)
7867 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7868 MovDir[x][y] = MV_NONE;
7872 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7874 struct XY *test_xy = xy_topdown;
7875 static int test_dir[4] =
7882 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7883 int move_preference = -1000000; // start with very low preference
7884 int new_move_dir = MV_NONE;
7885 int start_test = RND(4);
7888 for (i = 0; i < NUM_DIRECTIONS; i++)
7890 int j = (start_test + i) % 4;
7891 int move_dir = test_dir[j];
7892 int move_dir_preference;
7894 xx = x + test_xy[j].x;
7895 yy = y + test_xy[j].y;
7897 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7898 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7900 new_move_dir = move_dir;
7905 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7908 move_dir_preference = -1 * RunnerVisit[xx][yy];
7909 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7910 move_dir_preference = PlayerVisit[xx][yy];
7912 if (move_dir_preference > move_preference)
7914 // prefer field that has not been visited for the longest time
7915 move_preference = move_dir_preference;
7916 new_move_dir = move_dir;
7918 else if (move_dir_preference == move_preference &&
7919 move_dir == old_move_dir)
7921 // prefer last direction when all directions are preferred equally
7922 move_preference = move_dir_preference;
7923 new_move_dir = move_dir;
7927 MovDir[x][y] = new_move_dir;
7928 if (old_move_dir != new_move_dir)
7929 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7933 static void TurnRound(int x, int y)
7935 int direction = MovDir[x][y];
7939 GfxDir[x][y] = MovDir[x][y];
7941 if (direction != MovDir[x][y])
7945 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7947 ResetGfxFrame(x, y);
7950 static boolean JustBeingPushed(int x, int y)
7954 for (i = 0; i < MAX_PLAYERS; i++)
7956 struct PlayerInfo *player = &stored_player[i];
7958 if (player->active && player->is_pushing && player->MovPos)
7960 int next_jx = player->jx + (player->jx - player->last_jx);
7961 int next_jy = player->jy + (player->jy - player->last_jy);
7963 if (x == next_jx && y == next_jy)
7971 static void StartMoving(int x, int y)
7973 boolean started_moving = FALSE; // some elements can fall _and_ move
7974 int element = Tile[x][y];
7979 if (MovDelay[x][y] == 0)
7980 GfxAction[x][y] = ACTION_DEFAULT;
7982 if (CAN_FALL(element) && y < lev_fieldy - 1)
7984 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7985 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7986 if (JustBeingPushed(x, y))
7989 if (element == EL_QUICKSAND_FULL)
7991 if (IS_FREE(x, y + 1))
7993 InitMovingField(x, y, MV_DOWN);
7994 started_moving = TRUE;
7996 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7997 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7998 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7999 Store[x][y] = EL_ROCK;
8001 Store[x][y] = EL_ROCK;
8004 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
8006 else if (Tile[x][y + 1] == EL_QUICKSAND_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_FILLING);
8026 Tile[x][y] = EL_QUICKSAND_EMPTY;
8027 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8028 Store[x][y + 1] = Store[x][y];
8031 PlayLevelSoundAction(x, y, ACTION_FILLING);
8033 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8035 if (!MovDelay[x][y])
8037 MovDelay[x][y] = TILEY + 1;
8039 ResetGfxAnimation(x, y);
8040 ResetGfxAnimation(x, y + 1);
8045 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
8046 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8053 Tile[x][y] = EL_QUICKSAND_EMPTY;
8054 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8055 Store[x][y + 1] = Store[x][y];
8058 PlayLevelSoundAction(x, y, ACTION_FILLING);
8061 else if (element == EL_QUICKSAND_FAST_FULL)
8063 if (IS_FREE(x, y + 1))
8065 InitMovingField(x, y, MV_DOWN);
8066 started_moving = TRUE;
8068 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
8069 #if USE_QUICKSAND_BD_ROCK_BUGFIX
8070 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
8071 Store[x][y] = EL_ROCK;
8073 Store[x][y] = EL_ROCK;
8076 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
8078 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_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_FAST_FILLING);
8098 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8099 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8100 Store[x][y + 1] = Store[x][y];
8103 PlayLevelSoundAction(x, y, ACTION_FILLING);
8105 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8107 if (!MovDelay[x][y])
8109 MovDelay[x][y] = TILEY + 1;
8111 ResetGfxAnimation(x, y);
8112 ResetGfxAnimation(x, y + 1);
8117 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8118 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8125 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8126 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8127 Store[x][y + 1] = Store[x][y];
8130 PlayLevelSoundAction(x, y, ACTION_FILLING);
8133 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8134 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8136 InitMovingField(x, y, MV_DOWN);
8137 started_moving = TRUE;
8139 Tile[x][y] = EL_QUICKSAND_FILLING;
8140 Store[x][y] = element;
8142 PlayLevelSoundAction(x, y, ACTION_FILLING);
8144 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8145 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8147 InitMovingField(x, y, MV_DOWN);
8148 started_moving = TRUE;
8150 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
8151 Store[x][y] = element;
8153 PlayLevelSoundAction(x, y, ACTION_FILLING);
8155 else if (element == EL_MAGIC_WALL_FULL)
8157 if (IS_FREE(x, y + 1))
8159 InitMovingField(x, y, MV_DOWN);
8160 started_moving = TRUE;
8162 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
8163 Store[x][y] = EL_CHANGED(Store[x][y]);
8165 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
8167 if (!MovDelay[x][y])
8168 MovDelay[x][y] = TILEY / 4 + 1;
8177 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
8178 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
8179 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8183 else if (element == EL_BD_MAGIC_WALL_FULL)
8185 if (IS_FREE(x, y + 1))
8187 InitMovingField(x, y, MV_DOWN);
8188 started_moving = TRUE;
8190 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8191 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8193 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8195 if (!MovDelay[x][y])
8196 MovDelay[x][y] = TILEY / 4 + 1;
8205 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8206 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8207 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8211 else if (element == EL_DC_MAGIC_WALL_FULL)
8213 if (IS_FREE(x, y + 1))
8215 InitMovingField(x, y, MV_DOWN);
8216 started_moving = TRUE;
8218 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8219 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8221 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8223 if (!MovDelay[x][y])
8224 MovDelay[x][y] = TILEY / 4 + 1;
8233 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8234 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8235 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8239 else if ((CAN_PASS_MAGIC_WALL(element) &&
8240 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8241 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8242 (CAN_PASS_DC_MAGIC_WALL(element) &&
8243 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8246 InitMovingField(x, y, MV_DOWN);
8247 started_moving = TRUE;
8250 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8251 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8252 EL_DC_MAGIC_WALL_FILLING);
8253 Store[x][y] = element;
8255 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8257 SplashAcid(x, y + 1);
8259 InitMovingField(x, y, MV_DOWN);
8260 started_moving = TRUE;
8262 Store[x][y] = EL_ACID;
8265 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8266 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8267 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8268 CAN_FALL(element) && WasJustFalling[x][y] &&
8269 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8271 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8272 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8273 (Tile[x][y + 1] == EL_BLOCKED)))
8275 /* this is needed for a special case not covered by calling "Impact()"
8276 from "ContinueMoving()": if an element moves to a tile directly below
8277 another element which was just falling on that tile (which was empty
8278 in the previous frame), the falling element above would just stop
8279 instead of smashing the element below (in previous version, the above
8280 element was just checked for "moving" instead of "falling", resulting
8281 in incorrect smashes caused by horizontal movement of the above
8282 element; also, the case of the player being the element to smash was
8283 simply not covered here... :-/ ) */
8285 CheckCollision[x][y] = 0;
8286 CheckImpact[x][y] = 0;
8290 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8292 if (MovDir[x][y] == MV_NONE)
8294 InitMovingField(x, y, MV_DOWN);
8295 started_moving = TRUE;
8298 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8300 if (WasJustFalling[x][y]) // prevent animation from being restarted
8301 MovDir[x][y] = MV_DOWN;
8303 InitMovingField(x, y, MV_DOWN);
8304 started_moving = TRUE;
8306 else if (element == EL_AMOEBA_DROP)
8308 Tile[x][y] = EL_AMOEBA_GROWING;
8309 Store[x][y] = EL_AMOEBA_WET;
8311 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8312 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8313 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8314 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8316 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8317 (IS_FREE(x - 1, y + 1) ||
8318 Tile[x - 1][y + 1] == EL_ACID));
8319 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8320 (IS_FREE(x + 1, y + 1) ||
8321 Tile[x + 1][y + 1] == EL_ACID));
8322 boolean can_fall_any = (can_fall_left || can_fall_right);
8323 boolean can_fall_both = (can_fall_left && can_fall_right);
8324 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8326 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8328 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8329 can_fall_right = FALSE;
8330 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8331 can_fall_left = FALSE;
8332 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8333 can_fall_right = FALSE;
8334 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8335 can_fall_left = FALSE;
8337 can_fall_any = (can_fall_left || can_fall_right);
8338 can_fall_both = FALSE;
8343 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8344 can_fall_right = FALSE; // slip down on left side
8346 can_fall_left = !(can_fall_right = RND(2));
8348 can_fall_both = FALSE;
8353 // if not determined otherwise, prefer left side for slipping down
8354 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8355 started_moving = TRUE;
8358 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8360 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8361 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8362 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8363 int belt_dir = game.belt_dir[belt_nr];
8365 if ((belt_dir == MV_LEFT && left_is_free) ||
8366 (belt_dir == MV_RIGHT && right_is_free))
8368 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8370 InitMovingField(x, y, belt_dir);
8371 started_moving = TRUE;
8373 Pushed[x][y] = TRUE;
8374 Pushed[nextx][y] = TRUE;
8376 GfxAction[x][y] = ACTION_DEFAULT;
8380 MovDir[x][y] = 0; // if element was moving, stop it
8385 // not "else if" because of elements that can fall and move (EL_SPRING)
8386 if (CAN_MOVE(element) && !started_moving)
8388 int move_pattern = element_info[element].move_pattern;
8391 Moving2Blocked(x, y, &newx, &newy);
8393 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8396 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8397 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8399 WasJustMoving[x][y] = 0;
8400 CheckCollision[x][y] = 0;
8402 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8404 if (Tile[x][y] != element) // element has changed
8408 if (!MovDelay[x][y]) // start new movement phase
8410 // all objects that can change their move direction after each step
8411 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8413 if (element != EL_YAMYAM &&
8414 element != EL_DARK_YAMYAM &&
8415 element != EL_PACMAN &&
8416 !(move_pattern & MV_ANY_DIRECTION) &&
8417 move_pattern != MV_TURNING_LEFT &&
8418 move_pattern != MV_TURNING_RIGHT &&
8419 move_pattern != MV_TURNING_LEFT_RIGHT &&
8420 move_pattern != MV_TURNING_RIGHT_LEFT &&
8421 move_pattern != MV_TURNING_RANDOM)
8425 if (MovDelay[x][y] && (element == EL_BUG ||
8426 element == EL_SPACESHIP ||
8427 element == EL_SP_SNIKSNAK ||
8428 element == EL_SP_ELECTRON ||
8429 element == EL_MOLE))
8430 TEST_DrawLevelField(x, y);
8434 if (MovDelay[x][y]) // wait some time before next movement
8438 if (element == EL_ROBOT ||
8439 element == EL_YAMYAM ||
8440 element == EL_DARK_YAMYAM)
8442 DrawLevelElementAnimationIfNeeded(x, y, element);
8443 PlayLevelSoundAction(x, y, ACTION_WAITING);
8445 else if (element == EL_SP_ELECTRON)
8446 DrawLevelElementAnimationIfNeeded(x, y, element);
8447 else if (element == EL_DRAGON)
8450 int dir = MovDir[x][y];
8451 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8452 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8453 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8454 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8455 dir == MV_UP ? IMG_FLAMES_1_UP :
8456 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8457 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8459 GfxAction[x][y] = ACTION_ATTACKING;
8461 if (IS_PLAYER(x, y))
8462 DrawPlayerField(x, y);
8464 TEST_DrawLevelField(x, y);
8466 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8468 for (i = 1; i <= 3; i++)
8470 int xx = x + i * dx;
8471 int yy = y + i * dy;
8472 int sx = SCREENX(xx);
8473 int sy = SCREENY(yy);
8474 int flame_graphic = graphic + (i - 1);
8476 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8481 int flamed = MovingOrBlocked2Element(xx, yy);
8483 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8486 RemoveMovingField(xx, yy);
8488 ChangeDelay[xx][yy] = 0;
8490 Tile[xx][yy] = EL_FLAMES;
8492 if (IN_SCR_FIELD(sx, sy))
8494 TEST_DrawLevelFieldCrumbled(xx, yy);
8495 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8500 if (Tile[xx][yy] == EL_FLAMES)
8501 Tile[xx][yy] = EL_EMPTY;
8502 TEST_DrawLevelField(xx, yy);
8507 if (MovDelay[x][y]) // element still has to wait some time
8509 PlayLevelSoundAction(x, y, ACTION_WAITING);
8515 // now make next step
8517 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8519 if (DONT_COLLIDE_WITH(element) &&
8520 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8521 !PLAYER_ENEMY_PROTECTED(newx, newy))
8523 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8528 else if (CAN_MOVE_INTO_ACID(element) &&
8529 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8530 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8531 (MovDir[x][y] == MV_DOWN ||
8532 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8534 SplashAcid(newx, newy);
8535 Store[x][y] = EL_ACID;
8537 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8539 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8540 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8541 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8542 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8545 TEST_DrawLevelField(x, y);
8547 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8548 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8549 DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0);
8551 game.friends_still_needed--;
8552 if (!game.friends_still_needed &&
8554 game.all_players_gone)
8559 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8561 if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING)
8562 TEST_DrawLevelField(newx, newy);
8564 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8566 else if (!IS_FREE(newx, newy))
8568 GfxAction[x][y] = ACTION_WAITING;
8570 if (IS_PLAYER(x, y))
8571 DrawPlayerField(x, y);
8573 TEST_DrawLevelField(x, y);
8578 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8580 if (IS_FOOD_PIG(Tile[newx][newy]))
8582 if (IS_MOVING(newx, newy))
8583 RemoveMovingField(newx, newy);
8586 Tile[newx][newy] = EL_EMPTY;
8587 TEST_DrawLevelField(newx, newy);
8590 PlayLevelSound(x, y, SND_PIG_DIGGING);
8592 else if (!IS_FREE(newx, newy))
8594 if (IS_PLAYER(x, y))
8595 DrawPlayerField(x, y);
8597 TEST_DrawLevelField(x, y);
8602 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8604 if (Store[x][y] != EL_EMPTY)
8606 boolean can_clone = FALSE;
8609 // check if element to clone is still there
8610 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8612 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8620 // cannot clone or target field not free anymore -- do not clone
8621 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8622 Store[x][y] = EL_EMPTY;
8625 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8627 if (IS_MV_DIAGONAL(MovDir[x][y]))
8629 int diagonal_move_dir = MovDir[x][y];
8630 int stored = Store[x][y];
8631 int change_delay = 8;
8634 // android is moving diagonally
8636 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8638 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8639 GfxElement[x][y] = EL_EMC_ANDROID;
8640 GfxAction[x][y] = ACTION_SHRINKING;
8641 GfxDir[x][y] = diagonal_move_dir;
8642 ChangeDelay[x][y] = change_delay;
8644 if (Store[x][y] == EL_EMPTY)
8645 Store[x][y] = GfxElementEmpty[x][y];
8647 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8650 DrawLevelGraphicAnimation(x, y, graphic);
8651 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8653 if (Tile[newx][newy] == EL_ACID)
8655 SplashAcid(newx, newy);
8660 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8662 Store[newx][newy] = EL_EMC_ANDROID;
8663 GfxElement[newx][newy] = EL_EMC_ANDROID;
8664 GfxAction[newx][newy] = ACTION_GROWING;
8665 GfxDir[newx][newy] = diagonal_move_dir;
8666 ChangeDelay[newx][newy] = change_delay;
8668 graphic = el_act_dir2img(GfxElement[newx][newy],
8669 GfxAction[newx][newy], GfxDir[newx][newy]);
8671 DrawLevelGraphicAnimation(newx, newy, graphic);
8672 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8678 Tile[newx][newy] = EL_EMPTY;
8679 TEST_DrawLevelField(newx, newy);
8681 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8684 else if (!IS_FREE(newx, newy))
8689 else if (IS_CUSTOM_ELEMENT(element) &&
8690 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8692 if (!DigFieldByCE(newx, newy, element))
8695 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8697 RunnerVisit[x][y] = FrameCounter;
8698 PlayerVisit[x][y] /= 8; // expire player visit path
8701 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8703 if (!IS_FREE(newx, newy))
8705 if (IS_PLAYER(x, y))
8706 DrawPlayerField(x, y);
8708 TEST_DrawLevelField(x, y);
8714 boolean wanna_flame = !RND(10);
8715 int dx = newx - x, dy = newy - y;
8716 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8717 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8718 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8719 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8720 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8721 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8724 IS_CLASSIC_ENEMY(element1) ||
8725 IS_CLASSIC_ENEMY(element2)) &&
8726 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8727 element1 != EL_FLAMES && element2 != EL_FLAMES)
8729 ResetGfxAnimation(x, y);
8730 GfxAction[x][y] = ACTION_ATTACKING;
8732 if (IS_PLAYER(x, y))
8733 DrawPlayerField(x, y);
8735 TEST_DrawLevelField(x, y);
8737 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8739 MovDelay[x][y] = 50;
8741 Tile[newx][newy] = EL_FLAMES;
8742 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8743 Tile[newx1][newy1] = EL_FLAMES;
8744 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8745 Tile[newx2][newy2] = EL_FLAMES;
8751 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8752 Tile[newx][newy] == EL_DIAMOND)
8754 if (IS_MOVING(newx, newy))
8755 RemoveMovingField(newx, newy);
8758 Tile[newx][newy] = EL_EMPTY;
8759 TEST_DrawLevelField(newx, newy);
8762 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8764 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8765 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8767 if (AmoebaNr[newx][newy])
8769 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8770 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8771 Tile[newx][newy] == EL_BD_AMOEBA)
8772 AmoebaCnt[AmoebaNr[newx][newy]]--;
8775 if (IS_MOVING(newx, newy))
8777 RemoveMovingField(newx, newy);
8781 Tile[newx][newy] = EL_EMPTY;
8782 TEST_DrawLevelField(newx, newy);
8785 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8787 else if ((element == EL_PACMAN || element == EL_MOLE)
8788 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8790 if (AmoebaNr[newx][newy])
8792 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8793 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8794 Tile[newx][newy] == EL_BD_AMOEBA)
8795 AmoebaCnt[AmoebaNr[newx][newy]]--;
8798 if (element == EL_MOLE)
8800 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8801 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8803 ResetGfxAnimation(x, y);
8804 GfxAction[x][y] = ACTION_DIGGING;
8805 TEST_DrawLevelField(x, y);
8807 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8809 return; // wait for shrinking amoeba
8811 else // element == EL_PACMAN
8813 Tile[newx][newy] = EL_EMPTY;
8814 TEST_DrawLevelField(newx, newy);
8815 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8818 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8819 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8820 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8822 // wait for shrinking amoeba to completely disappear
8825 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8827 // object was running against a wall
8831 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8832 DrawLevelElementAnimation(x, y, element);
8834 if (DONT_TOUCH(element))
8835 TestIfBadThingTouchesPlayer(x, y);
8840 InitMovingField(x, y, MovDir[x][y]);
8842 PlayLevelSoundAction(x, y, ACTION_MOVING);
8846 ContinueMoving(x, y);
8849 void ContinueMoving(int x, int y)
8851 int element = Tile[x][y];
8852 struct ElementInfo *ei = &element_info[element];
8853 int direction = MovDir[x][y];
8854 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8855 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8856 int newx = x + dx, newy = y + dy;
8857 int stored = Store[x][y];
8858 int stored_new = Store[newx][newy];
8859 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8860 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8861 boolean last_line = (newy == lev_fieldy - 1);
8862 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8864 if (pushed_by_player) // special case: moving object pushed by player
8866 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos));
8868 else if (use_step_delay) // special case: moving object has step delay
8870 if (!MovDelay[x][y])
8871 MovPos[x][y] += getElementMoveStepsize(x, y);
8876 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8880 TEST_DrawLevelField(x, y);
8882 return; // element is still waiting
8885 else // normal case: generically moving object
8887 MovPos[x][y] += getElementMoveStepsize(x, y);
8890 if (ABS(MovPos[x][y]) < TILEX)
8892 TEST_DrawLevelField(x, y);
8894 return; // element is still moving
8897 // element reached destination field
8899 Tile[x][y] = EL_EMPTY;
8900 Tile[newx][newy] = element;
8901 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8903 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8905 element = Tile[newx][newy] = EL_ACID;
8907 else if (element == EL_MOLE)
8909 Tile[x][y] = EL_SAND;
8911 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8913 else if (element == EL_QUICKSAND_FILLING)
8915 element = Tile[newx][newy] = get_next_element(element);
8916 Store[newx][newy] = Store[x][y];
8918 else if (element == EL_QUICKSAND_EMPTYING)
8920 Tile[x][y] = get_next_element(element);
8921 element = Tile[newx][newy] = Store[x][y];
8923 else if (element == EL_QUICKSAND_FAST_FILLING)
8925 element = Tile[newx][newy] = get_next_element(element);
8926 Store[newx][newy] = Store[x][y];
8928 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8930 Tile[x][y] = get_next_element(element);
8931 element = Tile[newx][newy] = Store[x][y];
8933 else if (element == EL_MAGIC_WALL_FILLING)
8935 element = Tile[newx][newy] = get_next_element(element);
8936 if (!game.magic_wall_active)
8937 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8938 Store[newx][newy] = Store[x][y];
8940 else if (element == EL_MAGIC_WALL_EMPTYING)
8942 Tile[x][y] = get_next_element(element);
8943 if (!game.magic_wall_active)
8944 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8945 element = Tile[newx][newy] = Store[x][y];
8947 InitField(newx, newy, FALSE);
8949 else if (element == EL_BD_MAGIC_WALL_FILLING)
8951 element = Tile[newx][newy] = get_next_element(element);
8952 if (!game.magic_wall_active)
8953 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8954 Store[newx][newy] = Store[x][y];
8956 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8958 Tile[x][y] = get_next_element(element);
8959 if (!game.magic_wall_active)
8960 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8961 element = Tile[newx][newy] = Store[x][y];
8963 InitField(newx, newy, FALSE);
8965 else if (element == EL_DC_MAGIC_WALL_FILLING)
8967 element = Tile[newx][newy] = get_next_element(element);
8968 if (!game.magic_wall_active)
8969 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8970 Store[newx][newy] = Store[x][y];
8972 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8974 Tile[x][y] = get_next_element(element);
8975 if (!game.magic_wall_active)
8976 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8977 element = Tile[newx][newy] = Store[x][y];
8979 InitField(newx, newy, FALSE);
8981 else if (element == EL_AMOEBA_DROPPING)
8983 Tile[x][y] = get_next_element(element);
8984 element = Tile[newx][newy] = Store[x][y];
8986 else if (element == EL_SOKOBAN_OBJECT)
8989 Tile[x][y] = Back[x][y];
8991 if (Back[newx][newy])
8992 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8994 Back[x][y] = Back[newx][newy] = 0;
8997 Store[x][y] = EL_EMPTY;
9002 MovDelay[newx][newy] = 0;
9004 if (CAN_CHANGE_OR_HAS_ACTION(element))
9006 // copy element change control values to new field
9007 ChangeDelay[newx][newy] = ChangeDelay[x][y];
9008 ChangePage[newx][newy] = ChangePage[x][y];
9009 ChangeCount[newx][newy] = ChangeCount[x][y];
9010 ChangeEvent[newx][newy] = ChangeEvent[x][y];
9013 CustomValue[newx][newy] = CustomValue[x][y];
9015 ChangeDelay[x][y] = 0;
9016 ChangePage[x][y] = -1;
9017 ChangeCount[x][y] = 0;
9018 ChangeEvent[x][y] = -1;
9020 CustomValue[x][y] = 0;
9022 // copy animation control values to new field
9023 GfxFrame[newx][newy] = GfxFrame[x][y];
9024 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
9025 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
9026 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
9028 Pushed[x][y] = Pushed[newx][newy] = FALSE;
9030 // some elements can leave other elements behind after moving
9031 if (ei->move_leave_element != EL_EMPTY &&
9032 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
9033 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
9035 int move_leave_element = ei->move_leave_element;
9037 // this makes it possible to leave the removed element again
9038 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
9039 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
9041 Tile[x][y] = move_leave_element;
9043 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
9044 MovDir[x][y] = direction;
9046 InitField(x, y, FALSE);
9048 if (GFX_CRUMBLED(Tile[x][y]))
9049 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
9051 if (IS_PLAYER_ELEMENT(move_leave_element))
9052 RelocatePlayer(x, y, move_leave_element);
9055 // do this after checking for left-behind element
9056 ResetGfxAnimation(x, y); // reset animation values for old field
9058 if (!CAN_MOVE(element) ||
9059 (CAN_FALL(element) && direction == MV_DOWN &&
9060 (element == EL_SPRING ||
9061 element_info[element].move_pattern == MV_WHEN_PUSHED ||
9062 element_info[element].move_pattern == MV_WHEN_DROPPED)))
9063 GfxDir[x][y] = MovDir[newx][newy] = 0;
9065 TEST_DrawLevelField(x, y);
9066 TEST_DrawLevelField(newx, newy);
9068 Stop[newx][newy] = TRUE; // ignore this element until the next frame
9070 // prevent pushed element from moving on in pushed direction
9071 if (pushed_by_player && CAN_MOVE(element) &&
9072 element_info[element].move_pattern & MV_ANY_DIRECTION &&
9073 !(element_info[element].move_pattern & direction))
9074 TurnRound(newx, newy);
9076 // prevent elements on conveyor belt from moving on in last direction
9077 if (pushed_by_conveyor && CAN_FALL(element) &&
9078 direction & MV_HORIZONTAL)
9079 MovDir[newx][newy] = 0;
9081 if (!pushed_by_player)
9083 int nextx = newx + dx, nexty = newy + dy;
9084 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
9086 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
9088 if (CAN_FALL(element) && direction == MV_DOWN)
9089 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
9091 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
9092 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
9094 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
9095 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
9098 if (DONT_TOUCH(element)) // object may be nasty to player or others
9100 TestIfBadThingTouchesPlayer(newx, newy);
9101 TestIfBadThingTouchesFriend(newx, newy);
9103 if (!IS_CUSTOM_ELEMENT(element))
9104 TestIfBadThingTouchesOtherBadThing(newx, newy);
9106 else if (element == EL_PENGUIN)
9107 TestIfFriendTouchesBadThing(newx, newy);
9109 if (DONT_GET_HIT_BY(element))
9111 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
9114 // give the player one last chance (one more frame) to move away
9115 if (CAN_FALL(element) && direction == MV_DOWN &&
9116 (last_line || (!IS_FREE(x, newy + 1) &&
9117 (!IS_PLAYER(x, newy + 1) ||
9118 game.engine_version < VERSION_IDENT(3,1,1,0)))))
9121 if (pushed_by_player && !game.use_change_when_pushing_bug)
9123 int push_side = MV_DIR_OPPOSITE(direction);
9124 struct PlayerInfo *player = PLAYERINFO(x, y);
9126 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
9127 player->index_bit, push_side);
9128 CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X,
9129 player->index_bit, push_side);
9132 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
9133 MovDelay[newx][newy] = 1;
9135 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
9137 TestIfElementTouchesCustomElement(x, y); // empty or new element
9138 TestIfElementHitsCustomElement(newx, newy, direction);
9139 TestIfPlayerTouchesCustomElement(newx, newy);
9140 TestIfElementTouchesCustomElement(newx, newy);
9142 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
9143 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
9144 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
9145 MV_DIR_OPPOSITE(direction));
9148 int AmoebaNeighbourNr(int ax, int ay)
9151 int element = Tile[ax][ay];
9153 struct XY *xy = xy_topdown;
9155 for (i = 0; i < NUM_DIRECTIONS; i++)
9157 int x = ax + xy[i].x;
9158 int y = ay + xy[i].y;
9160 if (!IN_LEV_FIELD(x, y))
9163 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
9164 group_nr = AmoebaNr[x][y];
9170 static void AmoebaMerge(int ax, int ay)
9172 int i, x, y, xx, yy;
9173 int new_group_nr = AmoebaNr[ax][ay];
9174 struct XY *xy = xy_topdown;
9176 if (new_group_nr == 0)
9179 for (i = 0; i < NUM_DIRECTIONS; i++)
9184 if (!IN_LEV_FIELD(x, y))
9187 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9188 Tile[x][y] == EL_BD_AMOEBA ||
9189 Tile[x][y] == EL_AMOEBA_DEAD) &&
9190 AmoebaNr[x][y] != new_group_nr)
9192 int old_group_nr = AmoebaNr[x][y];
9194 if (old_group_nr == 0)
9197 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9198 AmoebaCnt[old_group_nr] = 0;
9199 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9200 AmoebaCnt2[old_group_nr] = 0;
9202 SCAN_PLAYFIELD(xx, yy)
9204 if (AmoebaNr[xx][yy] == old_group_nr)
9205 AmoebaNr[xx][yy] = new_group_nr;
9211 void AmoebaToDiamond(int ax, int ay)
9215 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9217 int group_nr = AmoebaNr[ax][ay];
9222 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9223 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9229 SCAN_PLAYFIELD(x, y)
9231 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9234 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9238 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9239 SND_AMOEBA_TURNING_TO_GEM :
9240 SND_AMOEBA_TURNING_TO_ROCK));
9245 struct XY *xy = xy_topdown;
9247 for (i = 0; i < NUM_DIRECTIONS; i++)
9252 if (!IN_LEV_FIELD(x, y))
9255 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9257 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9258 SND_AMOEBA_TURNING_TO_GEM :
9259 SND_AMOEBA_TURNING_TO_ROCK));
9266 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9269 int group_nr = AmoebaNr[ax][ay];
9270 boolean done = FALSE;
9275 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9276 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9282 SCAN_PLAYFIELD(x, y)
9284 if (AmoebaNr[x][y] == group_nr &&
9285 (Tile[x][y] == EL_AMOEBA_DEAD ||
9286 Tile[x][y] == EL_BD_AMOEBA ||
9287 Tile[x][y] == EL_AMOEBA_GROWING))
9290 Tile[x][y] = new_element;
9291 InitField(x, y, FALSE);
9292 TEST_DrawLevelField(x, y);
9298 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9299 SND_BD_AMOEBA_TURNING_TO_ROCK :
9300 SND_BD_AMOEBA_TURNING_TO_GEM));
9303 static void AmoebaGrowing(int x, int y)
9305 static DelayCounter sound_delay = { 0 };
9307 if (!MovDelay[x][y]) // start new growing cycle
9311 if (DelayReached(&sound_delay))
9313 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9314 sound_delay.value = 30;
9318 if (MovDelay[x][y]) // wait some time before growing bigger
9321 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9323 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9324 6 - MovDelay[x][y]);
9326 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9329 if (!MovDelay[x][y])
9331 Tile[x][y] = Store[x][y];
9333 TEST_DrawLevelField(x, y);
9338 static void AmoebaShrinking(int x, int y)
9340 static DelayCounter sound_delay = { 0 };
9342 if (!MovDelay[x][y]) // start new shrinking cycle
9346 if (DelayReached(&sound_delay))
9347 sound_delay.value = 30;
9350 if (MovDelay[x][y]) // wait some time before shrinking
9353 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9355 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9356 6 - MovDelay[x][y]);
9358 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9361 if (!MovDelay[x][y])
9363 Tile[x][y] = EL_EMPTY;
9364 TEST_DrawLevelField(x, y);
9366 // don't let mole enter this field in this cycle;
9367 // (give priority to objects falling to this field from above)
9373 static void AmoebaReproduce(int ax, int ay)
9376 int element = Tile[ax][ay];
9377 int graphic = el2img(element);
9378 int newax = ax, neway = ay;
9379 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9380 struct XY *xy = xy_topdown;
9382 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9384 Tile[ax][ay] = EL_AMOEBA_DEAD;
9385 TEST_DrawLevelField(ax, ay);
9389 if (IS_ANIMATED(graphic))
9390 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9392 if (!MovDelay[ax][ay]) // start making new amoeba field
9393 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9395 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9398 if (MovDelay[ax][ay])
9402 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9405 int x = ax + xy[start].x;
9406 int y = ay + xy[start].y;
9408 if (!IN_LEV_FIELD(x, y))
9411 if (IS_FREE(x, y) ||
9412 CAN_GROW_INTO(Tile[x][y]) ||
9413 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9414 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9420 if (newax == ax && neway == ay)
9423 else // normal or "filled" (BD style) amoeba
9426 boolean waiting_for_player = FALSE;
9428 for (i = 0; i < NUM_DIRECTIONS; i++)
9430 int j = (start + i) % 4;
9431 int x = ax + xy[j].x;
9432 int y = ay + xy[j].y;
9434 if (!IN_LEV_FIELD(x, y))
9437 if (IS_FREE(x, y) ||
9438 CAN_GROW_INTO(Tile[x][y]) ||
9439 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9440 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9446 else if (IS_PLAYER(x, y))
9447 waiting_for_player = TRUE;
9450 if (newax == ax && neway == ay) // amoeba cannot grow
9452 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9454 Tile[ax][ay] = EL_AMOEBA_DEAD;
9455 TEST_DrawLevelField(ax, ay);
9456 AmoebaCnt[AmoebaNr[ax][ay]]--;
9458 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9460 if (element == EL_AMOEBA_FULL)
9461 AmoebaToDiamond(ax, ay);
9462 else if (element == EL_BD_AMOEBA)
9463 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9468 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9470 // amoeba gets larger by growing in some direction
9472 int new_group_nr = AmoebaNr[ax][ay];
9475 if (new_group_nr == 0)
9477 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9479 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9485 AmoebaNr[newax][neway] = new_group_nr;
9486 AmoebaCnt[new_group_nr]++;
9487 AmoebaCnt2[new_group_nr]++;
9489 // if amoeba touches other amoeba(s) after growing, unify them
9490 AmoebaMerge(newax, neway);
9492 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9494 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9500 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9501 (neway == lev_fieldy - 1 && newax != ax))
9503 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9504 Store[newax][neway] = element;
9506 else if (neway == ay || element == EL_EMC_DRIPPER)
9508 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9510 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9514 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9515 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9516 Store[ax][ay] = EL_AMOEBA_DROP;
9517 ContinueMoving(ax, ay);
9521 TEST_DrawLevelField(newax, neway);
9524 static void Life(int ax, int ay)
9528 int element = Tile[ax][ay];
9529 int graphic = el2img(element);
9530 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9532 boolean changed = FALSE;
9534 if (IS_ANIMATED(graphic))
9535 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9540 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9541 MovDelay[ax][ay] = life_time;
9543 if (MovDelay[ax][ay]) // wait some time before next cycle
9546 if (MovDelay[ax][ay])
9550 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9552 int xx = ax + x1, yy = ay + y1;
9553 int old_element = Tile[xx][yy];
9554 int num_neighbours = 0;
9556 if (!IN_LEV_FIELD(xx, yy))
9559 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9561 int x = xx + x2, y = yy + y2;
9563 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9566 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9567 boolean is_neighbour = FALSE;
9569 if (level.use_life_bugs)
9571 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9572 (IS_FREE(x, y) && Stop[x][y]));
9575 (Last[x][y] == element || is_player_cell);
9581 boolean is_free = FALSE;
9583 if (level.use_life_bugs)
9584 is_free = (IS_FREE(xx, yy));
9586 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9588 if (xx == ax && yy == ay) // field in the middle
9590 if (num_neighbours < life_parameter[0] ||
9591 num_neighbours > life_parameter[1])
9593 Tile[xx][yy] = EL_EMPTY;
9594 if (Tile[xx][yy] != old_element)
9595 TEST_DrawLevelField(xx, yy);
9596 Stop[xx][yy] = TRUE;
9600 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9601 { // free border field
9602 if (num_neighbours >= life_parameter[2] &&
9603 num_neighbours <= life_parameter[3])
9605 Tile[xx][yy] = element;
9606 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9607 if (Tile[xx][yy] != old_element)
9608 TEST_DrawLevelField(xx, yy);
9609 Stop[xx][yy] = TRUE;
9616 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9617 SND_GAME_OF_LIFE_GROWING);
9620 static void InitRobotWheel(int x, int y)
9622 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9625 static void RunRobotWheel(int x, int y)
9627 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9630 static void StopRobotWheel(int x, int y)
9632 if (game.robot_wheel_x == x &&
9633 game.robot_wheel_y == y)
9635 game.robot_wheel_x = -1;
9636 game.robot_wheel_y = -1;
9637 game.robot_wheel_active = FALSE;
9641 static void InitTimegateWheel(int x, int y)
9643 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9646 static void RunTimegateWheel(int x, int y)
9648 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9651 static void InitMagicBallDelay(int x, int y)
9653 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9656 static void ActivateMagicBall(int bx, int by)
9660 if (level.ball_random)
9662 int pos_border = RND(8); // select one of the eight border elements
9663 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9664 int xx = pos_content % 3;
9665 int yy = pos_content / 3;
9670 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9671 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9675 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9677 int xx = x - bx + 1;
9678 int yy = y - by + 1;
9680 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9681 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9685 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9688 static void CheckExit(int x, int y)
9690 if (game.gems_still_needed > 0 ||
9691 game.sokoban_fields_still_needed > 0 ||
9692 game.sokoban_objects_still_needed > 0 ||
9693 game.lights_still_needed > 0)
9695 int element = Tile[x][y];
9696 int graphic = el2img(element);
9698 if (IS_ANIMATED(graphic))
9699 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9704 // do not re-open exit door closed after last player
9705 if (game.all_players_gone)
9708 Tile[x][y] = EL_EXIT_OPENING;
9710 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9713 static void CheckExitEM(int x, int y)
9715 if (game.gems_still_needed > 0 ||
9716 game.sokoban_fields_still_needed > 0 ||
9717 game.sokoban_objects_still_needed > 0 ||
9718 game.lights_still_needed > 0)
9720 int element = Tile[x][y];
9721 int graphic = el2img(element);
9723 if (IS_ANIMATED(graphic))
9724 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9729 // do not re-open exit door closed after last player
9730 if (game.all_players_gone)
9733 Tile[x][y] = EL_EM_EXIT_OPENING;
9735 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9738 static void CheckExitSteel(int x, int y)
9740 if (game.gems_still_needed > 0 ||
9741 game.sokoban_fields_still_needed > 0 ||
9742 game.sokoban_objects_still_needed > 0 ||
9743 game.lights_still_needed > 0)
9745 int element = Tile[x][y];
9746 int graphic = el2img(element);
9748 if (IS_ANIMATED(graphic))
9749 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9754 // do not re-open exit door closed after last player
9755 if (game.all_players_gone)
9758 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9760 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9763 static void CheckExitSteelEM(int x, int y)
9765 if (game.gems_still_needed > 0 ||
9766 game.sokoban_fields_still_needed > 0 ||
9767 game.sokoban_objects_still_needed > 0 ||
9768 game.lights_still_needed > 0)
9770 int element = Tile[x][y];
9771 int graphic = el2img(element);
9773 if (IS_ANIMATED(graphic))
9774 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9779 // do not re-open exit door closed after last player
9780 if (game.all_players_gone)
9783 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9785 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9788 static void CheckExitSP(int x, int y)
9790 if (game.gems_still_needed > 0)
9792 int element = Tile[x][y];
9793 int graphic = el2img(element);
9795 if (IS_ANIMATED(graphic))
9796 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9801 // do not re-open exit door closed after last player
9802 if (game.all_players_gone)
9805 Tile[x][y] = EL_SP_EXIT_OPENING;
9807 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9810 static void CloseAllOpenTimegates(void)
9814 SCAN_PLAYFIELD(x, y)
9816 int element = Tile[x][y];
9818 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9820 Tile[x][y] = EL_TIMEGATE_CLOSING;
9822 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9827 static void DrawTwinkleOnField(int x, int y)
9829 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9832 if (Tile[x][y] == EL_BD_DIAMOND)
9835 if (MovDelay[x][y] == 0) // next animation frame
9836 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9838 if (MovDelay[x][y] != 0) // wait some time before next frame
9842 DrawLevelElementAnimation(x, y, Tile[x][y]);
9844 if (MovDelay[x][y] != 0)
9846 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9847 10 - MovDelay[x][y]);
9849 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9854 static void WallGrowing(int x, int y)
9858 if (!MovDelay[x][y]) // next animation frame
9859 MovDelay[x][y] = 3 * delay;
9861 if (MovDelay[x][y]) // wait some time before next frame
9865 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9867 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9868 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9870 DrawLevelGraphic(x, y, graphic, frame);
9873 if (!MovDelay[x][y])
9875 if (MovDir[x][y] == MV_LEFT)
9877 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9878 TEST_DrawLevelField(x - 1, y);
9880 else if (MovDir[x][y] == MV_RIGHT)
9882 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9883 TEST_DrawLevelField(x + 1, y);
9885 else if (MovDir[x][y] == MV_UP)
9887 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9888 TEST_DrawLevelField(x, y - 1);
9892 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9893 TEST_DrawLevelField(x, y + 1);
9896 Tile[x][y] = Store[x][y];
9898 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9899 TEST_DrawLevelField(x, y);
9904 static void CheckWallGrowing(int ax, int ay)
9906 int element = Tile[ax][ay];
9907 int graphic = el2img(element);
9908 boolean free_top = FALSE;
9909 boolean free_bottom = FALSE;
9910 boolean free_left = FALSE;
9911 boolean free_right = FALSE;
9912 boolean stop_top = FALSE;
9913 boolean stop_bottom = FALSE;
9914 boolean stop_left = FALSE;
9915 boolean stop_right = FALSE;
9916 boolean new_wall = FALSE;
9918 boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9919 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9920 element == EL_EXPANDABLE_STEELWALL_ANY);
9922 boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9923 element == EL_EXPANDABLE_WALL_ANY ||
9924 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9925 element == EL_EXPANDABLE_STEELWALL_ANY);
9927 boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9928 element == EL_EXPANDABLE_WALL_ANY ||
9929 element == EL_EXPANDABLE_WALL ||
9930 element == EL_BD_EXPANDABLE_WALL ||
9931 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9932 element == EL_EXPANDABLE_STEELWALL_ANY);
9934 boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9935 element == EL_EXPANDABLE_STEELWALL_VERTICAL);
9937 boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9938 element == EL_EXPANDABLE_WALL ||
9939 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL);
9941 int wall_growing = (is_steelwall ?
9942 EL_EXPANDABLE_STEELWALL_GROWING :
9943 EL_EXPANDABLE_WALL_GROWING);
9945 int gfx_wall_growing_up = (is_steelwall ?
9946 IMG_EXPANDABLE_STEELWALL_GROWING_UP :
9947 IMG_EXPANDABLE_WALL_GROWING_UP);
9948 int gfx_wall_growing_down = (is_steelwall ?
9949 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN :
9950 IMG_EXPANDABLE_WALL_GROWING_DOWN);
9951 int gfx_wall_growing_left = (is_steelwall ?
9952 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT :
9953 IMG_EXPANDABLE_WALL_GROWING_LEFT);
9954 int gfx_wall_growing_right = (is_steelwall ?
9955 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT :
9956 IMG_EXPANDABLE_WALL_GROWING_RIGHT);
9958 if (IS_ANIMATED(graphic))
9959 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9961 if (!MovDelay[ax][ay]) // start building new wall
9962 MovDelay[ax][ay] = 6;
9964 if (MovDelay[ax][ay]) // wait some time before building new wall
9967 if (MovDelay[ax][ay])
9971 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9973 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9975 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9977 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9984 Tile[ax][ay - 1] = wall_growing;
9985 Store[ax][ay - 1] = element;
9986 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9988 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9989 DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0);
9996 Tile[ax][ay + 1] = wall_growing;
9997 Store[ax][ay + 1] = element;
9998 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
10000 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
10001 DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0);
10007 if (grow_horizontal)
10011 Tile[ax - 1][ay] = wall_growing;
10012 Store[ax - 1][ay] = element;
10013 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
10015 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
10016 DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0);
10023 Tile[ax + 1][ay] = wall_growing;
10024 Store[ax + 1][ay] = element;
10025 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
10027 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
10028 DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0);
10034 if (element == EL_EXPANDABLE_WALL && (free_left || free_right))
10035 TEST_DrawLevelField(ax, ay);
10037 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
10039 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
10040 stop_bottom = TRUE;
10041 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
10043 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
10046 if (((stop_top && stop_bottom) || stop_horizontal) &&
10047 ((stop_left && stop_right) || stop_vertical))
10048 Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL);
10051 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
10054 static void CheckForDragon(int x, int y)
10057 boolean dragon_found = FALSE;
10058 struct XY *xy = xy_topdown;
10060 for (i = 0; i < NUM_DIRECTIONS; i++)
10062 for (j = 0; j < 4; j++)
10064 int xx = x + j * xy[i].x;
10065 int yy = y + j * xy[i].y;
10067 if (IN_LEV_FIELD(xx, yy) &&
10068 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
10070 if (Tile[xx][yy] == EL_DRAGON)
10071 dragon_found = TRUE;
10080 for (i = 0; i < NUM_DIRECTIONS; i++)
10082 for (j = 0; j < 3; j++)
10084 int xx = x + j * xy[i].x;
10085 int yy = y + j * xy[i].y;
10087 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
10089 Tile[xx][yy] = EL_EMPTY;
10090 TEST_DrawLevelField(xx, yy);
10099 static void InitBuggyBase(int x, int y)
10101 int element = Tile[x][y];
10102 int activating_delay = FRAMES_PER_SECOND / 4;
10104 ChangeDelay[x][y] =
10105 (element == EL_SP_BUGGY_BASE ?
10106 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10107 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10109 element == EL_SP_BUGGY_BASE_ACTIVE ?
10110 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10113 static void WarnBuggyBase(int x, int y)
10116 struct XY *xy = xy_topdown;
10118 for (i = 0; i < NUM_DIRECTIONS; i++)
10120 int xx = x + xy[i].x;
10121 int yy = y + xy[i].y;
10123 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10125 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10132 static void InitTrap(int x, int y)
10134 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10137 static void ActivateTrap(int x, int y)
10139 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10142 static void ChangeActiveTrap(int x, int y)
10144 int graphic = IMG_TRAP_ACTIVE;
10146 // if new animation frame was drawn, correct crumbled sand border
10147 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10148 TEST_DrawLevelFieldCrumbled(x, y);
10151 static int getSpecialActionElement(int element, int number, int base_element)
10153 return (element != EL_EMPTY ? element :
10154 number != -1 ? base_element + number - 1 :
10158 static int getModifiedActionNumber(int value_old, int operator, int operand,
10159 int value_min, int value_max)
10161 int value_new = (operator == CA_MODE_SET ? operand :
10162 operator == CA_MODE_ADD ? value_old + operand :
10163 operator == CA_MODE_SUBTRACT ? value_old - operand :
10164 operator == CA_MODE_MULTIPLY ? value_old * operand :
10165 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10166 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10169 return (value_new < value_min ? value_min :
10170 value_new > value_max ? value_max :
10174 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10176 struct ElementInfo *ei = &element_info[element];
10177 struct ElementChangeInfo *change = &ei->change_page[page];
10178 int target_element = change->target_element;
10179 int action_type = change->action_type;
10180 int action_mode = change->action_mode;
10181 int action_arg = change->action_arg;
10182 int action_element = change->action_element;
10185 if (!change->has_action)
10188 // ---------- determine action paramater values -----------------------------
10190 int level_time_value =
10191 (level.time > 0 ? TimeLeft :
10194 int action_arg_element_raw =
10195 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10196 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10197 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10198 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10199 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10200 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10201 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10203 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10205 int action_arg_direction =
10206 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10207 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10208 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10209 change->actual_trigger_side :
10210 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10211 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10214 int action_arg_number_min =
10215 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10218 int action_arg_number_max =
10219 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10220 action_type == CA_SET_LEVEL_GEMS ? 999 :
10221 action_type == CA_SET_LEVEL_TIME ? 9999 :
10222 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10223 action_type == CA_SET_CE_VALUE ? 9999 :
10224 action_type == CA_SET_CE_SCORE ? 9999 :
10227 int action_arg_number_reset =
10228 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10229 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10230 action_type == CA_SET_LEVEL_TIME ? level.time :
10231 action_type == CA_SET_LEVEL_SCORE ? 0 :
10232 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10233 action_type == CA_SET_CE_SCORE ? 0 :
10236 int action_arg_number =
10237 (action_arg <= CA_ARG_MAX ? action_arg :
10238 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10239 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10240 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10241 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10242 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10243 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10244 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10245 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10246 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10247 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10248 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10249 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10250 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10251 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10252 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10253 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10254 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10255 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10256 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10257 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10258 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10261 int action_arg_number_old =
10262 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10263 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10264 action_type == CA_SET_LEVEL_SCORE ? game.score :
10265 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10266 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10269 int action_arg_number_new =
10270 getModifiedActionNumber(action_arg_number_old,
10271 action_mode, action_arg_number,
10272 action_arg_number_min, action_arg_number_max);
10274 int trigger_player_bits =
10275 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10276 change->actual_trigger_player_bits : change->trigger_player);
10278 int action_arg_player_bits =
10279 (action_arg >= CA_ARG_PLAYER_1 &&
10280 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10281 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10282 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10285 // ---------- execute action -----------------------------------------------
10287 switch (action_type)
10294 // ---------- level actions ----------------------------------------------
10296 case CA_RESTART_LEVEL:
10298 game.restart_level = TRUE;
10303 case CA_SHOW_ENVELOPE:
10305 int element = getSpecialActionElement(action_arg_element,
10306 action_arg_number, EL_ENVELOPE_1);
10308 if (IS_ENVELOPE(element))
10309 local_player->show_envelope = element;
10314 case CA_SET_LEVEL_TIME:
10316 if (level.time > 0) // only modify limited time value
10318 TimeLeft = action_arg_number_new;
10320 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10322 DisplayGameControlValues();
10324 if (!TimeLeft && game.time_limit)
10325 for (i = 0; i < MAX_PLAYERS; i++)
10326 KillPlayer(&stored_player[i]);
10332 case CA_SET_LEVEL_SCORE:
10334 game.score = action_arg_number_new;
10336 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10338 DisplayGameControlValues();
10343 case CA_SET_LEVEL_GEMS:
10345 game.gems_still_needed = action_arg_number_new;
10347 game.snapshot.collected_item = TRUE;
10349 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10351 DisplayGameControlValues();
10356 case CA_SET_LEVEL_WIND:
10358 game.wind_direction = action_arg_direction;
10363 case CA_SET_LEVEL_RANDOM_SEED:
10365 // ensure that setting a new random seed while playing is predictable
10366 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10371 // ---------- player actions ---------------------------------------------
10373 case CA_MOVE_PLAYER:
10374 case CA_MOVE_PLAYER_NEW:
10376 // automatically move to the next field in specified direction
10377 for (i = 0; i < MAX_PLAYERS; i++)
10378 if (trigger_player_bits & (1 << i))
10379 if (action_type == CA_MOVE_PLAYER ||
10380 stored_player[i].MovPos == 0)
10381 stored_player[i].programmed_action = action_arg_direction;
10386 case CA_EXIT_PLAYER:
10388 for (i = 0; i < MAX_PLAYERS; i++)
10389 if (action_arg_player_bits & (1 << i))
10390 ExitPlayer(&stored_player[i]);
10392 if (game.players_still_needed == 0)
10398 case CA_KILL_PLAYER:
10400 for (i = 0; i < MAX_PLAYERS; i++)
10401 if (action_arg_player_bits & (1 << i))
10402 KillPlayer(&stored_player[i]);
10407 case CA_SET_PLAYER_KEYS:
10409 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10410 int element = getSpecialActionElement(action_arg_element,
10411 action_arg_number, EL_KEY_1);
10413 if (IS_KEY(element))
10415 for (i = 0; i < MAX_PLAYERS; i++)
10417 if (trigger_player_bits & (1 << i))
10419 stored_player[i].key[KEY_NR(element)] = key_state;
10421 DrawGameDoorValues();
10429 case CA_SET_PLAYER_SPEED:
10431 for (i = 0; i < MAX_PLAYERS; i++)
10433 if (trigger_player_bits & (1 << i))
10435 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10437 if (action_arg == CA_ARG_SPEED_FASTER &&
10438 stored_player[i].cannot_move)
10440 action_arg_number = STEPSIZE_VERY_SLOW;
10442 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10443 action_arg == CA_ARG_SPEED_FASTER)
10445 action_arg_number = 2;
10446 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10449 else if (action_arg == CA_ARG_NUMBER_RESET)
10451 action_arg_number = level.initial_player_stepsize[i];
10455 getModifiedActionNumber(move_stepsize,
10458 action_arg_number_min,
10459 action_arg_number_max);
10461 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10468 case CA_SET_PLAYER_SHIELD:
10470 for (i = 0; i < MAX_PLAYERS; i++)
10472 if (trigger_player_bits & (1 << i))
10474 if (action_arg == CA_ARG_SHIELD_OFF)
10476 stored_player[i].shield_normal_time_left = 0;
10477 stored_player[i].shield_deadly_time_left = 0;
10479 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10481 stored_player[i].shield_normal_time_left = 999999;
10483 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10485 stored_player[i].shield_normal_time_left = 999999;
10486 stored_player[i].shield_deadly_time_left = 999999;
10494 case CA_SET_PLAYER_GRAVITY:
10496 for (i = 0; i < MAX_PLAYERS; i++)
10498 if (trigger_player_bits & (1 << i))
10500 stored_player[i].gravity =
10501 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10502 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10503 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10504 stored_player[i].gravity);
10511 case CA_SET_PLAYER_ARTWORK:
10513 for (i = 0; i < MAX_PLAYERS; i++)
10515 if (trigger_player_bits & (1 << i))
10517 int artwork_element = action_arg_element;
10519 if (action_arg == CA_ARG_ELEMENT_RESET)
10521 (level.use_artwork_element[i] ? level.artwork_element[i] :
10522 stored_player[i].element_nr);
10524 if (stored_player[i].artwork_element != artwork_element)
10525 stored_player[i].Frame = 0;
10527 stored_player[i].artwork_element = artwork_element;
10529 SetPlayerWaiting(&stored_player[i], FALSE);
10531 // set number of special actions for bored and sleeping animation
10532 stored_player[i].num_special_action_bored =
10533 get_num_special_action(artwork_element,
10534 ACTION_BORING_1, ACTION_BORING_LAST);
10535 stored_player[i].num_special_action_sleeping =
10536 get_num_special_action(artwork_element,
10537 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10544 case CA_SET_PLAYER_INVENTORY:
10546 for (i = 0; i < MAX_PLAYERS; i++)
10548 struct PlayerInfo *player = &stored_player[i];
10551 if (trigger_player_bits & (1 << i))
10553 int inventory_element = action_arg_element;
10555 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10556 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10557 action_arg == CA_ARG_ELEMENT_ACTION)
10559 int element = inventory_element;
10560 int collect_count = element_info[element].collect_count_initial;
10562 if (!IS_CUSTOM_ELEMENT(element))
10565 if (collect_count == 0)
10566 player->inventory_infinite_element = element;
10568 for (k = 0; k < collect_count; k++)
10569 if (player->inventory_size < MAX_INVENTORY_SIZE)
10570 player->inventory_element[player->inventory_size++] =
10573 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10574 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10575 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10577 if (player->inventory_infinite_element != EL_UNDEFINED &&
10578 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10579 action_arg_element_raw))
10580 player->inventory_infinite_element = EL_UNDEFINED;
10582 for (k = 0, j = 0; j < player->inventory_size; j++)
10584 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10585 action_arg_element_raw))
10586 player->inventory_element[k++] = player->inventory_element[j];
10589 player->inventory_size = k;
10591 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10593 if (player->inventory_size > 0)
10595 for (j = 0; j < player->inventory_size - 1; j++)
10596 player->inventory_element[j] = player->inventory_element[j + 1];
10598 player->inventory_size--;
10601 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10603 if (player->inventory_size > 0)
10604 player->inventory_size--;
10606 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10608 player->inventory_infinite_element = EL_UNDEFINED;
10609 player->inventory_size = 0;
10611 else if (action_arg == CA_ARG_INVENTORY_RESET)
10613 player->inventory_infinite_element = EL_UNDEFINED;
10614 player->inventory_size = 0;
10616 if (level.use_initial_inventory[i])
10618 for (j = 0; j < level.initial_inventory_size[i]; j++)
10620 int element = level.initial_inventory_content[i][j];
10621 int collect_count = element_info[element].collect_count_initial;
10623 if (!IS_CUSTOM_ELEMENT(element))
10626 if (collect_count == 0)
10627 player->inventory_infinite_element = element;
10629 for (k = 0; k < collect_count; k++)
10630 if (player->inventory_size < MAX_INVENTORY_SIZE)
10631 player->inventory_element[player->inventory_size++] =
10642 // ---------- CE actions -------------------------------------------------
10644 case CA_SET_CE_VALUE:
10646 int last_ce_value = CustomValue[x][y];
10648 CustomValue[x][y] = action_arg_number_new;
10650 if (CustomValue[x][y] != last_ce_value)
10652 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10653 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10655 if (CustomValue[x][y] == 0)
10657 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10658 ChangeCount[x][y] = 0; // allow at least one more change
10660 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10661 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10668 case CA_SET_CE_SCORE:
10670 int last_ce_score = ei->collect_score;
10672 ei->collect_score = action_arg_number_new;
10674 if (ei->collect_score != last_ce_score)
10676 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10677 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10679 if (ei->collect_score == 0)
10683 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10684 ChangeCount[x][y] = 0; // allow at least one more change
10686 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10687 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10690 This is a very special case that seems to be a mixture between
10691 CheckElementChange() and CheckTriggeredElementChange(): while
10692 the first one only affects single elements that are triggered
10693 directly, the second one affects multiple elements in the playfield
10694 that are triggered indirectly by another element. This is a third
10695 case: Changing the CE score always affects multiple identical CEs,
10696 so every affected CE must be checked, not only the single CE for
10697 which the CE score was changed in the first place (as every instance
10698 of that CE shares the same CE score, and therefore also can change)!
10700 SCAN_PLAYFIELD(xx, yy)
10702 if (Tile[xx][yy] == element)
10703 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10704 CE_SCORE_GETS_ZERO);
10712 case CA_SET_CE_ARTWORK:
10714 int artwork_element = action_arg_element;
10715 boolean reset_frame = FALSE;
10718 if (action_arg == CA_ARG_ELEMENT_RESET)
10719 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10722 if (ei->gfx_element != artwork_element)
10723 reset_frame = TRUE;
10725 ei->gfx_element = artwork_element;
10727 SCAN_PLAYFIELD(xx, yy)
10729 if (Tile[xx][yy] == element)
10733 ResetGfxAnimation(xx, yy);
10734 ResetRandomAnimationValue(xx, yy);
10737 TEST_DrawLevelField(xx, yy);
10744 // ---------- engine actions ---------------------------------------------
10746 case CA_SET_ENGINE_SCAN_MODE:
10748 InitPlayfieldScanMode(action_arg);
10758 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10760 int old_element = Tile[x][y];
10761 int new_element = GetElementFromGroupElement(element);
10762 int previous_move_direction = MovDir[x][y];
10763 int last_ce_value = CustomValue[x][y];
10764 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10765 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10766 boolean add_player_onto_element = (new_element_is_player &&
10767 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10768 IS_WALKABLE(old_element));
10770 if (!add_player_onto_element)
10772 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10773 RemoveMovingField(x, y);
10777 Tile[x][y] = new_element;
10779 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10780 MovDir[x][y] = previous_move_direction;
10782 if (element_info[new_element].use_last_ce_value)
10783 CustomValue[x][y] = last_ce_value;
10785 InitField_WithBug1(x, y, FALSE);
10787 new_element = Tile[x][y]; // element may have changed
10789 ResetGfxAnimation(x, y);
10790 ResetRandomAnimationValue(x, y);
10792 TEST_DrawLevelField(x, y);
10794 if (GFX_CRUMBLED(new_element))
10795 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10797 if (old_element == EL_EXPLOSION)
10799 Store[x][y] = Store2[x][y] = 0;
10801 // check if new element replaces an exploding player, requiring cleanup
10802 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
10803 StorePlayer[x][y] = 0;
10806 // check if element under the player changes from accessible to unaccessible
10807 // (needed for special case of dropping element which then changes)
10808 // (must be checked after creating new element for walkable group elements)
10809 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10810 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10812 KillPlayer(PLAYERINFO(x, y));
10818 // "ChangeCount" not set yet to allow "entered by player" change one time
10819 if (new_element_is_player)
10820 RelocatePlayer(x, y, new_element);
10823 ChangeCount[x][y]++; // count number of changes in the same frame
10825 TestIfBadThingTouchesPlayer(x, y);
10826 TestIfPlayerTouchesCustomElement(x, y);
10827 TestIfElementTouchesCustomElement(x, y);
10830 static void CreateField(int x, int y, int element)
10832 CreateFieldExt(x, y, element, FALSE);
10835 static void CreateElementFromChange(int x, int y, int element)
10837 element = GET_VALID_RUNTIME_ELEMENT(element);
10839 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10841 int old_element = Tile[x][y];
10843 // prevent changed element from moving in same engine frame
10844 // unless both old and new element can either fall or move
10845 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10846 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10850 CreateFieldExt(x, y, element, TRUE);
10853 static boolean ChangeElement(int x, int y, int element, int page)
10855 struct ElementInfo *ei = &element_info[element];
10856 struct ElementChangeInfo *change = &ei->change_page[page];
10857 int ce_value = CustomValue[x][y];
10858 int ce_score = ei->collect_score;
10859 int target_element;
10860 int old_element = Tile[x][y];
10862 // always use default change event to prevent running into a loop
10863 if (ChangeEvent[x][y] == -1)
10864 ChangeEvent[x][y] = CE_DELAY;
10866 if (ChangeEvent[x][y] == CE_DELAY)
10868 // reset actual trigger element, trigger player and action element
10869 change->actual_trigger_element = EL_EMPTY;
10870 change->actual_trigger_player = EL_EMPTY;
10871 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10872 change->actual_trigger_side = CH_SIDE_NONE;
10873 change->actual_trigger_ce_value = 0;
10874 change->actual_trigger_ce_score = 0;
10875 change->actual_trigger_x = -1;
10876 change->actual_trigger_y = -1;
10879 // do not change elements more than a specified maximum number of changes
10880 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10883 ChangeCount[x][y]++; // count number of changes in the same frame
10885 if (ei->has_anim_event)
10886 HandleGlobalAnimEventByElementChange(element, page, x, y,
10887 change->actual_trigger_x,
10888 change->actual_trigger_y);
10890 if (change->explode)
10897 if (change->use_target_content)
10899 boolean complete_replace = TRUE;
10900 boolean can_replace[3][3];
10903 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10906 boolean is_walkable;
10907 boolean is_diggable;
10908 boolean is_collectible;
10909 boolean is_removable;
10910 boolean is_destructible;
10911 int ex = x + xx - 1;
10912 int ey = y + yy - 1;
10913 int content_element = change->target_content.e[xx][yy];
10916 can_replace[xx][yy] = TRUE;
10918 if (ex == x && ey == y) // do not check changing element itself
10921 if (content_element == EL_EMPTY_SPACE)
10923 can_replace[xx][yy] = FALSE; // do not replace border with space
10928 if (!IN_LEV_FIELD(ex, ey))
10930 can_replace[xx][yy] = FALSE;
10931 complete_replace = FALSE;
10938 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10939 e = MovingOrBlocked2Element(ex, ey);
10941 is_empty = (IS_FREE(ex, ey) ||
10942 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10944 is_walkable = (is_empty || IS_WALKABLE(e));
10945 is_diggable = (is_empty || IS_DIGGABLE(e));
10946 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10947 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10948 is_removable = (is_diggable || is_collectible);
10950 can_replace[xx][yy] =
10951 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10952 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10953 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10954 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10955 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10956 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10957 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10959 if (!can_replace[xx][yy])
10960 complete_replace = FALSE;
10963 if (!change->only_if_complete || complete_replace)
10965 boolean something_has_changed = FALSE;
10967 if (change->only_if_complete && change->use_random_replace &&
10968 RND(100) < change->random_percentage)
10971 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10973 int ex = x + xx - 1;
10974 int ey = y + yy - 1;
10975 int content_element;
10977 if (can_replace[xx][yy] && (!change->use_random_replace ||
10978 RND(100) < change->random_percentage))
10980 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10981 RemoveMovingField(ex, ey);
10983 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10985 content_element = change->target_content.e[xx][yy];
10986 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10987 ce_value, ce_score);
10989 CreateElementFromChange(ex, ey, target_element);
10991 something_has_changed = TRUE;
10993 // for symmetry reasons, freeze newly created border elements
10994 if (ex != x || ey != y)
10995 Stop[ex][ey] = TRUE; // no more moving in this frame
10999 if (something_has_changed)
11001 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
11002 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
11008 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
11009 ce_value, ce_score);
11011 if (element == EL_DIAGONAL_GROWING ||
11012 element == EL_DIAGONAL_SHRINKING)
11014 target_element = Store[x][y];
11016 Store[x][y] = EL_EMPTY;
11019 // special case: element changes to player (and may be kept if walkable)
11020 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
11021 CreateElementFromChange(x, y, EL_EMPTY);
11023 CreateElementFromChange(x, y, target_element);
11025 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
11026 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
11029 // this uses direct change before indirect change
11030 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
11035 static void HandleElementChange(int x, int y, int page)
11037 int element = MovingOrBlocked2Element(x, y);
11038 struct ElementInfo *ei = &element_info[element];
11039 struct ElementChangeInfo *change = &ei->change_page[page];
11040 boolean handle_action_before_change = FALSE;
11043 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
11044 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
11046 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
11047 x, y, element, element_info[element].token_name);
11048 Debug("game:playing:HandleElementChange", "This should never happen!");
11052 // this can happen with classic bombs on walkable, changing elements
11053 if (!CAN_CHANGE_OR_HAS_ACTION(element))
11058 if (ChangeDelay[x][y] == 0) // initialize element change
11060 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
11062 if (change->can_change)
11064 // !!! not clear why graphic animation should be reset at all here !!!
11065 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
11066 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
11069 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
11071 When using an animation frame delay of 1 (this only happens with
11072 "sp_zonk.moving.left/right" in the classic graphics), the default
11073 (non-moving) animation shows wrong animation frames (while the
11074 moving animation, like "sp_zonk.moving.left/right", is correct,
11075 so this graphical bug never shows up with the classic graphics).
11076 For an animation with 4 frames, this causes wrong frames 0,0,1,2
11077 be drawn instead of the correct frames 0,1,2,3. This is caused by
11078 "GfxFrame[][]" being reset *twice* (in two successive frames) after
11079 an element change: First when the change delay ("ChangeDelay[][]")
11080 counter has reached zero after decrementing, then a second time in
11081 the next frame (after "GfxFrame[][]" was already incremented) when
11082 "ChangeDelay[][]" is reset to the initial delay value again.
11084 This causes frame 0 to be drawn twice, while the last frame won't
11085 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
11087 As some animations may already be cleverly designed around this bug
11088 (at least the "Snake Bite" snake tail animation does this), it cannot
11089 simply be fixed here without breaking such existing animations.
11090 Unfortunately, it cannot easily be detected if a graphics set was
11091 designed "before" or "after" the bug was fixed. As a workaround,
11092 a new graphics set option "game.graphics_engine_version" was added
11093 to be able to specify the game's major release version for which the
11094 graphics set was designed, which can then be used to decide if the
11095 bugfix should be used (version 4 and above) or not (version 3 or
11096 below, or if no version was specified at all, as with old sets).
11098 (The wrong/fixed animation frames can be tested with the test level set
11099 "test_gfxframe" and level "000", which contains a specially prepared
11100 custom element at level position (x/y) == (11/9) which uses the zonk
11101 animation mentioned above. Using "game.graphics_engine_version: 4"
11102 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
11103 This can also be seen from the debug output for this test element.)
11106 // when a custom element is about to change (for example by change delay),
11107 // do not reset graphic animation when the custom element is moving
11108 if (game.graphics_engine_version < 4 &&
11111 ResetGfxAnimation(x, y);
11112 ResetRandomAnimationValue(x, y);
11115 if (change->pre_change_function)
11116 change->pre_change_function(x, y);
11120 ChangeDelay[x][y]--;
11122 if (ChangeDelay[x][y] != 0) // continue element change
11124 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11126 // also needed if CE can not change, but has CE delay with CE action
11127 if (IS_ANIMATED(graphic))
11128 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11130 if (change->can_change)
11132 if (change->change_function)
11133 change->change_function(x, y);
11136 else // finish element change
11138 if (ChangePage[x][y] != -1) // remember page from delayed change
11140 page = ChangePage[x][y];
11141 ChangePage[x][y] = -1;
11143 change = &ei->change_page[page];
11146 if (IS_MOVING(x, y)) // never change a running system ;-)
11148 ChangeDelay[x][y] = 1; // try change after next move step
11149 ChangePage[x][y] = page; // remember page to use for change
11154 // special case: set new level random seed before changing element
11155 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11156 handle_action_before_change = TRUE;
11158 if (change->has_action && handle_action_before_change)
11159 ExecuteCustomElementAction(x, y, element, page);
11161 if (change->can_change)
11163 if (ChangeElement(x, y, element, page))
11165 if (change->post_change_function)
11166 change->post_change_function(x, y);
11170 if (change->has_action && !handle_action_before_change)
11171 ExecuteCustomElementAction(x, y, element, page);
11175 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11176 int trigger_element,
11178 int trigger_player,
11182 boolean change_done_any = FALSE;
11183 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11186 if (!(trigger_events[trigger_element][trigger_event]))
11189 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11191 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11193 int element = EL_CUSTOM_START + i;
11194 boolean change_done = FALSE;
11197 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11198 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11201 for (p = 0; p < element_info[element].num_change_pages; p++)
11203 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11205 if (change->can_change_or_has_action &&
11206 change->has_event[trigger_event] &&
11207 change->trigger_side & trigger_side &&
11208 change->trigger_player & trigger_player &&
11209 change->trigger_page & trigger_page_bits &&
11210 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11212 change->actual_trigger_element = trigger_element;
11213 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11214 change->actual_trigger_player_bits = trigger_player;
11215 change->actual_trigger_side = trigger_side;
11216 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11217 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11218 change->actual_trigger_x = trigger_x;
11219 change->actual_trigger_y = trigger_y;
11221 if ((change->can_change && !change_done) || change->has_action)
11225 SCAN_PLAYFIELD(x, y)
11227 if (Tile[x][y] == element)
11229 if (change->can_change && !change_done)
11231 // if element already changed in this frame, not only prevent
11232 // another element change (checked in ChangeElement()), but
11233 // also prevent additional element actions for this element
11235 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11236 !level.use_action_after_change_bug)
11239 ChangeDelay[x][y] = 1;
11240 ChangeEvent[x][y] = trigger_event;
11242 HandleElementChange(x, y, p);
11244 else if (change->has_action)
11246 // if element already changed in this frame, not only prevent
11247 // another element change (checked in ChangeElement()), but
11248 // also prevent additional element actions for this element
11250 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11251 !level.use_action_after_change_bug)
11254 ExecuteCustomElementAction(x, y, element, p);
11255 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11260 if (change->can_change)
11262 change_done = TRUE;
11263 change_done_any = TRUE;
11270 RECURSION_LOOP_DETECTION_END();
11272 return change_done_any;
11275 static boolean CheckElementChangeExt(int x, int y,
11277 int trigger_element,
11279 int trigger_player,
11282 boolean change_done = FALSE;
11285 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11286 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11289 if (Tile[x][y] == EL_BLOCKED)
11291 Blocked2Moving(x, y, &x, &y);
11292 element = Tile[x][y];
11295 // check if element has already changed or is about to change after moving
11296 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11297 Tile[x][y] != element) ||
11299 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11300 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11301 ChangePage[x][y] != -1)))
11304 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11306 for (p = 0; p < element_info[element].num_change_pages; p++)
11308 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11310 /* check trigger element for all events where the element that is checked
11311 for changing interacts with a directly adjacent element -- this is
11312 different to element changes that affect other elements to change on the
11313 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11314 boolean check_trigger_element =
11315 (trigger_event == CE_NEXT_TO_X ||
11316 trigger_event == CE_TOUCHING_X ||
11317 trigger_event == CE_HITTING_X ||
11318 trigger_event == CE_HIT_BY_X ||
11319 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11321 if (change->can_change_or_has_action &&
11322 change->has_event[trigger_event] &&
11323 change->trigger_side & trigger_side &&
11324 change->trigger_player & trigger_player &&
11325 (!check_trigger_element ||
11326 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11328 change->actual_trigger_element = trigger_element;
11329 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11330 change->actual_trigger_player_bits = trigger_player;
11331 change->actual_trigger_side = trigger_side;
11332 change->actual_trigger_ce_value = CustomValue[x][y];
11333 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11334 change->actual_trigger_x = x;
11335 change->actual_trigger_y = y;
11337 // special case: trigger element not at (x,y) position for some events
11338 if (check_trigger_element)
11350 { 0, 0 }, { 0, 0 }, { 0, 0 },
11354 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11355 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11357 change->actual_trigger_ce_value = CustomValue[xx][yy];
11358 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11359 change->actual_trigger_x = xx;
11360 change->actual_trigger_y = yy;
11363 if (change->can_change && !change_done)
11365 ChangeDelay[x][y] = 1;
11366 ChangeEvent[x][y] = trigger_event;
11368 HandleElementChange(x, y, p);
11370 change_done = TRUE;
11372 else if (change->has_action)
11374 ExecuteCustomElementAction(x, y, element, p);
11375 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11380 RECURSION_LOOP_DETECTION_END();
11382 return change_done;
11385 static void PlayPlayerSound(struct PlayerInfo *player)
11387 int jx = player->jx, jy = player->jy;
11388 int sound_element = player->artwork_element;
11389 int last_action = player->last_action_waiting;
11390 int action = player->action_waiting;
11392 if (player->is_waiting)
11394 if (action != last_action)
11395 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11397 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11401 if (action != last_action)
11402 StopSound(element_info[sound_element].sound[last_action]);
11404 if (last_action == ACTION_SLEEPING)
11405 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11409 static void PlayAllPlayersSound(void)
11413 for (i = 0; i < MAX_PLAYERS; i++)
11414 if (stored_player[i].active)
11415 PlayPlayerSound(&stored_player[i]);
11418 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11420 boolean last_waiting = player->is_waiting;
11421 int move_dir = player->MovDir;
11423 player->dir_waiting = move_dir;
11424 player->last_action_waiting = player->action_waiting;
11428 if (!last_waiting) // not waiting -> waiting
11430 player->is_waiting = TRUE;
11432 player->frame_counter_bored =
11434 game.player_boring_delay_fixed +
11435 GetSimpleRandom(game.player_boring_delay_random);
11436 player->frame_counter_sleeping =
11438 game.player_sleeping_delay_fixed +
11439 GetSimpleRandom(game.player_sleeping_delay_random);
11441 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11444 if (game.player_sleeping_delay_fixed +
11445 game.player_sleeping_delay_random > 0 &&
11446 player->anim_delay_counter == 0 &&
11447 player->post_delay_counter == 0 &&
11448 FrameCounter >= player->frame_counter_sleeping)
11449 player->is_sleeping = TRUE;
11450 else if (game.player_boring_delay_fixed +
11451 game.player_boring_delay_random > 0 &&
11452 FrameCounter >= player->frame_counter_bored)
11453 player->is_bored = TRUE;
11455 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11456 player->is_bored ? ACTION_BORING :
11459 if (player->is_sleeping && player->use_murphy)
11461 // special case for sleeping Murphy when leaning against non-free tile
11463 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11464 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11465 !IS_MOVING(player->jx - 1, player->jy)))
11466 move_dir = MV_LEFT;
11467 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11468 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11469 !IS_MOVING(player->jx + 1, player->jy)))
11470 move_dir = MV_RIGHT;
11472 player->is_sleeping = FALSE;
11474 player->dir_waiting = move_dir;
11477 if (player->is_sleeping)
11479 if (player->num_special_action_sleeping > 0)
11481 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11483 int last_special_action = player->special_action_sleeping;
11484 int num_special_action = player->num_special_action_sleeping;
11485 int special_action =
11486 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11487 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11488 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11489 last_special_action + 1 : ACTION_SLEEPING);
11490 int special_graphic =
11491 el_act_dir2img(player->artwork_element, special_action, move_dir);
11493 player->anim_delay_counter =
11494 graphic_info[special_graphic].anim_delay_fixed +
11495 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11496 player->post_delay_counter =
11497 graphic_info[special_graphic].post_delay_fixed +
11498 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11500 player->special_action_sleeping = special_action;
11503 if (player->anim_delay_counter > 0)
11505 player->action_waiting = player->special_action_sleeping;
11506 player->anim_delay_counter--;
11508 else if (player->post_delay_counter > 0)
11510 player->post_delay_counter--;
11514 else if (player->is_bored)
11516 if (player->num_special_action_bored > 0)
11518 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11520 int special_action =
11521 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11522 int special_graphic =
11523 el_act_dir2img(player->artwork_element, special_action, move_dir);
11525 player->anim_delay_counter =
11526 graphic_info[special_graphic].anim_delay_fixed +
11527 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11528 player->post_delay_counter =
11529 graphic_info[special_graphic].post_delay_fixed +
11530 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11532 player->special_action_bored = special_action;
11535 if (player->anim_delay_counter > 0)
11537 player->action_waiting = player->special_action_bored;
11538 player->anim_delay_counter--;
11540 else if (player->post_delay_counter > 0)
11542 player->post_delay_counter--;
11547 else if (last_waiting) // waiting -> not waiting
11549 player->is_waiting = FALSE;
11550 player->is_bored = FALSE;
11551 player->is_sleeping = FALSE;
11553 player->frame_counter_bored = -1;
11554 player->frame_counter_sleeping = -1;
11556 player->anim_delay_counter = 0;
11557 player->post_delay_counter = 0;
11559 player->dir_waiting = player->MovDir;
11560 player->action_waiting = ACTION_DEFAULT;
11562 player->special_action_bored = ACTION_DEFAULT;
11563 player->special_action_sleeping = ACTION_DEFAULT;
11567 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11569 if ((!player->is_moving && player->was_moving) ||
11570 (player->MovPos == 0 && player->was_moving) ||
11571 (player->is_snapping && !player->was_snapping) ||
11572 (player->is_dropping && !player->was_dropping))
11574 if (!CheckSaveEngineSnapshotToList())
11577 player->was_moving = FALSE;
11578 player->was_snapping = TRUE;
11579 player->was_dropping = TRUE;
11583 if (player->is_moving)
11584 player->was_moving = TRUE;
11586 if (!player->is_snapping)
11587 player->was_snapping = FALSE;
11589 if (!player->is_dropping)
11590 player->was_dropping = FALSE;
11593 static struct MouseActionInfo mouse_action_last = { 0 };
11594 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11595 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11598 CheckSaveEngineSnapshotToList();
11600 mouse_action_last = mouse_action;
11603 static void CheckSingleStepMode(struct PlayerInfo *player)
11605 if (tape.single_step && tape.recording && !tape.pausing)
11607 // as it is called "single step mode", just return to pause mode when the
11608 // player stopped moving after one tile (or never starts moving at all)
11609 // (reverse logic needed here in case single step mode used in team mode)
11610 if (player->is_moving ||
11611 player->is_pushing ||
11612 player->is_dropping_pressed ||
11613 player->effective_mouse_action.button)
11614 game.enter_single_step_mode = FALSE;
11617 CheckSaveEngineSnapshot(player);
11620 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11622 int left = player_action & JOY_LEFT;
11623 int right = player_action & JOY_RIGHT;
11624 int up = player_action & JOY_UP;
11625 int down = player_action & JOY_DOWN;
11626 int button1 = player_action & JOY_BUTTON_1;
11627 int button2 = player_action & JOY_BUTTON_2;
11628 int dx = (left ? -1 : right ? 1 : 0);
11629 int dy = (up ? -1 : down ? 1 : 0);
11631 if (!player->active || tape.pausing)
11637 SnapField(player, dx, dy);
11641 DropElement(player);
11643 MovePlayer(player, dx, dy);
11646 CheckSingleStepMode(player);
11648 SetPlayerWaiting(player, FALSE);
11650 return player_action;
11654 // no actions for this player (no input at player's configured device)
11656 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11657 SnapField(player, 0, 0);
11658 CheckGravityMovementWhenNotMoving(player);
11660 if (player->MovPos == 0)
11661 SetPlayerWaiting(player, TRUE);
11663 if (player->MovPos == 0) // needed for tape.playing
11664 player->is_moving = FALSE;
11666 player->is_dropping = FALSE;
11667 player->is_dropping_pressed = FALSE;
11668 player->drop_pressed_delay = 0;
11670 CheckSingleStepMode(player);
11676 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11679 if (!tape.use_mouse_actions)
11682 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11683 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11684 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11687 static void SetTapeActionFromMouseAction(byte *tape_action,
11688 struct MouseActionInfo *mouse_action)
11690 if (!tape.use_mouse_actions)
11693 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11694 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11695 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11698 static void CheckLevelSolved(void)
11700 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11702 if (game_bd.level_solved &&
11703 !game_bd.game_over) // game won
11707 game_bd.game_over = TRUE;
11709 game.all_players_gone = TRUE;
11712 if (game_bd.game_over) // game lost
11713 game.all_players_gone = TRUE;
11715 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11717 if (game_em.level_solved &&
11718 !game_em.game_over) // game won
11722 game_em.game_over = TRUE;
11724 game.all_players_gone = TRUE;
11727 if (game_em.game_over) // game lost
11728 game.all_players_gone = TRUE;
11730 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11732 if (game_sp.level_solved &&
11733 !game_sp.game_over) // game won
11737 game_sp.game_over = TRUE;
11739 game.all_players_gone = TRUE;
11742 if (game_sp.game_over) // game lost
11743 game.all_players_gone = TRUE;
11745 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11747 if (game_mm.level_solved &&
11748 !game_mm.game_over) // game won
11752 game_mm.game_over = TRUE;
11754 game.all_players_gone = TRUE;
11757 if (game_mm.game_over) // game lost
11758 game.all_players_gone = TRUE;
11762 static void PlayTimeoutSound(int seconds_left)
11764 // will be played directly by BD engine (for classic bonus time sounds)
11765 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && checkBonusTime_BD())
11768 // try to use individual "running out of time" sound for each second left
11769 int sound = SND_GAME_RUNNING_OUT_OF_TIME_0 - seconds_left;
11771 // if special sound per second not defined, use default sound
11772 if (getSoundInfoEntryFilename(sound) == NULL)
11773 sound = SND_GAME_RUNNING_OUT_OF_TIME;
11775 // if out of time, but player still alive, play special "timeout" sound, if defined
11776 if (seconds_left == 0 && !checkGameFailed())
11777 if (getSoundInfoEntryFilename(SND_GAME_TIMEOUT) != NULL)
11778 sound = SND_GAME_TIMEOUT;
11783 static void CheckLevelTime_StepCounter(void)
11793 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11794 PlayTimeoutSound(TimeLeft);
11796 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11798 DisplayGameControlValues();
11800 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11801 for (i = 0; i < MAX_PLAYERS; i++)
11802 KillPlayer(&stored_player[i]);
11804 else if (game.no_level_time_limit && !game.all_players_gone)
11806 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11808 DisplayGameControlValues();
11812 static void CheckLevelTime(void)
11814 int frames_per_second = FRAMES_PER_SECOND;
11817 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11819 // level time may be running slower in native BD engine
11820 frames_per_second = getFramesPerSecond_BD();
11822 // if native engine time changed, force main engine time change
11823 if (getTimeLeft_BD() < TimeLeft)
11824 TimeFrames = frames_per_second;
11826 // if last second running, wait for native engine time to exactly reach zero
11827 if (getTimeLeft_BD() == 1 && TimeLeft == 1)
11828 TimeFrames = frames_per_second - 1;
11831 if (TimeFrames >= frames_per_second)
11835 for (i = 0; i < MAX_PLAYERS; i++)
11837 struct PlayerInfo *player = &stored_player[i];
11839 if (SHIELD_ON(player))
11841 player->shield_normal_time_left--;
11843 if (player->shield_deadly_time_left > 0)
11844 player->shield_deadly_time_left--;
11848 if (!game.LevelSolved && !level.use_step_counter)
11856 if (TimeLeft <= 10 && game.time_limit)
11857 PlayTimeoutSound(TimeLeft);
11859 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11860 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11862 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11864 if (!TimeLeft && game.time_limit)
11866 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11868 if (game_bd.game->cave->player_state == GD_PL_LIVING)
11869 game_bd.game->cave->player_state = GD_PL_TIMEOUT;
11871 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11873 game_em.lev->killed_out_of_time = TRUE;
11877 for (i = 0; i < MAX_PLAYERS; i++)
11878 KillPlayer(&stored_player[i]);
11882 else if (game.no_level_time_limit && !game.all_players_gone)
11884 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11887 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11891 if (TapeTimeFrames >= FRAMES_PER_SECOND)
11893 TapeTimeFrames = 0;
11896 if (tape.recording || tape.playing)
11897 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11900 if (tape.recording || tape.playing)
11901 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11903 UpdateAndDisplayGameControlValues();
11906 void AdvanceFrameAndPlayerCounters(int player_nr)
11910 // handle game and tape time differently for native BD game engine
11912 // tape time is running in native BD engine even if player is not hatched yet
11913 if (!checkGameRunning())
11916 // advance frame counters (global frame counter and tape time frame counter)
11920 // level time is running in native BD engine after player is being hatched
11921 if (!checkGamePlaying())
11924 // advance time frame counter (used to control available time to solve level)
11927 // advance player counters (counters for move delay, move animation etc.)
11928 for (i = 0; i < MAX_PLAYERS; i++)
11930 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11931 int move_delay_value = stored_player[i].move_delay_value;
11932 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11934 if (!advance_player_counters) // not all players may be affected
11937 if (move_frames == 0) // less than one move per game frame
11939 int stepsize = TILEX / move_delay_value;
11940 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11941 int count = (stored_player[i].is_moving ?
11942 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11944 if (count % delay == 0)
11948 stored_player[i].Frame += move_frames;
11950 if (stored_player[i].MovPos != 0)
11951 stored_player[i].StepFrame += move_frames;
11953 if (stored_player[i].move_delay > 0)
11954 stored_player[i].move_delay--;
11956 // due to bugs in previous versions, counter must count up, not down
11957 if (stored_player[i].push_delay != -1)
11958 stored_player[i].push_delay++;
11960 if (stored_player[i].drop_delay > 0)
11961 stored_player[i].drop_delay--;
11963 if (stored_player[i].is_dropping_pressed)
11964 stored_player[i].drop_pressed_delay++;
11968 void AdvanceFrameCounter(void)
11973 void AdvanceGfxFrame(void)
11977 SCAN_PLAYFIELD(x, y)
11983 static void HandleMouseAction(struct MouseActionInfo *mouse_action,
11984 struct MouseActionInfo *mouse_action_last)
11986 if (mouse_action->button)
11988 int new_button = (mouse_action->button && mouse_action_last->button == 0);
11989 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button);
11990 int x = mouse_action->lx;
11991 int y = mouse_action->ly;
11992 int element = Tile[x][y];
11996 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
11997 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12001 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12002 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12005 if (level.use_step_counter)
12007 boolean counted_click = FALSE;
12009 // element clicked that can change when clicked/pressed
12010 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
12011 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
12012 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
12013 counted_click = TRUE;
12015 // element clicked that can trigger change when clicked/pressed
12016 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
12017 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
12018 counted_click = TRUE;
12020 if (new_button && counted_click)
12021 CheckLevelTime_StepCounter();
12026 void StartGameActions(boolean init_network_game, boolean record_tape,
12029 unsigned int new_random_seed = InitRND(random_seed);
12032 TapeStartRecording(new_random_seed);
12034 if (setup.auto_pause_on_start && !tape.pausing)
12035 TapeTogglePause(TAPE_TOGGLE_MANUAL);
12037 if (init_network_game)
12039 SendToServer_LevelFile();
12040 SendToServer_StartPlaying();
12048 static void GameActionsExt(void)
12051 static unsigned int game_frame_delay = 0;
12053 unsigned int game_frame_delay_value;
12054 byte *recorded_player_action;
12055 byte summarized_player_action = 0;
12056 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
12059 // detect endless loops, caused by custom element programming
12060 if (recursion_loop_detected && recursion_loop_depth == 0)
12062 char *message = getStringCat3("Internal Error! Element ",
12063 EL_NAME(recursion_loop_element),
12064 " caused endless loop! Quit the game?");
12066 Warn("element '%s' caused endless loop in game engine",
12067 EL_NAME(recursion_loop_element));
12069 RequestQuitGameExt(program.headless, level_editor_test_game, message);
12071 recursion_loop_detected = FALSE; // if game should be continued
12078 if (game.restart_level)
12079 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
12081 CheckLevelSolved();
12083 if (game.LevelSolved && !game.LevelSolved_GameEnd)
12086 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
12089 if (game_status != GAME_MODE_PLAYING) // status might have changed
12092 game_frame_delay_value =
12093 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
12095 if (tape.playing && tape.warp_forward && !tape.pausing)
12096 game_frame_delay_value = 0;
12098 SetVideoFrameDelay(game_frame_delay_value);
12100 // (de)activate virtual buttons depending on current game status
12101 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
12103 if (game.all_players_gone) // if no players there to be controlled anymore
12104 SetOverlayActive(FALSE);
12105 else if (!tape.playing) // if game continues after tape stopped playing
12106 SetOverlayActive(TRUE);
12111 // ---------- main game synchronization point ----------
12113 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12115 Debug("game:playing:skip", "skip == %d", skip);
12118 // ---------- main game synchronization point ----------
12120 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12124 if (network_playing && !network_player_action_received)
12126 // try to get network player actions in time
12128 // last chance to get network player actions without main loop delay
12129 HandleNetworking();
12131 // game was quit by network peer
12132 if (game_status != GAME_MODE_PLAYING)
12135 // check if network player actions still missing and game still running
12136 if (!network_player_action_received && !checkGameEnded())
12137 return; // failed to get network player actions in time
12139 // do not yet reset "network_player_action_received" (for tape.pausing)
12145 // at this point we know that we really continue executing the game
12147 network_player_action_received = FALSE;
12149 // when playing tape, read previously recorded player input from tape data
12150 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
12152 local_player->effective_mouse_action = local_player->mouse_action;
12154 if (recorded_player_action != NULL)
12155 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
12156 recorded_player_action);
12158 // TapePlayAction() may return NULL when toggling to "pause before death"
12162 if (tape.set_centered_player)
12164 game.centered_player_nr_next = tape.centered_player_nr_next;
12165 game.set_centered_player = TRUE;
12168 for (i = 0; i < MAX_PLAYERS; i++)
12170 summarized_player_action |= stored_player[i].action;
12172 if (!network_playing && (game.team_mode || tape.playing))
12173 stored_player[i].effective_action = stored_player[i].action;
12176 if (network_playing && !checkGameEnded())
12177 SendToServer_MovePlayer(summarized_player_action);
12179 // summarize all actions at local players mapped input device position
12180 // (this allows using different input devices in single player mode)
12181 if (!network.enabled && !game.team_mode)
12182 stored_player[map_player_action[local_player->index_nr]].effective_action =
12183 summarized_player_action;
12185 // summarize all actions at centered player in local team mode
12186 if (tape.recording &&
12187 setup.team_mode && !network.enabled &&
12188 setup.input_on_focus &&
12189 game.centered_player_nr != -1)
12191 for (i = 0; i < MAX_PLAYERS; i++)
12192 stored_player[map_player_action[i]].effective_action =
12193 (i == game.centered_player_nr ? summarized_player_action : 0);
12196 if (recorded_player_action != NULL)
12197 for (i = 0; i < MAX_PLAYERS; i++)
12198 stored_player[i].effective_action = recorded_player_action[i];
12200 for (i = 0; i < MAX_PLAYERS; i++)
12202 tape_action[i] = stored_player[i].effective_action;
12204 /* (this may happen in the RND game engine if a player was not present on
12205 the playfield on level start, but appeared later from a custom element */
12206 if (setup.team_mode &&
12209 !tape.player_participates[i])
12210 tape.player_participates[i] = TRUE;
12213 SetTapeActionFromMouseAction(tape_action,
12214 &local_player->effective_mouse_action);
12216 // only record actions from input devices, but not programmed actions
12217 if (tape.recording)
12218 TapeRecordAction(tape_action);
12220 // remember if game was played (especially after tape stopped playing)
12221 if (!tape.playing && summarized_player_action && !checkGameFailed())
12222 game.GamePlayed = TRUE;
12224 #if USE_NEW_PLAYER_ASSIGNMENTS
12225 // !!! also map player actions in single player mode !!!
12226 // if (game.team_mode)
12229 byte mapped_action[MAX_PLAYERS];
12231 #if DEBUG_PLAYER_ACTIONS
12232 for (i = 0; i < MAX_PLAYERS; i++)
12233 DebugContinued("", "%d, ", stored_player[i].effective_action);
12236 for (i = 0; i < MAX_PLAYERS; i++)
12237 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
12239 for (i = 0; i < MAX_PLAYERS; i++)
12240 stored_player[i].effective_action = mapped_action[i];
12242 #if DEBUG_PLAYER_ACTIONS
12243 DebugContinued("", "=> ");
12244 for (i = 0; i < MAX_PLAYERS; i++)
12245 DebugContinued("", "%d, ", stored_player[i].effective_action);
12246 DebugContinued("game:playing:player", "\n");
12249 #if DEBUG_PLAYER_ACTIONS
12252 for (i = 0; i < MAX_PLAYERS; i++)
12253 DebugContinued("", "%d, ", stored_player[i].effective_action);
12254 DebugContinued("game:playing:player", "\n");
12259 for (i = 0; i < MAX_PLAYERS; i++)
12261 // allow engine snapshot in case of changed movement attempt
12262 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12263 (stored_player[i].effective_action & KEY_MOTION))
12264 game.snapshot.changed_action = TRUE;
12266 // allow engine snapshot in case of snapping/dropping attempt
12267 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12268 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12269 game.snapshot.changed_action = TRUE;
12271 game.snapshot.last_action[i] = stored_player[i].effective_action;
12274 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
12276 GameActions_BD_Main();
12278 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12280 GameActions_EM_Main();
12282 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12284 GameActions_SP_Main();
12286 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12288 GameActions_MM_Main();
12292 GameActions_RND_Main();
12295 BlitScreenToBitmap(backbuffer);
12297 CheckLevelSolved();
12300 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12302 if (global.show_frames_per_second)
12304 static unsigned int fps_counter = 0;
12305 static int fps_frames = 0;
12306 unsigned int fps_delay_ms = Counter() - fps_counter;
12310 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12312 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12315 fps_counter = Counter();
12317 // always draw FPS to screen after FPS value was updated
12318 redraw_mask |= REDRAW_FPS;
12321 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12322 if (GetDrawDeactivationMask() == REDRAW_NONE)
12323 redraw_mask |= REDRAW_FPS;
12327 static void GameActions_CheckSaveEngineSnapshot(void)
12329 if (!game.snapshot.save_snapshot)
12332 // clear flag for saving snapshot _before_ saving snapshot
12333 game.snapshot.save_snapshot = FALSE;
12335 SaveEngineSnapshotToList();
12338 void GameActions(void)
12342 GameActions_CheckSaveEngineSnapshot();
12345 void GameActions_BD_Main(void)
12347 byte effective_action[MAX_PLAYERS];
12350 for (i = 0; i < MAX_PLAYERS; i++)
12351 effective_action[i] = stored_player[i].effective_action;
12353 GameActions_BD(effective_action);
12356 void GameActions_EM_Main(void)
12358 byte effective_action[MAX_PLAYERS];
12361 for (i = 0; i < MAX_PLAYERS; i++)
12362 effective_action[i] = stored_player[i].effective_action;
12364 GameActions_EM(effective_action);
12367 void GameActions_SP_Main(void)
12369 byte effective_action[MAX_PLAYERS];
12372 for (i = 0; i < MAX_PLAYERS; i++)
12373 effective_action[i] = stored_player[i].effective_action;
12375 GameActions_SP(effective_action);
12377 for (i = 0; i < MAX_PLAYERS; i++)
12379 if (stored_player[i].force_dropping)
12380 stored_player[i].action |= KEY_BUTTON_DROP;
12382 stored_player[i].force_dropping = FALSE;
12386 void GameActions_MM_Main(void)
12390 GameActions_MM(local_player->effective_mouse_action);
12393 void GameActions_RND_Main(void)
12398 void GameActions_RND(void)
12400 static struct MouseActionInfo mouse_action_last = { 0 };
12401 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12402 int magic_wall_x = 0, magic_wall_y = 0;
12403 int i, x, y, element, graphic, last_gfx_frame;
12405 InitPlayfieldScanModeVars();
12407 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12409 SCAN_PLAYFIELD(x, y)
12411 ChangeCount[x][y] = 0;
12412 ChangeEvent[x][y] = -1;
12416 if (game.set_centered_player)
12418 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12420 // switching to "all players" only possible if all players fit to screen
12421 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12423 game.centered_player_nr_next = game.centered_player_nr;
12424 game.set_centered_player = FALSE;
12427 // do not switch focus to non-existing (or non-active) player
12428 if (game.centered_player_nr_next >= 0 &&
12429 !stored_player[game.centered_player_nr_next].active)
12431 game.centered_player_nr_next = game.centered_player_nr;
12432 game.set_centered_player = FALSE;
12436 if (game.set_centered_player &&
12437 ScreenMovPos == 0) // screen currently aligned at tile position
12441 if (game.centered_player_nr_next == -1)
12443 setScreenCenteredToAllPlayers(&sx, &sy);
12447 sx = stored_player[game.centered_player_nr_next].jx;
12448 sy = stored_player[game.centered_player_nr_next].jy;
12451 game.centered_player_nr = game.centered_player_nr_next;
12452 game.set_centered_player = FALSE;
12454 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12455 DrawGameDoorValues();
12458 // check single step mode (set flag and clear again if any player is active)
12459 game.enter_single_step_mode =
12460 (tape.single_step && tape.recording && !tape.pausing);
12462 for (i = 0; i < MAX_PLAYERS; i++)
12464 int actual_player_action = stored_player[i].effective_action;
12467 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12468 - rnd_equinox_tetrachloride 048
12469 - rnd_equinox_tetrachloride_ii 096
12470 - rnd_emanuel_schmieg 002
12471 - doctor_sloan_ww 001, 020
12473 if (stored_player[i].MovPos == 0)
12474 CheckGravityMovement(&stored_player[i]);
12477 // overwrite programmed action with tape action
12478 if (stored_player[i].programmed_action)
12479 actual_player_action = stored_player[i].programmed_action;
12481 PlayerActions(&stored_player[i], actual_player_action);
12483 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12486 // single step pause mode may already have been toggled by "ScrollPlayer()"
12487 if (game.enter_single_step_mode && !tape.pausing)
12488 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12490 ScrollScreen(NULL, SCROLL_GO_ON);
12492 /* for backwards compatibility, the following code emulates a fixed bug that
12493 occured when pushing elements (causing elements that just made their last
12494 pushing step to already (if possible) make their first falling step in the
12495 same game frame, which is bad); this code is also needed to use the famous
12496 "spring push bug" which is used in older levels and might be wanted to be
12497 used also in newer levels, but in this case the buggy pushing code is only
12498 affecting the "spring" element and no other elements */
12500 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12502 for (i = 0; i < MAX_PLAYERS; i++)
12504 struct PlayerInfo *player = &stored_player[i];
12505 int x = player->jx;
12506 int y = player->jy;
12508 if (player->active && player->is_pushing && player->is_moving &&
12510 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12511 Tile[x][y] == EL_SPRING))
12513 ContinueMoving(x, y);
12515 // continue moving after pushing (this is actually a bug)
12516 if (!IS_MOVING(x, y))
12517 Stop[x][y] = FALSE;
12522 SCAN_PLAYFIELD(x, y)
12524 Last[x][y] = Tile[x][y];
12526 ChangeCount[x][y] = 0;
12527 ChangeEvent[x][y] = -1;
12529 // this must be handled before main playfield loop
12530 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12533 if (MovDelay[x][y] <= 0)
12537 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12540 if (MovDelay[x][y] <= 0)
12542 int element = Store[x][y];
12543 int move_direction = MovDir[x][y];
12544 int player_index_bit = Store2[x][y];
12550 TEST_DrawLevelField(x, y);
12552 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12554 if (IS_ENVELOPE(element))
12555 local_player->show_envelope = element;
12560 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12562 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12564 Debug("game:playing:GameActions_RND", "This should never happen!");
12566 ChangePage[x][y] = -1;
12570 Stop[x][y] = FALSE;
12571 if (WasJustMoving[x][y] > 0)
12572 WasJustMoving[x][y]--;
12573 if (WasJustFalling[x][y] > 0)
12574 WasJustFalling[x][y]--;
12575 if (CheckCollision[x][y] > 0)
12576 CheckCollision[x][y]--;
12577 if (CheckImpact[x][y] > 0)
12578 CheckImpact[x][y]--;
12582 /* reset finished pushing action (not done in ContinueMoving() to allow
12583 continuous pushing animation for elements with zero push delay) */
12584 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12586 ResetGfxAnimation(x, y);
12587 TEST_DrawLevelField(x, y);
12591 if (IS_BLOCKED(x, y))
12595 Blocked2Moving(x, y, &oldx, &oldy);
12596 if (!IS_MOVING(oldx, oldy))
12598 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12599 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12600 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12601 Debug("game:playing:GameActions_RND", "This should never happen!");
12607 HandleMouseAction(&mouse_action, &mouse_action_last);
12609 SCAN_PLAYFIELD(x, y)
12611 element = Tile[x][y];
12612 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12613 last_gfx_frame = GfxFrame[x][y];
12615 if (element == EL_EMPTY)
12616 graphic = el2img(GfxElementEmpty[x][y]);
12618 ResetGfxFrame(x, y);
12620 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12621 DrawLevelGraphicAnimation(x, y, graphic);
12623 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12624 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12625 ResetRandomAnimationValue(x, y);
12627 SetRandomAnimationValue(x, y);
12629 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12631 if (IS_INACTIVE(element))
12633 if (IS_ANIMATED(graphic))
12634 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12639 // this may take place after moving, so 'element' may have changed
12640 if (IS_CHANGING(x, y) &&
12641 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12643 int page = element_info[element].event_page_nr[CE_DELAY];
12645 HandleElementChange(x, y, page);
12647 element = Tile[x][y];
12648 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12651 CheckNextToConditions(x, y);
12653 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12657 element = Tile[x][y];
12658 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12660 if (IS_ANIMATED(graphic) &&
12661 !IS_MOVING(x, y) &&
12663 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12665 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12666 TEST_DrawTwinkleOnField(x, y);
12668 else if (element == EL_ACID)
12671 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12673 else if ((element == EL_EXIT_OPEN ||
12674 element == EL_EM_EXIT_OPEN ||
12675 element == EL_SP_EXIT_OPEN ||
12676 element == EL_STEEL_EXIT_OPEN ||
12677 element == EL_EM_STEEL_EXIT_OPEN ||
12678 element == EL_SP_TERMINAL ||
12679 element == EL_SP_TERMINAL_ACTIVE ||
12680 element == EL_EXTRA_TIME ||
12681 element == EL_SHIELD_NORMAL ||
12682 element == EL_SHIELD_DEADLY) &&
12683 IS_ANIMATED(graphic))
12684 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12685 else if (IS_MOVING(x, y))
12686 ContinueMoving(x, y);
12687 else if (IS_ACTIVE_BOMB(element))
12688 CheckDynamite(x, y);
12689 else if (element == EL_AMOEBA_GROWING)
12690 AmoebaGrowing(x, y);
12691 else if (element == EL_AMOEBA_SHRINKING)
12692 AmoebaShrinking(x, y);
12694 #if !USE_NEW_AMOEBA_CODE
12695 else if (IS_AMOEBALIVE(element))
12696 AmoebaReproduce(x, y);
12699 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12701 else if (element == EL_EXIT_CLOSED)
12703 else if (element == EL_EM_EXIT_CLOSED)
12705 else if (element == EL_STEEL_EXIT_CLOSED)
12706 CheckExitSteel(x, y);
12707 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12708 CheckExitSteelEM(x, y);
12709 else if (element == EL_SP_EXIT_CLOSED)
12711 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12712 element == EL_EXPANDABLE_STEELWALL_GROWING)
12714 else if (element == EL_EXPANDABLE_WALL ||
12715 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12716 element == EL_EXPANDABLE_WALL_VERTICAL ||
12717 element == EL_EXPANDABLE_WALL_ANY ||
12718 element == EL_BD_EXPANDABLE_WALL ||
12719 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12720 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12721 element == EL_EXPANDABLE_STEELWALL_ANY)
12722 CheckWallGrowing(x, y);
12723 else if (element == EL_FLAMES)
12724 CheckForDragon(x, y);
12725 else if (element == EL_EXPLOSION)
12726 ; // drawing of correct explosion animation is handled separately
12727 else if (element == EL_ELEMENT_SNAPPING ||
12728 element == EL_DIAGONAL_SHRINKING ||
12729 element == EL_DIAGONAL_GROWING)
12731 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]);
12733 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12735 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12736 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12738 if (IS_BELT_ACTIVE(element))
12739 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12741 if (game.magic_wall_active)
12743 int jx = local_player->jx, jy = local_player->jy;
12745 // play the element sound at the position nearest to the player
12746 if ((element == EL_MAGIC_WALL_FULL ||
12747 element == EL_MAGIC_WALL_ACTIVE ||
12748 element == EL_MAGIC_WALL_EMPTYING ||
12749 element == EL_BD_MAGIC_WALL_FULL ||
12750 element == EL_BD_MAGIC_WALL_ACTIVE ||
12751 element == EL_BD_MAGIC_WALL_EMPTYING ||
12752 element == EL_DC_MAGIC_WALL_FULL ||
12753 element == EL_DC_MAGIC_WALL_ACTIVE ||
12754 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12755 ABS(x - jx) + ABS(y - jy) <
12756 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12764 #if USE_NEW_AMOEBA_CODE
12765 // new experimental amoeba growth stuff
12766 if (!(FrameCounter % 8))
12768 static unsigned int random = 1684108901;
12770 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12772 x = RND(lev_fieldx);
12773 y = RND(lev_fieldy);
12774 element = Tile[x][y];
12776 if (!IS_PLAYER(x, y) &&
12777 (element == EL_EMPTY ||
12778 CAN_GROW_INTO(element) ||
12779 element == EL_QUICKSAND_EMPTY ||
12780 element == EL_QUICKSAND_FAST_EMPTY ||
12781 element == EL_ACID_SPLASH_LEFT ||
12782 element == EL_ACID_SPLASH_RIGHT))
12784 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12785 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12786 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12787 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12788 Tile[x][y] = EL_AMOEBA_DROP;
12791 random = random * 129 + 1;
12796 game.explosions_delayed = FALSE;
12798 SCAN_PLAYFIELD(x, y)
12800 element = Tile[x][y];
12802 if (ExplodeField[x][y])
12803 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12804 else if (element == EL_EXPLOSION)
12805 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12807 ExplodeField[x][y] = EX_TYPE_NONE;
12810 game.explosions_delayed = TRUE;
12812 if (game.magic_wall_active)
12814 if (!(game.magic_wall_time_left % 4))
12816 int element = Tile[magic_wall_x][magic_wall_y];
12818 if (element == EL_BD_MAGIC_WALL_FULL ||
12819 element == EL_BD_MAGIC_WALL_ACTIVE ||
12820 element == EL_BD_MAGIC_WALL_EMPTYING)
12821 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12822 else if (element == EL_DC_MAGIC_WALL_FULL ||
12823 element == EL_DC_MAGIC_WALL_ACTIVE ||
12824 element == EL_DC_MAGIC_WALL_EMPTYING)
12825 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12827 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12830 if (game.magic_wall_time_left > 0)
12832 game.magic_wall_time_left--;
12834 if (!game.magic_wall_time_left)
12836 SCAN_PLAYFIELD(x, y)
12838 element = Tile[x][y];
12840 if (element == EL_MAGIC_WALL_ACTIVE ||
12841 element == EL_MAGIC_WALL_FULL)
12843 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12844 TEST_DrawLevelField(x, y);
12846 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12847 element == EL_BD_MAGIC_WALL_FULL)
12849 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12850 TEST_DrawLevelField(x, y);
12852 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12853 element == EL_DC_MAGIC_WALL_FULL)
12855 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12856 TEST_DrawLevelField(x, y);
12860 game.magic_wall_active = FALSE;
12865 if (game.light_time_left > 0)
12867 game.light_time_left--;
12869 if (game.light_time_left == 0)
12870 RedrawAllLightSwitchesAndInvisibleElements();
12873 if (game.timegate_time_left > 0)
12875 game.timegate_time_left--;
12877 if (game.timegate_time_left == 0)
12878 CloseAllOpenTimegates();
12881 if (game.lenses_time_left > 0)
12883 game.lenses_time_left--;
12885 if (game.lenses_time_left == 0)
12886 RedrawAllInvisibleElementsForLenses();
12889 if (game.magnify_time_left > 0)
12891 game.magnify_time_left--;
12893 if (game.magnify_time_left == 0)
12894 RedrawAllInvisibleElementsForMagnifier();
12897 for (i = 0; i < MAX_PLAYERS; i++)
12899 struct PlayerInfo *player = &stored_player[i];
12901 if (SHIELD_ON(player))
12903 if (player->shield_deadly_time_left)
12904 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12905 else if (player->shield_normal_time_left)
12906 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12910 #if USE_DELAYED_GFX_REDRAW
12911 SCAN_PLAYFIELD(x, y)
12913 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12915 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12916 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12918 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12919 DrawLevelField(x, y);
12921 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12922 DrawLevelFieldCrumbled(x, y);
12924 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12925 DrawLevelFieldCrumbledNeighbours(x, y);
12927 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12928 DrawTwinkleOnField(x, y);
12931 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12936 PlayAllPlayersSound();
12938 for (i = 0; i < MAX_PLAYERS; i++)
12940 struct PlayerInfo *player = &stored_player[i];
12942 if (player->show_envelope != 0 && (!player->active ||
12943 player->MovPos == 0))
12945 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12947 player->show_envelope = 0;
12951 // use random number generator in every frame to make it less predictable
12952 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12955 mouse_action_last = mouse_action;
12958 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12960 int min_x = x, min_y = y, max_x = x, max_y = y;
12961 int scr_fieldx = getScreenFieldSizeX();
12962 int scr_fieldy = getScreenFieldSizeY();
12965 for (i = 0; i < MAX_PLAYERS; i++)
12967 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12969 if (!stored_player[i].active || &stored_player[i] == player)
12972 min_x = MIN(min_x, jx);
12973 min_y = MIN(min_y, jy);
12974 max_x = MAX(max_x, jx);
12975 max_y = MAX(max_y, jy);
12978 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12981 static boolean AllPlayersInVisibleScreen(void)
12985 for (i = 0; i < MAX_PLAYERS; i++)
12987 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12989 if (!stored_player[i].active)
12992 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12999 void ScrollLevel(int dx, int dy)
13001 int scroll_offset = 2 * TILEX_VAR;
13004 BlitBitmap(drawto_field, drawto_field,
13005 FX + TILEX_VAR * (dx == -1) - scroll_offset,
13006 FY + TILEY_VAR * (dy == -1) - scroll_offset,
13007 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
13008 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
13009 FX + TILEX_VAR * (dx == 1) - scroll_offset,
13010 FY + TILEY_VAR * (dy == 1) - scroll_offset);
13014 x = (dx == 1 ? BX1 : BX2);
13015 for (y = BY1; y <= BY2; y++)
13016 DrawScreenField(x, y);
13021 y = (dy == 1 ? BY1 : BY2);
13022 for (x = BX1; x <= BX2; x++)
13023 DrawScreenField(x, y);
13026 redraw_mask |= REDRAW_FIELD;
13029 static boolean canFallDown(struct PlayerInfo *player)
13031 int jx = player->jx, jy = player->jy;
13033 return (IN_LEV_FIELD(jx, jy + 1) &&
13034 (IS_FREE(jx, jy + 1) ||
13035 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
13036 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
13037 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
13040 static boolean canPassField(int x, int y, int move_dir)
13042 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13043 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13044 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13045 int nextx = x + dx;
13046 int nexty = y + dy;
13047 int element = Tile[x][y];
13049 return (IS_PASSABLE_FROM(element, opposite_dir) &&
13050 !CAN_MOVE(element) &&
13051 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
13052 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
13053 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
13056 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
13058 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13059 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13060 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13064 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
13065 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
13066 (IS_DIGGABLE(Tile[newx][newy]) ||
13067 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
13068 canPassField(newx, newy, move_dir)));
13071 static void CheckGravityMovement(struct PlayerInfo *player)
13073 if (player->gravity && !player->programmed_action)
13075 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
13076 int move_dir_vertical = player->effective_action & MV_VERTICAL;
13077 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
13078 int jx = player->jx, jy = player->jy;
13079 boolean player_is_moving_to_valid_field =
13080 (!player_is_snapping &&
13081 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
13082 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
13083 boolean player_can_fall_down = canFallDown(player);
13085 if (player_can_fall_down &&
13086 !player_is_moving_to_valid_field)
13087 player->programmed_action = MV_DOWN;
13091 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
13093 return CheckGravityMovement(player);
13095 if (player->gravity && !player->programmed_action)
13097 int jx = player->jx, jy = player->jy;
13098 boolean field_under_player_is_free =
13099 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
13100 boolean player_is_standing_on_valid_field =
13101 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
13102 (IS_WALKABLE(Tile[jx][jy]) &&
13103 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
13105 if (field_under_player_is_free && !player_is_standing_on_valid_field)
13106 player->programmed_action = MV_DOWN;
13111 MovePlayerOneStep()
13112 -----------------------------------------------------------------------------
13113 dx, dy: direction (non-diagonal) to try to move the player to
13114 real_dx, real_dy: direction as read from input device (can be diagonal)
13117 boolean MovePlayerOneStep(struct PlayerInfo *player,
13118 int dx, int dy, int real_dx, int real_dy)
13120 int jx = player->jx, jy = player->jy;
13121 int new_jx = jx + dx, new_jy = jy + dy;
13123 boolean player_can_move = !player->cannot_move;
13125 if (!player->active || (!dx && !dy))
13126 return MP_NO_ACTION;
13128 player->MovDir = (dx < 0 ? MV_LEFT :
13129 dx > 0 ? MV_RIGHT :
13131 dy > 0 ? MV_DOWN : MV_NONE);
13133 if (!IN_LEV_FIELD(new_jx, new_jy))
13134 return MP_NO_ACTION;
13136 if (!player_can_move)
13138 if (player->MovPos == 0)
13140 player->is_moving = FALSE;
13141 player->is_digging = FALSE;
13142 player->is_collecting = FALSE;
13143 player->is_snapping = FALSE;
13144 player->is_pushing = FALSE;
13148 if (!network.enabled && game.centered_player_nr == -1 &&
13149 !AllPlayersInSight(player, new_jx, new_jy))
13150 return MP_NO_ACTION;
13152 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG);
13153 if (can_move != MP_MOVING)
13156 // check if DigField() has caused relocation of the player
13157 if (player->jx != jx || player->jy != jy)
13158 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
13160 StorePlayer[jx][jy] = 0;
13161 player->last_jx = jx;
13162 player->last_jy = jy;
13163 player->jx = new_jx;
13164 player->jy = new_jy;
13165 StorePlayer[new_jx][new_jy] = player->element_nr;
13167 if (player->move_delay_value_next != -1)
13169 player->move_delay_value = player->move_delay_value_next;
13170 player->move_delay_value_next = -1;
13174 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
13176 player->step_counter++;
13178 PlayerVisit[jx][jy] = FrameCounter;
13180 player->is_moving = TRUE;
13183 // should better be called in MovePlayer(), but this breaks some tapes
13184 ScrollPlayer(player, SCROLL_INIT);
13190 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
13192 int jx = player->jx, jy = player->jy;
13193 int old_jx = jx, old_jy = jy;
13194 int moved = MP_NO_ACTION;
13196 if (!player->active)
13201 if (player->MovPos == 0)
13203 player->is_moving = FALSE;
13204 player->is_digging = FALSE;
13205 player->is_collecting = FALSE;
13206 player->is_snapping = FALSE;
13207 player->is_pushing = FALSE;
13213 if (player->move_delay > 0)
13216 player->move_delay = -1; // set to "uninitialized" value
13218 // store if player is automatically moved to next field
13219 player->is_auto_moving = (player->programmed_action != MV_NONE);
13221 // remove the last programmed player action
13222 player->programmed_action = 0;
13224 if (player->MovPos)
13226 // should only happen if pre-1.2 tape recordings are played
13227 // this is only for backward compatibility
13229 int original_move_delay_value = player->move_delay_value;
13232 Debug("game:playing:MovePlayer",
13233 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13237 // scroll remaining steps with finest movement resolution
13238 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13240 while (player->MovPos)
13242 ScrollPlayer(player, SCROLL_GO_ON);
13243 ScrollScreen(NULL, SCROLL_GO_ON);
13245 AdvanceFrameAndPlayerCounters(player->index_nr);
13248 BackToFront_WithFrameDelay(0);
13251 player->move_delay_value = original_move_delay_value;
13254 player->is_active = FALSE;
13256 if (player->last_move_dir & MV_HORIZONTAL)
13258 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13259 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13263 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13264 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13267 if (!moved && !player->is_active)
13269 player->is_moving = FALSE;
13270 player->is_digging = FALSE;
13271 player->is_collecting = FALSE;
13272 player->is_snapping = FALSE;
13273 player->is_pushing = FALSE;
13279 if (moved & MP_MOVING && !ScreenMovPos &&
13280 (player->index_nr == game.centered_player_nr ||
13281 game.centered_player_nr == -1))
13283 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13285 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13287 // actual player has left the screen -- scroll in that direction
13288 if (jx != old_jx) // player has moved horizontally
13289 scroll_x += (jx - old_jx);
13290 else // player has moved vertically
13291 scroll_y += (jy - old_jy);
13295 int offset_raw = game.scroll_delay_value;
13297 if (jx != old_jx) // player has moved horizontally
13299 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13300 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13301 int new_scroll_x = jx - MIDPOSX + offset_x;
13303 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13304 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13305 scroll_x = new_scroll_x;
13307 // don't scroll over playfield boundaries
13308 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13310 // don't scroll more than one field at a time
13311 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13313 // don't scroll against the player's moving direction
13314 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13315 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13316 scroll_x = old_scroll_x;
13318 else // player has moved vertically
13320 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13321 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13322 int new_scroll_y = jy - MIDPOSY + offset_y;
13324 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13325 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13326 scroll_y = new_scroll_y;
13328 // don't scroll over playfield boundaries
13329 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13331 // don't scroll more than one field at a time
13332 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13334 // don't scroll against the player's moving direction
13335 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13336 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13337 scroll_y = old_scroll_y;
13341 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13343 if (!network.enabled && game.centered_player_nr == -1 &&
13344 !AllPlayersInVisibleScreen())
13346 scroll_x = old_scroll_x;
13347 scroll_y = old_scroll_y;
13351 ScrollScreen(player, SCROLL_INIT);
13352 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13357 player->StepFrame = 0;
13359 if (moved & MP_MOVING)
13361 if (old_jx != jx && old_jy == jy)
13362 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13363 else if (old_jx == jx && old_jy != jy)
13364 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13366 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13368 player->last_move_dir = player->MovDir;
13369 player->is_moving = TRUE;
13370 player->is_snapping = FALSE;
13371 player->is_switching = FALSE;
13372 player->is_dropping = FALSE;
13373 player->is_dropping_pressed = FALSE;
13374 player->drop_pressed_delay = 0;
13377 // should better be called here than above, but this breaks some tapes
13378 ScrollPlayer(player, SCROLL_INIT);
13383 CheckGravityMovementWhenNotMoving(player);
13385 player->is_moving = FALSE;
13387 /* at this point, the player is allowed to move, but cannot move right now
13388 (e.g. because of something blocking the way) -- ensure that the player
13389 is also allowed to move in the next frame (in old versions before 3.1.1,
13390 the player was forced to wait again for eight frames before next try) */
13392 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13393 player->move_delay = 0; // allow direct movement in the next frame
13396 if (player->move_delay == -1) // not yet initialized by DigField()
13397 player->move_delay = player->move_delay_value;
13399 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13401 TestIfPlayerTouchesBadThing(jx, jy);
13402 TestIfPlayerTouchesCustomElement(jx, jy);
13405 if (!player->active)
13406 RemovePlayer(player);
13411 void ScrollPlayer(struct PlayerInfo *player, int mode)
13413 int jx = player->jx, jy = player->jy;
13414 int last_jx = player->last_jx, last_jy = player->last_jy;
13415 int move_stepsize = TILEX / player->move_delay_value;
13417 if (!player->active)
13420 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13423 if (mode == SCROLL_INIT)
13425 player->actual_frame_counter.count = FrameCounter;
13426 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13428 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13429 Tile[last_jx][last_jy] == EL_EMPTY)
13431 int last_field_block_delay = 0; // start with no blocking at all
13432 int block_delay_adjustment = player->block_delay_adjustment;
13434 // if player blocks last field, add delay for exactly one move
13435 if (player->block_last_field)
13437 last_field_block_delay += player->move_delay_value;
13439 // when blocking enabled, prevent moving up despite gravity
13440 if (player->gravity && player->MovDir == MV_UP)
13441 block_delay_adjustment = -1;
13444 // add block delay adjustment (also possible when not blocking)
13445 last_field_block_delay += block_delay_adjustment;
13447 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13448 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13451 if (player->MovPos != 0) // player has not yet reached destination
13454 else if (!FrameReached(&player->actual_frame_counter))
13457 if (player->MovPos != 0)
13459 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13460 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13462 // before DrawPlayer() to draw correct player graphic for this case
13463 if (player->MovPos == 0)
13464 CheckGravityMovement(player);
13467 if (player->MovPos == 0) // player reached destination field
13469 if (player->move_delay_reset_counter > 0)
13471 player->move_delay_reset_counter--;
13473 if (player->move_delay_reset_counter == 0)
13475 // continue with normal speed after quickly moving through gate
13476 HALVE_PLAYER_SPEED(player);
13478 // be able to make the next move without delay
13479 player->move_delay = 0;
13483 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13484 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13485 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13486 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13487 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13488 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13489 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13490 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13492 ExitPlayer(player);
13494 if (game.players_still_needed == 0 &&
13495 (game.friends_still_needed == 0 ||
13496 IS_SP_ELEMENT(Tile[jx][jy])))
13500 player->last_jx = jx;
13501 player->last_jy = jy;
13503 // this breaks one level: "machine", level 000
13505 int move_direction = player->MovDir;
13506 int enter_side = MV_DIR_OPPOSITE(move_direction);
13507 int leave_side = move_direction;
13508 int old_jx = last_jx;
13509 int old_jy = last_jy;
13510 int old_element = Tile[old_jx][old_jy];
13511 int new_element = Tile[jx][jy];
13513 if (IS_CUSTOM_ELEMENT(old_element))
13514 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13516 player->index_bit, leave_side);
13518 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13519 CE_PLAYER_LEAVES_X,
13520 player->index_bit, leave_side);
13522 // needed because pushed element has not yet reached its destination,
13523 // so it would trigger a change event at its previous field location
13524 if (!player->is_pushing)
13526 if (IS_CUSTOM_ELEMENT(new_element))
13527 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13528 player->index_bit, enter_side);
13530 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13531 CE_PLAYER_ENTERS_X,
13532 player->index_bit, enter_side);
13535 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13536 CE_MOVE_OF_X, move_direction);
13539 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13541 TestIfPlayerTouchesBadThing(jx, jy);
13542 TestIfPlayerTouchesCustomElement(jx, jy);
13544 // needed because pushed element has not yet reached its destination,
13545 // so it would trigger a change event at its previous field location
13546 if (!player->is_pushing)
13547 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13549 if (level.finish_dig_collect &&
13550 (player->is_digging || player->is_collecting))
13552 int last_element = player->last_removed_element;
13553 int move_direction = player->MovDir;
13554 int enter_side = MV_DIR_OPPOSITE(move_direction);
13555 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13556 CE_PLAYER_COLLECTS_X);
13558 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13559 player->index_bit, enter_side);
13561 player->last_removed_element = EL_UNDEFINED;
13564 if (!player->active)
13565 RemovePlayer(player);
13568 if (level.use_step_counter)
13569 CheckLevelTime_StepCounter();
13571 if (tape.single_step && tape.recording && !tape.pausing &&
13572 !player->programmed_action)
13573 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13575 if (!player->programmed_action)
13576 CheckSaveEngineSnapshot(player);
13580 void ScrollScreen(struct PlayerInfo *player, int mode)
13582 static DelayCounter screen_frame_counter = { 0 };
13584 if (mode == SCROLL_INIT)
13586 // set scrolling step size according to actual player's moving speed
13587 ScrollStepSize = TILEX / player->move_delay_value;
13589 screen_frame_counter.count = FrameCounter;
13590 screen_frame_counter.value = 1;
13592 ScreenMovDir = player->MovDir;
13593 ScreenMovPos = player->MovPos;
13594 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13597 else if (!FrameReached(&screen_frame_counter))
13602 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13603 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13604 redraw_mask |= REDRAW_FIELD;
13607 ScreenMovDir = MV_NONE;
13610 void CheckNextToConditions(int x, int y)
13612 int element = Tile[x][y];
13614 if (IS_PLAYER(x, y))
13615 TestIfPlayerNextToCustomElement(x, y);
13617 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13618 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13619 TestIfElementNextToCustomElement(x, y);
13622 void TestIfPlayerNextToCustomElement(int x, int y)
13624 struct XY *xy = xy_topdown;
13625 static int trigger_sides[4][2] =
13627 // center side border side
13628 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13629 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13630 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13631 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13635 if (!IS_PLAYER(x, y))
13638 struct PlayerInfo *player = PLAYERINFO(x, y);
13640 if (player->is_moving)
13643 for (i = 0; i < NUM_DIRECTIONS; i++)
13645 int xx = x + xy[i].x;
13646 int yy = y + xy[i].y;
13647 int border_side = trigger_sides[i][1];
13648 int border_element;
13650 if (!IN_LEV_FIELD(xx, yy))
13653 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13654 continue; // center and border element not connected
13656 border_element = Tile[xx][yy];
13658 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13659 player->index_bit, border_side);
13660 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13661 CE_PLAYER_NEXT_TO_X,
13662 player->index_bit, border_side);
13664 /* use player element that is initially defined in the level playfield,
13665 not the player element that corresponds to the runtime player number
13666 (example: a level that contains EL_PLAYER_3 as the only player would
13667 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13669 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13670 CE_NEXT_TO_X, border_side);
13674 void TestIfPlayerTouchesCustomElement(int x, int y)
13676 struct XY *xy = xy_topdown;
13677 static int trigger_sides[4][2] =
13679 // center side border side
13680 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13681 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13682 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13683 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13685 static int touch_dir[4] =
13687 MV_LEFT | MV_RIGHT,
13692 int center_element = Tile[x][y]; // should always be non-moving!
13695 for (i = 0; i < NUM_DIRECTIONS; i++)
13697 int xx = x + xy[i].x;
13698 int yy = y + xy[i].y;
13699 int center_side = trigger_sides[i][0];
13700 int border_side = trigger_sides[i][1];
13701 int border_element;
13703 if (!IN_LEV_FIELD(xx, yy))
13706 if (IS_PLAYER(x, y)) // player found at center element
13708 struct PlayerInfo *player = PLAYERINFO(x, y);
13710 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13711 border_element = Tile[xx][yy]; // may be moving!
13712 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13713 border_element = Tile[xx][yy];
13714 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13715 border_element = MovingOrBlocked2Element(xx, yy);
13717 continue; // center and border element do not touch
13719 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13720 player->index_bit, border_side);
13721 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13722 CE_PLAYER_TOUCHES_X,
13723 player->index_bit, border_side);
13726 /* use player element that is initially defined in the level playfield,
13727 not the player element that corresponds to the runtime player number
13728 (example: a level that contains EL_PLAYER_3 as the only player would
13729 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13730 int player_element = PLAYERINFO(x, y)->initial_element;
13732 // as element "X" is the player here, check opposite (center) side
13733 CheckElementChangeBySide(xx, yy, border_element, player_element,
13734 CE_TOUCHING_X, center_side);
13737 else if (IS_PLAYER(xx, yy)) // player found at border element
13739 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13741 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13743 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13744 continue; // center and border element do not touch
13747 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13748 player->index_bit, center_side);
13749 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13750 CE_PLAYER_TOUCHES_X,
13751 player->index_bit, center_side);
13754 /* use player element that is initially defined in the level playfield,
13755 not the player element that corresponds to the runtime player number
13756 (example: a level that contains EL_PLAYER_3 as the only player would
13757 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13758 int player_element = PLAYERINFO(xx, yy)->initial_element;
13760 // as element "X" is the player here, check opposite (border) side
13761 CheckElementChangeBySide(x, y, center_element, player_element,
13762 CE_TOUCHING_X, border_side);
13770 void TestIfElementNextToCustomElement(int x, int y)
13772 struct XY *xy = xy_topdown;
13773 static int trigger_sides[4][2] =
13775 // center side border side
13776 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13777 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13778 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13779 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13781 int center_element = Tile[x][y]; // should always be non-moving!
13784 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13787 for (i = 0; i < NUM_DIRECTIONS; i++)
13789 int xx = x + xy[i].x;
13790 int yy = y + xy[i].y;
13791 int border_side = trigger_sides[i][1];
13792 int border_element;
13794 if (!IN_LEV_FIELD(xx, yy))
13797 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13798 continue; // center and border element not connected
13800 border_element = Tile[xx][yy];
13802 // check for change of center element (but change it only once)
13803 if (CheckElementChangeBySide(x, y, center_element, border_element,
13804 CE_NEXT_TO_X, border_side))
13809 void TestIfElementTouchesCustomElement(int x, int y)
13811 struct XY *xy = xy_topdown;
13812 static int trigger_sides[4][2] =
13814 // center side border side
13815 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13816 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13817 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13818 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13820 static int touch_dir[4] =
13822 MV_LEFT | MV_RIGHT,
13827 boolean change_center_element = FALSE;
13828 int center_element = Tile[x][y]; // should always be non-moving!
13829 int border_element_old[NUM_DIRECTIONS];
13832 for (i = 0; i < NUM_DIRECTIONS; i++)
13834 int xx = x + xy[i].x;
13835 int yy = y + xy[i].y;
13836 int border_element;
13838 border_element_old[i] = -1;
13840 if (!IN_LEV_FIELD(xx, yy))
13843 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13844 border_element = Tile[xx][yy]; // may be moving!
13845 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13846 border_element = Tile[xx][yy];
13847 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13848 border_element = MovingOrBlocked2Element(xx, yy);
13850 continue; // center and border element do not touch
13852 border_element_old[i] = border_element;
13855 for (i = 0; i < NUM_DIRECTIONS; i++)
13857 int xx = x + xy[i].x;
13858 int yy = y + xy[i].y;
13859 int center_side = trigger_sides[i][0];
13860 int border_element = border_element_old[i];
13862 if (border_element == -1)
13865 // check for change of border element
13866 CheckElementChangeBySide(xx, yy, border_element, center_element,
13867 CE_TOUCHING_X, center_side);
13869 // (center element cannot be player, so we don't have to check this here)
13872 for (i = 0; i < NUM_DIRECTIONS; i++)
13874 int xx = x + xy[i].x;
13875 int yy = y + xy[i].y;
13876 int border_side = trigger_sides[i][1];
13877 int border_element = border_element_old[i];
13879 if (border_element == -1)
13882 // check for change of center element (but change it only once)
13883 if (!change_center_element)
13884 change_center_element =
13885 CheckElementChangeBySide(x, y, center_element, border_element,
13886 CE_TOUCHING_X, border_side);
13888 if (IS_PLAYER(xx, yy))
13890 /* use player element that is initially defined in the level playfield,
13891 not the player element that corresponds to the runtime player number
13892 (example: a level that contains EL_PLAYER_3 as the only player would
13893 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13894 int player_element = PLAYERINFO(xx, yy)->initial_element;
13896 // as element "X" is the player here, check opposite (border) side
13897 CheckElementChangeBySide(x, y, center_element, player_element,
13898 CE_TOUCHING_X, border_side);
13903 void TestIfElementHitsCustomElement(int x, int y, int direction)
13905 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13906 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13907 int hitx = x + dx, hity = y + dy;
13908 int hitting_element = Tile[x][y];
13909 int touched_element;
13911 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13914 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13915 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13917 if (IN_LEV_FIELD(hitx, hity))
13919 int opposite_direction = MV_DIR_OPPOSITE(direction);
13920 int hitting_side = direction;
13921 int touched_side = opposite_direction;
13922 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13923 MovDir[hitx][hity] != direction ||
13924 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13930 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13931 CE_HITTING_X, touched_side);
13933 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13934 CE_HIT_BY_X, hitting_side);
13936 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13937 CE_HIT_BY_SOMETHING, opposite_direction);
13939 if (IS_PLAYER(hitx, hity))
13941 /* use player element that is initially defined in the level playfield,
13942 not the player element that corresponds to the runtime player number
13943 (example: a level that contains EL_PLAYER_3 as the only player would
13944 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13945 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13947 CheckElementChangeBySide(x, y, hitting_element, player_element,
13948 CE_HITTING_X, touched_side);
13953 // "hitting something" is also true when hitting the playfield border
13954 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13955 CE_HITTING_SOMETHING, direction);
13958 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13960 int i, kill_x = -1, kill_y = -1;
13962 int bad_element = -1;
13963 struct XY *test_xy = xy_topdown;
13964 static int test_dir[4] =
13972 for (i = 0; i < NUM_DIRECTIONS; i++)
13974 int test_x, test_y, test_move_dir, test_element;
13976 test_x = good_x + test_xy[i].x;
13977 test_y = good_y + test_xy[i].y;
13979 if (!IN_LEV_FIELD(test_x, test_y))
13983 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13985 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13987 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13988 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13990 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13991 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13995 bad_element = test_element;
14001 if (kill_x != -1 || kill_y != -1)
14003 if (IS_PLAYER(good_x, good_y))
14005 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
14007 if (player->shield_deadly_time_left > 0 &&
14008 !IS_INDESTRUCTIBLE(bad_element))
14009 Bang(kill_x, kill_y);
14010 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
14011 KillPlayer(player);
14014 Bang(good_x, good_y);
14018 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
14020 int i, kill_x = -1, kill_y = -1;
14021 int bad_element = Tile[bad_x][bad_y];
14022 struct XY *test_xy = xy_topdown;
14023 static int touch_dir[4] =
14025 MV_LEFT | MV_RIGHT,
14030 static int test_dir[4] =
14038 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
14041 for (i = 0; i < NUM_DIRECTIONS; i++)
14043 int test_x, test_y, test_move_dir, test_element;
14045 test_x = bad_x + test_xy[i].x;
14046 test_y = bad_y + test_xy[i].y;
14048 if (!IN_LEV_FIELD(test_x, test_y))
14052 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14054 test_element = Tile[test_x][test_y];
14056 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
14057 2nd case: DONT_TOUCH style bad thing does not move away from good thing
14059 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
14060 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
14062 // good thing is player or penguin that does not move away
14063 if (IS_PLAYER(test_x, test_y))
14065 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14067 if (bad_element == EL_ROBOT && player->is_moving)
14068 continue; // robot does not kill player if he is moving
14070 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
14072 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
14073 continue; // center and border element do not touch
14081 else if (test_element == EL_PENGUIN)
14091 if (kill_x != -1 || kill_y != -1)
14093 if (IS_PLAYER(kill_x, kill_y))
14095 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14097 if (player->shield_deadly_time_left > 0 &&
14098 !IS_INDESTRUCTIBLE(bad_element))
14099 Bang(bad_x, bad_y);
14100 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14101 KillPlayer(player);
14104 Bang(kill_x, kill_y);
14108 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
14110 int bad_element = Tile[bad_x][bad_y];
14111 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
14112 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
14113 int test_x = bad_x + dx, test_y = bad_y + dy;
14114 int test_move_dir, test_element;
14115 int kill_x = -1, kill_y = -1;
14117 if (!IN_LEV_FIELD(test_x, test_y))
14121 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14123 test_element = Tile[test_x][test_y];
14125 if (test_move_dir != bad_move_dir)
14127 // good thing can be player or penguin that does not move away
14128 if (IS_PLAYER(test_x, test_y))
14130 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14132 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
14133 player as being hit when he is moving towards the bad thing, because
14134 the "get hit by" condition would be lost after the player stops) */
14135 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
14136 return; // player moves away from bad thing
14141 else if (test_element == EL_PENGUIN)
14148 if (kill_x != -1 || kill_y != -1)
14150 if (IS_PLAYER(kill_x, kill_y))
14152 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14154 if (player->shield_deadly_time_left > 0 &&
14155 !IS_INDESTRUCTIBLE(bad_element))
14156 Bang(bad_x, bad_y);
14157 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14158 KillPlayer(player);
14161 Bang(kill_x, kill_y);
14165 void TestIfPlayerTouchesBadThing(int x, int y)
14167 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14170 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
14172 TestIfGoodThingHitsBadThing(x, y, move_dir);
14175 void TestIfBadThingTouchesPlayer(int x, int y)
14177 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14180 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
14182 TestIfBadThingHitsGoodThing(x, y, move_dir);
14185 void TestIfFriendTouchesBadThing(int x, int y)
14187 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14190 void TestIfBadThingTouchesFriend(int x, int y)
14192 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14195 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
14197 int i, kill_x = bad_x, kill_y = bad_y;
14198 struct XY *xy = xy_topdown;
14200 for (i = 0; i < NUM_DIRECTIONS; i++)
14204 x = bad_x + xy[i].x;
14205 y = bad_y + xy[i].y;
14206 if (!IN_LEV_FIELD(x, y))
14209 element = Tile[x][y];
14210 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14211 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14219 if (kill_x != bad_x || kill_y != bad_y)
14220 Bang(bad_x, bad_y);
14223 void KillPlayer(struct PlayerInfo *player)
14225 int jx = player->jx, jy = player->jy;
14227 if (!player->active)
14231 Debug("game:playing:KillPlayer",
14232 "0: killed == %d, active == %d, reanimated == %d",
14233 player->killed, player->active, player->reanimated);
14236 /* the following code was introduced to prevent an infinite loop when calling
14238 -> CheckTriggeredElementChangeExt()
14239 -> ExecuteCustomElementAction()
14241 -> (infinitely repeating the above sequence of function calls)
14242 which occurs when killing the player while having a CE with the setting
14243 "kill player X when explosion of <player X>"; the solution using a new
14244 field "player->killed" was chosen for backwards compatibility, although
14245 clever use of the fields "player->active" etc. would probably also work */
14247 if (player->killed)
14251 player->killed = TRUE;
14253 // remove accessible field at the player's position
14254 RemoveField(jx, jy);
14256 // deactivate shield (else Bang()/Explode() would not work right)
14257 player->shield_normal_time_left = 0;
14258 player->shield_deadly_time_left = 0;
14261 Debug("game:playing:KillPlayer",
14262 "1: killed == %d, active == %d, reanimated == %d",
14263 player->killed, player->active, player->reanimated);
14269 Debug("game:playing:KillPlayer",
14270 "2: killed == %d, active == %d, reanimated == %d",
14271 player->killed, player->active, player->reanimated);
14274 if (player->reanimated) // killed player may have been reanimated
14275 player->killed = player->reanimated = FALSE;
14277 BuryPlayer(player);
14280 static void KillPlayerUnlessEnemyProtected(int x, int y)
14282 if (!PLAYER_ENEMY_PROTECTED(x, y))
14283 KillPlayer(PLAYERINFO(x, y));
14286 static void KillPlayerUnlessExplosionProtected(int x, int y)
14288 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14289 KillPlayer(PLAYERINFO(x, y));
14292 void BuryPlayer(struct PlayerInfo *player)
14294 int jx = player->jx, jy = player->jy;
14296 if (!player->active)
14299 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14301 RemovePlayer(player);
14303 player->buried = TRUE;
14305 if (game.all_players_gone)
14306 game.GameOver = TRUE;
14309 void RemovePlayer(struct PlayerInfo *player)
14311 int jx = player->jx, jy = player->jy;
14312 int i, found = FALSE;
14314 player->present = FALSE;
14315 player->active = FALSE;
14317 // required for some CE actions (even if the player is not active anymore)
14318 player->MovPos = 0;
14320 if (!ExplodeField[jx][jy])
14321 StorePlayer[jx][jy] = 0;
14323 if (player->is_moving)
14324 TEST_DrawLevelField(player->last_jx, player->last_jy);
14326 for (i = 0; i < MAX_PLAYERS; i++)
14327 if (stored_player[i].active)
14332 game.all_players_gone = TRUE;
14333 game.GameOver = TRUE;
14336 game.exit_x = game.robot_wheel_x = jx;
14337 game.exit_y = game.robot_wheel_y = jy;
14340 void ExitPlayer(struct PlayerInfo *player)
14342 DrawPlayer(player); // needed here only to cleanup last field
14343 RemovePlayer(player);
14345 if (game.players_still_needed > 0)
14346 game.players_still_needed--;
14349 static void SetFieldForSnapping(int x, int y, int element, int direction,
14350 int player_index_bit)
14352 struct ElementInfo *ei = &element_info[element];
14353 int direction_bit = MV_DIR_TO_BIT(direction);
14354 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14355 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14356 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14358 Tile[x][y] = EL_ELEMENT_SNAPPING;
14359 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14360 MovDir[x][y] = direction;
14361 Store[x][y] = element;
14362 Store2[x][y] = player_index_bit;
14364 ResetGfxAnimation(x, y);
14366 GfxElement[x][y] = element;
14367 GfxAction[x][y] = action;
14368 GfxDir[x][y] = direction;
14369 GfxFrame[x][y] = -1;
14372 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14373 int player_index_bit)
14375 TestIfElementTouchesCustomElement(x, y); // for empty space
14377 if (level.finish_dig_collect)
14379 int dig_side = MV_DIR_OPPOSITE(direction);
14380 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14381 CE_PLAYER_COLLECTS_X);
14383 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14384 player_index_bit, dig_side);
14385 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14386 player_index_bit, dig_side);
14391 =============================================================================
14392 checkDiagonalPushing()
14393 -----------------------------------------------------------------------------
14394 check if diagonal input device direction results in pushing of object
14395 (by checking if the alternative direction is walkable, diggable, ...)
14396 =============================================================================
14399 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14400 int x, int y, int real_dx, int real_dy)
14402 int jx, jy, dx, dy, xx, yy;
14404 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14407 // diagonal direction: check alternative direction
14412 xx = jx + (dx == 0 ? real_dx : 0);
14413 yy = jy + (dy == 0 ? real_dy : 0);
14415 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14419 =============================================================================
14421 -----------------------------------------------------------------------------
14422 x, y: field next to player (non-diagonal) to try to dig to
14423 real_dx, real_dy: direction as read from input device (can be diagonal)
14424 =============================================================================
14427 static int DigField(struct PlayerInfo *player,
14428 int oldx, int oldy, int x, int y,
14429 int real_dx, int real_dy, int mode)
14431 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14432 boolean player_was_pushing = player->is_pushing;
14433 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14434 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14435 int jx = oldx, jy = oldy;
14436 int dx = x - jx, dy = y - jy;
14437 int nextx = x + dx, nexty = y + dy;
14438 int move_direction = (dx == -1 ? MV_LEFT :
14439 dx == +1 ? MV_RIGHT :
14441 dy == +1 ? MV_DOWN : MV_NONE);
14442 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14443 int dig_side = MV_DIR_OPPOSITE(move_direction);
14444 int old_element = Tile[jx][jy];
14445 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14448 if (is_player) // function can also be called by EL_PENGUIN
14450 if (player->MovPos == 0)
14452 player->is_digging = FALSE;
14453 player->is_collecting = FALSE;
14456 if (player->MovPos == 0) // last pushing move finished
14457 player->is_pushing = FALSE;
14459 if (mode == DF_NO_PUSH) // player just stopped pushing
14461 player->is_switching = FALSE;
14462 player->push_delay = -1;
14464 return MP_NO_ACTION;
14467 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14468 old_element = Back[jx][jy];
14470 // in case of element dropped at player position, check background
14471 else if (Back[jx][jy] != EL_EMPTY &&
14472 game.engine_version >= VERSION_IDENT(2,2,0,0))
14473 old_element = Back[jx][jy];
14475 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14476 return MP_NO_ACTION; // field has no opening in this direction
14478 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction))
14479 return MP_NO_ACTION; // field has no opening in this direction
14481 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14485 Tile[jx][jy] = player->artwork_element;
14486 InitMovingField(jx, jy, MV_DOWN);
14487 Store[jx][jy] = EL_ACID;
14488 ContinueMoving(jx, jy);
14489 BuryPlayer(player);
14491 return MP_DONT_RUN_INTO;
14494 if (player_can_move && DONT_RUN_INTO(element))
14496 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14498 return MP_DONT_RUN_INTO;
14501 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14502 return MP_NO_ACTION;
14504 collect_count = element_info[element].collect_count_initial;
14506 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14507 return MP_NO_ACTION;
14509 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14510 player_can_move = player_can_move_or_snap;
14512 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14513 game.engine_version >= VERSION_IDENT(2,2,0,0))
14515 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14516 player->index_bit, dig_side);
14517 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14518 player->index_bit, dig_side);
14520 if (element == EL_DC_LANDMINE)
14523 if (Tile[x][y] != element) // field changed by snapping
14526 return MP_NO_ACTION;
14529 if (player->gravity && is_player && !player->is_auto_moving &&
14530 canFallDown(player) && move_direction != MV_DOWN &&
14531 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14532 return MP_NO_ACTION; // player cannot walk here due to gravity
14534 if (player_can_move &&
14535 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14537 int sound_element = SND_ELEMENT(element);
14538 int sound_action = ACTION_WALKING;
14540 if (IS_RND_GATE(element))
14542 if (!player->key[RND_GATE_NR(element)])
14543 return MP_NO_ACTION;
14545 else if (IS_RND_GATE_GRAY(element))
14547 if (!player->key[RND_GATE_GRAY_NR(element)])
14548 return MP_NO_ACTION;
14550 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14552 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14553 return MP_NO_ACTION;
14555 else if (element == EL_EXIT_OPEN ||
14556 element == EL_EM_EXIT_OPEN ||
14557 element == EL_EM_EXIT_OPENING ||
14558 element == EL_STEEL_EXIT_OPEN ||
14559 element == EL_EM_STEEL_EXIT_OPEN ||
14560 element == EL_EM_STEEL_EXIT_OPENING ||
14561 element == EL_SP_EXIT_OPEN ||
14562 element == EL_SP_EXIT_OPENING)
14564 sound_action = ACTION_PASSING; // player is passing exit
14566 else if (element == EL_EMPTY)
14568 sound_action = ACTION_MOVING; // nothing to walk on
14571 // play sound from background or player, whatever is available
14572 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14573 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14575 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14577 else if (player_can_move &&
14578 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14580 if (!ACCESS_FROM(element, opposite_direction))
14581 return MP_NO_ACTION; // field not accessible from this direction
14583 if (CAN_MOVE(element)) // only fixed elements can be passed!
14584 return MP_NO_ACTION;
14586 if (IS_EM_GATE(element))
14588 if (!player->key[EM_GATE_NR(element)])
14589 return MP_NO_ACTION;
14591 else if (IS_EM_GATE_GRAY(element))
14593 if (!player->key[EM_GATE_GRAY_NR(element)])
14594 return MP_NO_ACTION;
14596 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14598 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14599 return MP_NO_ACTION;
14601 else if (IS_EMC_GATE(element))
14603 if (!player->key[EMC_GATE_NR(element)])
14604 return MP_NO_ACTION;
14606 else if (IS_EMC_GATE_GRAY(element))
14608 if (!player->key[EMC_GATE_GRAY_NR(element)])
14609 return MP_NO_ACTION;
14611 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14613 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14614 return MP_NO_ACTION;
14616 else if (element == EL_DC_GATE_WHITE ||
14617 element == EL_DC_GATE_WHITE_GRAY ||
14618 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14620 if (player->num_white_keys == 0)
14621 return MP_NO_ACTION;
14623 player->num_white_keys--;
14625 else if (IS_SP_PORT(element))
14627 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14628 element == EL_SP_GRAVITY_PORT_RIGHT ||
14629 element == EL_SP_GRAVITY_PORT_UP ||
14630 element == EL_SP_GRAVITY_PORT_DOWN)
14631 player->gravity = !player->gravity;
14632 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14633 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14634 element == EL_SP_GRAVITY_ON_PORT_UP ||
14635 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14636 player->gravity = TRUE;
14637 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14638 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14639 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14640 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14641 player->gravity = FALSE;
14644 // automatically move to the next field with double speed
14645 player->programmed_action = move_direction;
14647 if (player->move_delay_reset_counter == 0)
14649 player->move_delay_reset_counter = 2; // two double speed steps
14651 DOUBLE_PLAYER_SPEED(player);
14654 PlayLevelSoundAction(x, y, ACTION_PASSING);
14656 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14660 if (mode != DF_SNAP)
14662 GfxElement[x][y] = GFX_ELEMENT(element);
14663 player->is_digging = TRUE;
14666 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14668 // use old behaviour for old levels (digging)
14669 if (!level.finish_dig_collect)
14671 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14672 player->index_bit, dig_side);
14674 // if digging triggered player relocation, finish digging tile
14675 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14676 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14679 if (mode == DF_SNAP)
14681 if (level.block_snap_field)
14682 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14684 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14686 // use old behaviour for old levels (snapping)
14687 if (!level.finish_dig_collect)
14688 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14689 player->index_bit, dig_side);
14692 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14696 if (is_player && mode != DF_SNAP)
14698 GfxElement[x][y] = element;
14699 player->is_collecting = TRUE;
14702 if (element == EL_SPEED_PILL)
14704 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14706 else if (element == EL_EXTRA_TIME && level.time > 0)
14708 TimeLeft += level.extra_time;
14710 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14712 DisplayGameControlValues();
14714 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14716 int shield_time = (element == EL_SHIELD_DEADLY ?
14717 level.shield_deadly_time :
14718 level.shield_normal_time);
14720 player->shield_normal_time_left += shield_time;
14721 if (element == EL_SHIELD_DEADLY)
14722 player->shield_deadly_time_left += shield_time;
14724 else if (element == EL_DYNAMITE ||
14725 element == EL_EM_DYNAMITE ||
14726 element == EL_SP_DISK_RED)
14728 if (player->inventory_size < MAX_INVENTORY_SIZE)
14729 player->inventory_element[player->inventory_size++] = element;
14731 DrawGameDoorValues();
14733 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14735 player->dynabomb_count++;
14736 player->dynabombs_left++;
14738 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14740 player->dynabomb_size++;
14742 else if (element == EL_DYNABOMB_INCREASE_POWER)
14744 player->dynabomb_xl = TRUE;
14746 else if (IS_KEY(element))
14748 player->key[KEY_NR(element)] = TRUE;
14750 DrawGameDoorValues();
14752 else if (element == EL_DC_KEY_WHITE)
14754 player->num_white_keys++;
14756 // display white keys?
14757 // DrawGameDoorValues();
14759 else if (IS_ENVELOPE(element))
14761 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14763 if (!wait_for_snapping)
14764 player->show_envelope = element;
14766 else if (element == EL_EMC_LENSES)
14768 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14770 RedrawAllInvisibleElementsForLenses();
14772 else if (element == EL_EMC_MAGNIFIER)
14774 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14776 RedrawAllInvisibleElementsForMagnifier();
14778 else if (IS_DROPPABLE(element) ||
14779 IS_THROWABLE(element)) // can be collected and dropped
14783 if (collect_count == 0)
14784 player->inventory_infinite_element = element;
14786 for (i = 0; i < collect_count; i++)
14787 if (player->inventory_size < MAX_INVENTORY_SIZE)
14788 player->inventory_element[player->inventory_size++] = element;
14790 DrawGameDoorValues();
14792 else if (collect_count > 0)
14794 game.gems_still_needed -= collect_count;
14795 if (game.gems_still_needed < 0)
14796 game.gems_still_needed = 0;
14798 game.snapshot.collected_item = TRUE;
14800 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14802 DisplayGameControlValues();
14805 RaiseScoreElement(element);
14806 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14808 // use old behaviour for old levels (collecting)
14809 if (!level.finish_dig_collect && is_player)
14811 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14812 player->index_bit, dig_side);
14814 // if collecting triggered player relocation, finish collecting tile
14815 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14816 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14819 if (mode == DF_SNAP)
14821 if (level.block_snap_field)
14822 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14824 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14826 // use old behaviour for old levels (snapping)
14827 if (!level.finish_dig_collect)
14828 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14829 player->index_bit, dig_side);
14832 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14834 if (mode == DF_SNAP && element != EL_BD_ROCK)
14835 return MP_NO_ACTION;
14837 if (CAN_FALL(element) && dy)
14838 return MP_NO_ACTION;
14840 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14841 !(element == EL_SPRING && level.use_spring_bug))
14842 return MP_NO_ACTION;
14844 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14845 ((move_direction & MV_VERTICAL &&
14846 ((element_info[element].move_pattern & MV_LEFT &&
14847 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14848 (element_info[element].move_pattern & MV_RIGHT &&
14849 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14850 (move_direction & MV_HORIZONTAL &&
14851 ((element_info[element].move_pattern & MV_UP &&
14852 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14853 (element_info[element].move_pattern & MV_DOWN &&
14854 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14855 return MP_NO_ACTION;
14857 // do not push elements already moving away faster than player
14858 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14859 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14860 return MP_NO_ACTION;
14862 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14864 if (player->push_delay_value == -1 || !player_was_pushing)
14865 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14867 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14869 if (player->push_delay_value == -1)
14870 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14872 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14874 if (!player->is_pushing)
14875 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14878 player->is_pushing = TRUE;
14879 player->is_active = TRUE;
14881 if (!(IN_LEV_FIELD(nextx, nexty) &&
14882 (IS_FREE(nextx, nexty) ||
14883 (IS_SB_ELEMENT(element) &&
14884 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14885 (IS_CUSTOM_ELEMENT(element) &&
14886 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14887 return MP_NO_ACTION;
14889 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14890 return MP_NO_ACTION;
14892 if (player->push_delay == -1) // new pushing; restart delay
14893 player->push_delay = 0;
14895 if (player->push_delay < player->push_delay_value &&
14896 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14897 element != EL_SPRING && element != EL_BALLOON)
14899 // make sure that there is no move delay before next try to push
14900 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14901 player->move_delay = 0;
14903 return MP_NO_ACTION;
14906 if (IS_CUSTOM_ELEMENT(element) &&
14907 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14909 if (!DigFieldByCE(nextx, nexty, element))
14910 return MP_NO_ACTION;
14913 if (IS_SB_ELEMENT(element))
14915 boolean sokoban_task_solved = FALSE;
14917 if (element == EL_SOKOBAN_FIELD_FULL)
14919 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14921 IncrementSokobanFieldsNeeded();
14922 IncrementSokobanObjectsNeeded();
14925 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14927 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14929 DecrementSokobanFieldsNeeded();
14930 DecrementSokobanObjectsNeeded();
14932 // sokoban object was pushed from empty field to sokoban field
14933 if (Back[x][y] == EL_EMPTY)
14934 sokoban_task_solved = TRUE;
14937 Tile[x][y] = EL_SOKOBAN_OBJECT;
14939 if (Back[x][y] == Back[nextx][nexty])
14940 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14941 else if (Back[x][y] != 0)
14942 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14945 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14948 if (sokoban_task_solved &&
14949 game.sokoban_fields_still_needed == 0 &&
14950 game.sokoban_objects_still_needed == 0 &&
14951 level.auto_exit_sokoban)
14953 game.players_still_needed = 0;
14957 PlaySound(SND_GAME_SOKOBAN_SOLVING);
14961 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14963 InitMovingField(x, y, move_direction);
14964 GfxAction[x][y] = ACTION_PUSHING;
14966 if (mode == DF_SNAP)
14967 ContinueMoving(x, y);
14969 MovPos[x][y] = (dx != 0 ? dx : dy);
14971 Pushed[x][y] = TRUE;
14972 Pushed[nextx][nexty] = TRUE;
14974 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14975 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14977 player->push_delay_value = -1; // get new value later
14979 // check for element change _after_ element has been pushed
14980 if (game.use_change_when_pushing_bug)
14982 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14983 player->index_bit, dig_side);
14984 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14985 player->index_bit, dig_side);
14988 else if (IS_SWITCHABLE(element))
14990 if (PLAYER_SWITCHING(player, x, y))
14992 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14993 player->index_bit, dig_side);
14998 player->is_switching = TRUE;
14999 player->switch_x = x;
15000 player->switch_y = y;
15002 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15004 if (element == EL_ROBOT_WHEEL)
15006 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
15008 game.robot_wheel_x = x;
15009 game.robot_wheel_y = y;
15010 game.robot_wheel_active = TRUE;
15012 TEST_DrawLevelField(x, y);
15014 else if (element == EL_SP_TERMINAL)
15018 SCAN_PLAYFIELD(xx, yy)
15020 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
15024 else if (Tile[xx][yy] == EL_SP_TERMINAL)
15026 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
15028 ResetGfxAnimation(xx, yy);
15029 TEST_DrawLevelField(xx, yy);
15033 else if (IS_BELT_SWITCH(element))
15035 ToggleBeltSwitch(x, y);
15037 else if (element == EL_SWITCHGATE_SWITCH_UP ||
15038 element == EL_SWITCHGATE_SWITCH_DOWN ||
15039 element == EL_DC_SWITCHGATE_SWITCH_UP ||
15040 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
15042 ToggleSwitchgateSwitch();
15044 else if (element == EL_LIGHT_SWITCH ||
15045 element == EL_LIGHT_SWITCH_ACTIVE)
15047 ToggleLightSwitch(x, y);
15049 else if (element == EL_TIMEGATE_SWITCH ||
15050 element == EL_DC_TIMEGATE_SWITCH)
15052 ActivateTimegateSwitch(x, y);
15054 else if (element == EL_BALLOON_SWITCH_LEFT ||
15055 element == EL_BALLOON_SWITCH_RIGHT ||
15056 element == EL_BALLOON_SWITCH_UP ||
15057 element == EL_BALLOON_SWITCH_DOWN ||
15058 element == EL_BALLOON_SWITCH_NONE ||
15059 element == EL_BALLOON_SWITCH_ANY)
15061 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
15062 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
15063 element == EL_BALLOON_SWITCH_UP ? MV_UP :
15064 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
15065 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
15068 else if (element == EL_LAMP)
15070 Tile[x][y] = EL_LAMP_ACTIVE;
15071 game.lights_still_needed--;
15073 ResetGfxAnimation(x, y);
15074 TEST_DrawLevelField(x, y);
15076 else if (element == EL_TIME_ORB_FULL)
15078 Tile[x][y] = EL_TIME_ORB_EMPTY;
15080 if (level.time > 0 || level.use_time_orb_bug)
15082 TimeLeft += level.time_orb_time;
15083 game.no_level_time_limit = FALSE;
15085 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
15087 DisplayGameControlValues();
15090 ResetGfxAnimation(x, y);
15091 TEST_DrawLevelField(x, y);
15093 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
15094 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15098 game.ball_active = !game.ball_active;
15100 SCAN_PLAYFIELD(xx, yy)
15102 int e = Tile[xx][yy];
15104 if (game.ball_active)
15106 if (e == EL_EMC_MAGIC_BALL)
15107 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
15108 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
15109 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
15113 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
15114 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
15115 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15116 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
15121 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15122 player->index_bit, dig_side);
15124 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15125 player->index_bit, dig_side);
15127 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15128 player->index_bit, dig_side);
15134 if (!PLAYER_SWITCHING(player, x, y))
15136 player->is_switching = TRUE;
15137 player->switch_x = x;
15138 player->switch_y = y;
15140 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
15141 player->index_bit, dig_side);
15142 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15143 player->index_bit, dig_side);
15145 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
15146 player->index_bit, dig_side);
15147 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15148 player->index_bit, dig_side);
15151 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
15152 player->index_bit, dig_side);
15153 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15154 player->index_bit, dig_side);
15156 return MP_NO_ACTION;
15159 player->push_delay = -1;
15161 if (is_player) // function can also be called by EL_PENGUIN
15163 if (Tile[x][y] != element) // really digged/collected something
15165 player->is_collecting = !player->is_digging;
15166 player->is_active = TRUE;
15168 player->last_removed_element = element;
15175 static boolean DigFieldByCE(int x, int y, int digging_element)
15177 int element = Tile[x][y];
15179 if (!IS_FREE(x, y))
15181 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
15182 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
15185 // no element can dig solid indestructible elements
15186 if (IS_INDESTRUCTIBLE(element) &&
15187 !IS_DIGGABLE(element) &&
15188 !IS_COLLECTIBLE(element))
15191 if (AmoebaNr[x][y] &&
15192 (element == EL_AMOEBA_FULL ||
15193 element == EL_BD_AMOEBA ||
15194 element == EL_AMOEBA_GROWING))
15196 AmoebaCnt[AmoebaNr[x][y]]--;
15197 AmoebaCnt2[AmoebaNr[x][y]]--;
15200 if (IS_MOVING(x, y))
15201 RemoveMovingField(x, y);
15205 TEST_DrawLevelField(x, y);
15208 // if digged element was about to explode, prevent the explosion
15209 ExplodeField[x][y] = EX_TYPE_NONE;
15211 PlayLevelSoundAction(x, y, action);
15214 Store[x][y] = EL_EMPTY;
15216 // this makes it possible to leave the removed element again
15217 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15218 Store[x][y] = element;
15223 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15225 int jx = player->jx, jy = player->jy;
15226 int x = jx + dx, y = jy + dy;
15227 int snap_direction = (dx == -1 ? MV_LEFT :
15228 dx == +1 ? MV_RIGHT :
15230 dy == +1 ? MV_DOWN : MV_NONE);
15231 boolean can_continue_snapping = (level.continuous_snapping &&
15232 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15234 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15237 if (!player->active || !IN_LEV_FIELD(x, y))
15245 if (player->MovPos == 0)
15246 player->is_pushing = FALSE;
15248 player->is_snapping = FALSE;
15250 if (player->MovPos == 0)
15252 player->is_moving = FALSE;
15253 player->is_digging = FALSE;
15254 player->is_collecting = FALSE;
15260 // prevent snapping with already pressed snap key when not allowed
15261 if (player->is_snapping && !can_continue_snapping)
15264 player->MovDir = snap_direction;
15266 if (player->MovPos == 0)
15268 player->is_moving = FALSE;
15269 player->is_digging = FALSE;
15270 player->is_collecting = FALSE;
15273 player->is_dropping = FALSE;
15274 player->is_dropping_pressed = FALSE;
15275 player->drop_pressed_delay = 0;
15277 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15280 player->is_snapping = TRUE;
15281 player->is_active = TRUE;
15283 if (player->MovPos == 0)
15285 player->is_moving = FALSE;
15286 player->is_digging = FALSE;
15287 player->is_collecting = FALSE;
15290 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15291 TEST_DrawLevelField(player->last_jx, player->last_jy);
15293 TEST_DrawLevelField(x, y);
15298 static boolean DropElement(struct PlayerInfo *player)
15300 int old_element, new_element;
15301 int dropx = player->jx, dropy = player->jy;
15302 int drop_direction = player->MovDir;
15303 int drop_side = drop_direction;
15304 int drop_element = get_next_dropped_element(player);
15306 /* do not drop an element on top of another element; when holding drop key
15307 pressed without moving, dropped element must move away before the next
15308 element can be dropped (this is especially important if the next element
15309 is dynamite, which can be placed on background for historical reasons) */
15310 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15313 if (IS_THROWABLE(drop_element))
15315 dropx += GET_DX_FROM_DIR(drop_direction);
15316 dropy += GET_DY_FROM_DIR(drop_direction);
15318 if (!IN_LEV_FIELD(dropx, dropy))
15322 old_element = Tile[dropx][dropy]; // old element at dropping position
15323 new_element = drop_element; // default: no change when dropping
15325 // check if player is active, not moving and ready to drop
15326 if (!player->active || player->MovPos || player->drop_delay > 0)
15329 // check if player has anything that can be dropped
15330 if (new_element == EL_UNDEFINED)
15333 // only set if player has anything that can be dropped
15334 player->is_dropping_pressed = TRUE;
15336 // check if drop key was pressed long enough for EM style dynamite
15337 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15340 // check if anything can be dropped at the current position
15341 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15344 // collected custom elements can only be dropped on empty fields
15345 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15348 if (old_element != EL_EMPTY)
15349 Back[dropx][dropy] = old_element; // store old element on this field
15351 ResetGfxAnimation(dropx, dropy);
15352 ResetRandomAnimationValue(dropx, dropy);
15354 if (player->inventory_size > 0 ||
15355 player->inventory_infinite_element != EL_UNDEFINED)
15357 if (player->inventory_size > 0)
15359 player->inventory_size--;
15361 DrawGameDoorValues();
15363 if (new_element == EL_DYNAMITE)
15364 new_element = EL_DYNAMITE_ACTIVE;
15365 else if (new_element == EL_EM_DYNAMITE)
15366 new_element = EL_EM_DYNAMITE_ACTIVE;
15367 else if (new_element == EL_SP_DISK_RED)
15368 new_element = EL_SP_DISK_RED_ACTIVE;
15371 Tile[dropx][dropy] = new_element;
15373 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15374 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15375 el2img(Tile[dropx][dropy]), 0);
15377 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15379 // needed if previous element just changed to "empty" in the last frame
15380 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15382 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15383 player->index_bit, drop_side);
15384 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15386 player->index_bit, drop_side);
15388 TestIfElementTouchesCustomElement(dropx, dropy);
15390 else // player is dropping a dyna bomb
15392 player->dynabombs_left--;
15394 Tile[dropx][dropy] = new_element;
15396 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15397 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15398 el2img(Tile[dropx][dropy]), 0);
15400 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15403 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15404 InitField_WithBug1(dropx, dropy, FALSE);
15406 new_element = Tile[dropx][dropy]; // element might have changed
15408 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15409 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15411 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15412 MovDir[dropx][dropy] = drop_direction;
15414 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15416 // do not cause impact style collision by dropping elements that can fall
15417 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15420 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15421 player->is_dropping = TRUE;
15423 player->drop_pressed_delay = 0;
15424 player->is_dropping_pressed = FALSE;
15426 player->drop_x = dropx;
15427 player->drop_y = dropy;
15432 // ----------------------------------------------------------------------------
15433 // game sound playing functions
15434 // ----------------------------------------------------------------------------
15436 static int *loop_sound_frame = NULL;
15437 static int *loop_sound_volume = NULL;
15439 void InitPlayLevelSound(void)
15441 int num_sounds = getSoundListSize();
15443 checked_free(loop_sound_frame);
15444 checked_free(loop_sound_volume);
15446 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15447 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15450 static void PlayLevelSoundExt(int x, int y, int nr, boolean is_loop_sound)
15452 int sx = SCREENX(x), sy = SCREENY(y);
15453 int volume, stereo_position;
15454 int max_distance = 8;
15455 int type = (is_loop_sound ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15457 if ((!setup.sound_simple && !is_loop_sound) ||
15458 (!setup.sound_loops && is_loop_sound))
15461 if (!IN_LEV_FIELD(x, y) ||
15462 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15463 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15466 volume = SOUND_MAX_VOLUME;
15468 if (!IN_SCR_FIELD(sx, sy))
15470 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15471 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15473 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15476 stereo_position = (SOUND_MAX_LEFT +
15477 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15478 (SCR_FIELDX + 2 * max_distance));
15482 /* This assures that quieter loop sounds do not overwrite louder ones,
15483 while restarting sound volume comparison with each new game frame. */
15485 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15488 loop_sound_volume[nr] = volume;
15489 loop_sound_frame[nr] = FrameCounter;
15492 PlaySoundExt(nr, volume, stereo_position, type);
15495 static void PlayLevelSound(int x, int y, int nr)
15497 PlayLevelSoundExt(x, y, nr, IS_LOOP_SOUND(nr));
15500 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15502 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15503 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15504 y < LEVELY(BY1) ? LEVELY(BY1) :
15505 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15509 static void PlayLevelSoundAction(int x, int y, int action)
15511 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15514 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15516 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15518 if (sound_effect != SND_UNDEFINED)
15519 PlayLevelSound(x, y, sound_effect);
15522 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15525 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15527 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15528 PlayLevelSound(x, y, sound_effect);
15531 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15533 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15535 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15536 PlayLevelSound(x, y, sound_effect);
15539 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15541 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15543 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15544 StopSound(sound_effect);
15547 static int getLevelMusicNr(void)
15549 int level_pos = level_nr - leveldir_current->first_level;
15551 if (levelset.music[level_nr] != MUS_UNDEFINED)
15552 return levelset.music[level_nr]; // from config file
15554 return MAP_NOCONF_MUSIC(level_pos); // from music dir
15557 static void FadeLevelSounds(void)
15562 static void FadeLevelMusic(void)
15564 int music_nr = getLevelMusicNr();
15565 char *curr_music = getCurrentlyPlayingMusicFilename();
15566 char *next_music = getMusicInfoEntryFilename(music_nr);
15568 if (!strEqual(curr_music, next_music))
15572 void FadeLevelSoundsAndMusic(void)
15578 static void PlayLevelMusic(void)
15580 int music_nr = getLevelMusicNr();
15581 char *curr_music = getCurrentlyPlayingMusicFilename();
15582 char *next_music = getMusicInfoEntryFilename(music_nr);
15584 if (!strEqual(curr_music, next_music))
15585 PlayMusicLoop(music_nr);
15588 static int getSoundAction_BD(int sample)
15592 case GD_S_STONE_PUSHING:
15593 case GD_S_MEGA_STONE_PUSHING:
15594 case GD_S_FLYING_STONE_PUSHING:
15595 case GD_S_WAITING_STONE_PUSHING:
15596 case GD_S_CHASING_STONE_PUSHING:
15597 case GD_S_NUT_PUSHING:
15598 case GD_S_NITRO_PACK_PUSHING:
15599 case GD_S_BLADDER_PUSHING:
15600 case GD_S_BOX_PUSHING:
15601 return ACTION_PUSHING;
15603 case GD_S_STONE_FALLING:
15604 case GD_S_MEGA_STONE_FALLING:
15605 case GD_S_FLYING_STONE_FALLING:
15606 case GD_S_NUT_FALLING:
15607 case GD_S_DIRT_BALL_FALLING:
15608 case GD_S_DIRT_LOOSE_FALLING:
15609 case GD_S_NITRO_PACK_FALLING:
15610 case GD_S_FALLING_WALL_FALLING:
15611 return ACTION_FALLING;
15613 case GD_S_STONE_IMPACT:
15614 case GD_S_MEGA_STONE_IMPACT:
15615 case GD_S_FLYING_STONE_IMPACT:
15616 case GD_S_NUT_IMPACT:
15617 case GD_S_DIRT_BALL_IMPACT:
15618 case GD_S_DIRT_LOOSE_IMPACT:
15619 case GD_S_NITRO_PACK_IMPACT:
15620 case GD_S_FALLING_WALL_IMPACT:
15621 return ACTION_IMPACT;
15623 case GD_S_NUT_CRACKING:
15624 return ACTION_BREAKING;
15626 case GD_S_EXPANDING_WALL:
15627 case GD_S_WALL_REAPPEARING:
15630 case GD_S_ACID_SPREADING:
15631 return ACTION_GROWING;
15633 case GD_S_DIAMOND_COLLECTING:
15634 case GD_S_FLYING_DIAMOND_COLLECTING:
15635 case GD_S_SKELETON_COLLECTING:
15636 case GD_S_PNEUMATIC_COLLECTING:
15637 case GD_S_BOMB_COLLECTING:
15638 case GD_S_CLOCK_COLLECTING:
15639 case GD_S_SWEET_COLLECTING:
15640 case GD_S_KEY_COLLECTING:
15641 case GD_S_DIAMOND_KEY_COLLECTING:
15642 return ACTION_COLLECTING;
15644 case GD_S_BOMB_PLACING:
15645 case GD_S_REPLICATOR:
15646 return ACTION_DROPPING;
15648 case GD_S_BLADDER_MOVING:
15649 return ACTION_MOVING;
15651 case GD_S_BLADDER_SPENDER:
15652 case GD_S_BLADDER_CONVERTING:
15653 case GD_S_GRAVITY_CHANGING:
15654 return ACTION_CHANGING;
15656 case GD_S_BITER_EATING:
15657 return ACTION_EATING;
15659 case GD_S_DOOR_OPENING:
15660 case GD_S_CRACKING:
15661 return ACTION_OPENING;
15663 case GD_S_DIRT_WALKING:
15664 return ACTION_DIGGING;
15666 case GD_S_EMPTY_WALKING:
15667 return ACTION_WALKING;
15669 case GD_S_SWITCH_BITER:
15670 case GD_S_SWITCH_CREATURES:
15671 case GD_S_SWITCH_GRAVITY:
15672 case GD_S_SWITCH_EXPANDING:
15673 case GD_S_SWITCH_CONVEYOR:
15674 case GD_S_SWITCH_REPLICATOR:
15675 case GD_S_STIRRING:
15676 return ACTION_ACTIVATING;
15678 case GD_S_TELEPORTER:
15679 return ACTION_PASSING;
15681 case GD_S_EXPLODING:
15682 case GD_S_BOMB_EXPLODING:
15683 case GD_S_GHOST_EXPLODING:
15684 case GD_S_VOODOO_EXPLODING:
15685 case GD_S_NITRO_PACK_EXPLODING:
15686 return ACTION_EXPLODING;
15688 case GD_S_COVERING:
15690 case GD_S_MAGIC_WALL:
15691 case GD_S_PNEUMATIC_HAMMER:
15693 return ACTION_ACTIVE;
15695 case GD_S_DIAMOND_FALLING_RANDOM:
15696 case GD_S_DIAMOND_FALLING_1:
15697 case GD_S_DIAMOND_FALLING_2:
15698 case GD_S_DIAMOND_FALLING_3:
15699 case GD_S_DIAMOND_FALLING_4:
15700 case GD_S_DIAMOND_FALLING_5:
15701 case GD_S_DIAMOND_FALLING_6:
15702 case GD_S_DIAMOND_FALLING_7:
15703 case GD_S_DIAMOND_FALLING_8:
15704 case GD_S_DIAMOND_IMPACT_RANDOM:
15705 case GD_S_DIAMOND_IMPACT_1:
15706 case GD_S_DIAMOND_IMPACT_2:
15707 case GD_S_DIAMOND_IMPACT_3:
15708 case GD_S_DIAMOND_IMPACT_4:
15709 case GD_S_DIAMOND_IMPACT_5:
15710 case GD_S_DIAMOND_IMPACT_6:
15711 case GD_S_DIAMOND_IMPACT_7:
15712 case GD_S_DIAMOND_IMPACT_8:
15713 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15714 case GD_S_FLYING_DIAMOND_FALLING_1:
15715 case GD_S_FLYING_DIAMOND_FALLING_2:
15716 case GD_S_FLYING_DIAMOND_FALLING_3:
15717 case GD_S_FLYING_DIAMOND_FALLING_4:
15718 case GD_S_FLYING_DIAMOND_FALLING_5:
15719 case GD_S_FLYING_DIAMOND_FALLING_6:
15720 case GD_S_FLYING_DIAMOND_FALLING_7:
15721 case GD_S_FLYING_DIAMOND_FALLING_8:
15722 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15723 case GD_S_FLYING_DIAMOND_IMPACT_1:
15724 case GD_S_FLYING_DIAMOND_IMPACT_2:
15725 case GD_S_FLYING_DIAMOND_IMPACT_3:
15726 case GD_S_FLYING_DIAMOND_IMPACT_4:
15727 case GD_S_FLYING_DIAMOND_IMPACT_5:
15728 case GD_S_FLYING_DIAMOND_IMPACT_6:
15729 case GD_S_FLYING_DIAMOND_IMPACT_7:
15730 case GD_S_FLYING_DIAMOND_IMPACT_8:
15731 case GD_S_TIMEOUT_0:
15732 case GD_S_TIMEOUT_1:
15733 case GD_S_TIMEOUT_2:
15734 case GD_S_TIMEOUT_3:
15735 case GD_S_TIMEOUT_4:
15736 case GD_S_TIMEOUT_5:
15737 case GD_S_TIMEOUT_6:
15738 case GD_S_TIMEOUT_7:
15739 case GD_S_TIMEOUT_8:
15740 case GD_S_TIMEOUT_9:
15741 case GD_S_TIMEOUT_10:
15742 case GD_S_BONUS_LIFE:
15743 // trigger special post-processing (and force sound to be non-looping)
15744 return ACTION_OTHER;
15746 case GD_S_AMOEBA_MAGIC:
15747 case GD_S_FINISHED:
15748 // trigger special post-processing (and force sound to be looping)
15749 return ACTION_DEFAULT;
15752 return ACTION_DEFAULT;
15756 static int getSoundEffect_BD(int element_bd, int sample)
15758 int sound_action = getSoundAction_BD(sample);
15759 int sound_effect = element_info[SND_ELEMENT(element_bd)].sound[sound_action];
15763 if (sound_action != ACTION_OTHER &&
15764 sound_action != ACTION_DEFAULT)
15765 return sound_effect;
15767 // special post-processing for some sounds
15770 case GD_S_DIAMOND_FALLING_RANDOM:
15771 case GD_S_DIAMOND_FALLING_1:
15772 case GD_S_DIAMOND_FALLING_2:
15773 case GD_S_DIAMOND_FALLING_3:
15774 case GD_S_DIAMOND_FALLING_4:
15775 case GD_S_DIAMOND_FALLING_5:
15776 case GD_S_DIAMOND_FALLING_6:
15777 case GD_S_DIAMOND_FALLING_7:
15778 case GD_S_DIAMOND_FALLING_8:
15779 nr = (sample == GD_S_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15780 sample - GD_S_DIAMOND_FALLING_1);
15781 sound_effect = SND_BD_DIAMOND_FALLING_RANDOM_1 + nr;
15783 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15784 sound_effect = SND_BD_DIAMOND_FALLING;
15787 case GD_S_DIAMOND_IMPACT_RANDOM:
15788 case GD_S_DIAMOND_IMPACT_1:
15789 case GD_S_DIAMOND_IMPACT_2:
15790 case GD_S_DIAMOND_IMPACT_3:
15791 case GD_S_DIAMOND_IMPACT_4:
15792 case GD_S_DIAMOND_IMPACT_5:
15793 case GD_S_DIAMOND_IMPACT_6:
15794 case GD_S_DIAMOND_IMPACT_7:
15795 case GD_S_DIAMOND_IMPACT_8:
15796 nr = (sample == GD_S_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15797 sample - GD_S_DIAMOND_IMPACT_1);
15798 sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr;
15800 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15801 sound_effect = SND_BD_DIAMOND_IMPACT;
15804 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15805 case GD_S_FLYING_DIAMOND_FALLING_1:
15806 case GD_S_FLYING_DIAMOND_FALLING_2:
15807 case GD_S_FLYING_DIAMOND_FALLING_3:
15808 case GD_S_FLYING_DIAMOND_FALLING_4:
15809 case GD_S_FLYING_DIAMOND_FALLING_5:
15810 case GD_S_FLYING_DIAMOND_FALLING_6:
15811 case GD_S_FLYING_DIAMOND_FALLING_7:
15812 case GD_S_FLYING_DIAMOND_FALLING_8:
15813 nr = (sample == GD_S_FLYING_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15814 sample - GD_S_FLYING_DIAMOND_FALLING_1);
15815 sound_effect = SND_BD_FLYING_DIAMOND_FALLING_RANDOM_1 + nr;
15817 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15818 sound_effect = SND_BD_FLYING_DIAMOND_FALLING;
15821 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15822 case GD_S_FLYING_DIAMOND_IMPACT_1:
15823 case GD_S_FLYING_DIAMOND_IMPACT_2:
15824 case GD_S_FLYING_DIAMOND_IMPACT_3:
15825 case GD_S_FLYING_DIAMOND_IMPACT_4:
15826 case GD_S_FLYING_DIAMOND_IMPACT_5:
15827 case GD_S_FLYING_DIAMOND_IMPACT_6:
15828 case GD_S_FLYING_DIAMOND_IMPACT_7:
15829 case GD_S_FLYING_DIAMOND_IMPACT_8:
15830 nr = (sample == GD_S_FLYING_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15831 sample - GD_S_FLYING_DIAMOND_IMPACT_1);
15832 sound_effect = SND_BD_FLYING_DIAMOND_IMPACT_RANDOM_1 + nr;
15834 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15835 sound_effect = SND_BD_FLYING_DIAMOND_IMPACT;
15838 case GD_S_TIMEOUT_0:
15839 case GD_S_TIMEOUT_1:
15840 case GD_S_TIMEOUT_2:
15841 case GD_S_TIMEOUT_3:
15842 case GD_S_TIMEOUT_4:
15843 case GD_S_TIMEOUT_5:
15844 case GD_S_TIMEOUT_6:
15845 case GD_S_TIMEOUT_7:
15846 case GD_S_TIMEOUT_8:
15847 case GD_S_TIMEOUT_9:
15848 case GD_S_TIMEOUT_10:
15849 nr = sample - GD_S_TIMEOUT_0;
15850 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME_0 + nr;
15852 if (getSoundInfoEntryFilename(sound_effect) == NULL && sample != GD_S_TIMEOUT_0)
15853 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME;
15856 case GD_S_BONUS_LIFE:
15857 sound_effect = SND_GAME_HEALTH_BONUS;
15860 case GD_S_AMOEBA_MAGIC:
15861 sound_effect = SND_BD_AMOEBA_OTHER;
15864 case GD_S_FINISHED:
15865 sound_effect = SND_GAME_LEVELTIME_BONUS;
15869 sound_effect = SND_UNDEFINED;
15873 return sound_effect;
15876 void PlayLevelSound_BD(int xx, int yy, int element_bd, int sample)
15878 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15879 int sound_effect = getSoundEffect_BD(element, sample);
15880 int sound_action = getSoundAction_BD(sample);
15881 boolean is_loop_sound = IS_LOOP_SOUND(sound_effect);
15883 int x = xx - offset;
15884 int y = yy - offset;
15886 // some sound actions are always looping in native BD game engine
15887 if (sound_action == ACTION_DEFAULT)
15888 is_loop_sound = TRUE;
15890 // some sound actions are always non-looping in native BD game engine
15891 if (sound_action == ACTION_FALLING ||
15892 sound_action == ACTION_MOVING ||
15893 sound_action == ACTION_OTHER)
15894 is_loop_sound = FALSE;
15896 if (sound_effect != SND_UNDEFINED)
15897 PlayLevelSoundExt(x, y, sound_effect, is_loop_sound);
15900 void StopSound_BD(int element_bd, int sample)
15902 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15903 int sound_effect = getSoundEffect_BD(element, sample);
15905 if (sound_effect != SND_UNDEFINED)
15906 StopSound(sound_effect);
15909 boolean isSoundPlaying_BD(int element_bd, int sample)
15911 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15912 int sound_effect = getSoundEffect_BD(element, sample);
15914 if (sound_effect != SND_UNDEFINED)
15915 return isSoundPlaying(sound_effect);
15920 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15922 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15924 int x = xx - offset;
15925 int y = yy - offset;
15930 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15934 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15938 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15942 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15946 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15950 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15954 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15957 case SOUND_android_clone:
15958 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15961 case SOUND_android_move:
15962 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15966 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15970 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15974 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15977 case SOUND_eater_eat:
15978 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15982 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15985 case SOUND_collect:
15986 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15989 case SOUND_diamond:
15990 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15994 // !!! CHECK THIS !!!
15996 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15998 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
16002 case SOUND_wonderfall:
16003 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
16007 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
16011 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
16015 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
16019 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
16023 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16027 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
16031 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16035 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16038 case SOUND_exit_open:
16039 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
16042 case SOUND_exit_leave:
16043 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16046 case SOUND_dynamite:
16047 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16051 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16055 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
16059 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16063 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
16067 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
16071 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
16075 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
16080 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
16082 int element = map_element_SP_to_RND(element_sp);
16083 int action = map_action_SP_to_RND(action_sp);
16084 int offset = (setup.sp_show_border_elements ? 0 : 1);
16085 int x = xx - offset;
16086 int y = yy - offset;
16088 PlayLevelSoundElementAction(x, y, element, action);
16091 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
16093 int element = map_element_MM_to_RND(element_mm);
16094 int action = map_action_MM_to_RND(action_mm);
16096 int x = xx - offset;
16097 int y = yy - offset;
16099 if (!IS_MM_ELEMENT(element))
16100 element = EL_MM_DEFAULT;
16102 PlayLevelSoundElementAction(x, y, element, action);
16105 void PlaySound_MM(int sound_mm)
16107 int sound = map_sound_MM_to_RND(sound_mm);
16109 if (sound == SND_UNDEFINED)
16115 void PlaySoundLoop_MM(int sound_mm)
16117 int sound = map_sound_MM_to_RND(sound_mm);
16119 if (sound == SND_UNDEFINED)
16122 PlaySoundLoop(sound);
16125 void StopSound_MM(int sound_mm)
16127 int sound = map_sound_MM_to_RND(sound_mm);
16129 if (sound == SND_UNDEFINED)
16135 void RaiseScore(int value)
16137 game.score += value;
16139 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
16141 DisplayGameControlValues();
16144 void RaiseScoreElement(int element)
16149 case EL_BD_DIAMOND:
16150 case EL_EMERALD_YELLOW:
16151 case EL_EMERALD_RED:
16152 case EL_EMERALD_PURPLE:
16153 case EL_SP_INFOTRON:
16154 RaiseScore(level.score[SC_EMERALD]);
16157 RaiseScore(level.score[SC_DIAMOND]);
16160 RaiseScore(level.score[SC_CRYSTAL]);
16163 RaiseScore(level.score[SC_PEARL]);
16166 case EL_BD_BUTTERFLY:
16167 case EL_SP_ELECTRON:
16168 RaiseScore(level.score[SC_BUG]);
16171 case EL_BD_FIREFLY:
16172 case EL_SP_SNIKSNAK:
16173 RaiseScore(level.score[SC_SPACESHIP]);
16176 case EL_DARK_YAMYAM:
16177 RaiseScore(level.score[SC_YAMYAM]);
16180 RaiseScore(level.score[SC_ROBOT]);
16183 RaiseScore(level.score[SC_PACMAN]);
16186 RaiseScore(level.score[SC_NUT]);
16189 case EL_EM_DYNAMITE:
16190 case EL_SP_DISK_RED:
16191 case EL_DYNABOMB_INCREASE_NUMBER:
16192 case EL_DYNABOMB_INCREASE_SIZE:
16193 case EL_DYNABOMB_INCREASE_POWER:
16194 RaiseScore(level.score[SC_DYNAMITE]);
16196 case EL_SHIELD_NORMAL:
16197 case EL_SHIELD_DEADLY:
16198 RaiseScore(level.score[SC_SHIELD]);
16200 case EL_EXTRA_TIME:
16201 RaiseScore(level.extra_time_score);
16215 case EL_DC_KEY_WHITE:
16216 RaiseScore(level.score[SC_KEY]);
16219 RaiseScore(element_info[element].collect_score);
16224 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
16226 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
16230 // prevent short reactivation of overlay buttons while closing door
16231 SetOverlayActive(FALSE);
16232 UnmapGameButtons();
16234 // door may still be open due to skipped or envelope style request
16235 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
16238 if (network.enabled)
16240 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
16244 // when using BD game engine, cover screen before fading out
16245 if (!quick_quit && level.game_engine_type == GAME_ENGINE_TYPE_BD)
16246 game_bd.cover_screen = TRUE;
16249 FadeSkipNextFadeIn();
16251 SetGameStatus(GAME_MODE_MAIN);
16256 else // continue playing the game
16258 if (tape.playing && tape.deactivate_display)
16259 TapeDeactivateDisplayOff(TRUE);
16261 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
16263 if (tape.playing && tape.deactivate_display)
16264 TapeDeactivateDisplayOn();
16268 void RequestQuitGame(boolean escape_key_pressed)
16270 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
16271 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
16272 level_editor_test_game);
16273 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
16274 quick_quit || score_info_tape_play);
16276 RequestQuitGameExt(skip_request, quick_quit,
16277 "Do you really want to quit the game?");
16280 static char *getRestartGameMessage(void)
16282 boolean play_again = hasStartedNetworkGame();
16283 static char message[MAX_OUTPUT_LINESIZE];
16284 char *game_over_text = "Game over!";
16285 char *play_again_text = " Play it again?";
16287 if (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
16288 game_mm.game_over_message != NULL)
16289 game_over_text = game_mm.game_over_message;
16291 snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text,
16292 (play_again ? play_again_text : ""));
16297 static void RequestRestartGame(void)
16299 char *message = getRestartGameMessage();
16300 boolean has_started_game = hasStartedNetworkGame();
16301 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
16302 int door_state = DOOR_CLOSE_1;
16304 boolean restart_wanted = (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game);
16306 // if no restart wanted, continue with next level for BD style intermission levels
16307 if (!restart_wanted && !level_editor_test_game && level.bd_intermission)
16309 boolean success = AdvanceToNextLevel();
16311 restart_wanted = (success && setup.auto_play_next_level);
16314 if (restart_wanted)
16316 CloseDoor(door_state);
16318 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16322 // if game was invoked from level editor, also close tape recorder door
16323 if (level_editor_test_game)
16324 door_state = DOOR_CLOSE_ALL;
16326 CloseDoor(door_state);
16328 SetGameStatus(GAME_MODE_MAIN);
16334 boolean CheckRestartGame(void)
16336 static int game_over_delay = 0;
16337 int game_over_delay_value = 50;
16338 boolean game_over = checkGameFailed();
16342 game_over_delay = game_over_delay_value;
16347 if (game_over_delay > 0)
16349 if (game_over_delay == game_over_delay_value / 2)
16350 PlaySound(SND_GAME_LOSING);
16357 // do not ask to play again if request dialog is already active
16358 if (checkRequestActive())
16361 // do not ask to play again if request dialog already handled
16362 if (game.RestartGameRequested)
16365 // do not ask to play again if game was never actually played
16366 if (!game.GamePlayed)
16369 // do not ask to play again if this was disabled in setup menu
16370 if (!setup.ask_on_game_over)
16373 game.RestartGameRequested = TRUE;
16375 RequestRestartGame();
16380 boolean checkGameRunning(void)
16382 if (game_status != GAME_MODE_PLAYING)
16385 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGameRunning_BD())
16391 boolean checkGamePlaying(void)
16393 if (game_status != GAME_MODE_PLAYING)
16396 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGamePlaying_BD())
16402 boolean checkGameSolved(void)
16404 // set for all game engines if level was solved
16405 return game.LevelSolved_GameEnd;
16408 boolean checkGameFailed(void)
16410 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
16411 return (game_bd.game_over && !game_bd.level_solved);
16412 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16413 return (game_em.game_over && !game_em.level_solved);
16414 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16415 return (game_sp.game_over && !game_sp.level_solved);
16416 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16417 return (game_mm.game_over && !game_mm.level_solved);
16418 else // GAME_ENGINE_TYPE_RND
16419 return (game.GameOver && !game.LevelSolved);
16422 boolean checkGameEnded(void)
16424 return (checkGameSolved() || checkGameFailed());
16427 boolean checkRequestActive(void)
16429 return (game.request_active || game.envelope_active || game.any_door_active);
16433 // ----------------------------------------------------------------------------
16434 // random generator functions
16435 // ----------------------------------------------------------------------------
16437 unsigned int InitEngineRandom_RND(int seed)
16439 game.num_random_calls = 0;
16441 return InitEngineRandom(seed);
16444 unsigned int RND(int max)
16448 game.num_random_calls++;
16450 return GetEngineRandom(max);
16457 // ----------------------------------------------------------------------------
16458 // game engine snapshot handling functions
16459 // ----------------------------------------------------------------------------
16461 struct EngineSnapshotInfo
16463 // runtime values for custom element collect score
16464 int collect_score[NUM_CUSTOM_ELEMENTS];
16466 // runtime values for group element choice position
16467 int choice_pos[NUM_GROUP_ELEMENTS];
16469 // runtime values for belt position animations
16470 int belt_graphic[4][NUM_BELT_PARTS];
16471 int belt_anim_mode[4][NUM_BELT_PARTS];
16474 static struct EngineSnapshotInfo engine_snapshot_rnd;
16475 static char *snapshot_level_identifier = NULL;
16476 static int snapshot_level_nr = -1;
16478 static void SaveEngineSnapshotValues_RND(void)
16480 static int belt_base_active_element[4] =
16482 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
16483 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
16484 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
16485 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
16489 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16491 int element = EL_CUSTOM_START + i;
16493 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
16496 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16498 int element = EL_GROUP_START + i;
16500 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
16503 for (i = 0; i < 4; i++)
16505 for (j = 0; j < NUM_BELT_PARTS; j++)
16507 int element = belt_base_active_element[i] + j;
16508 int graphic = el2img(element);
16509 int anim_mode = graphic_info[graphic].anim_mode;
16511 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
16512 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
16517 static void LoadEngineSnapshotValues_RND(void)
16519 unsigned int num_random_calls = game.num_random_calls;
16522 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16524 int element = EL_CUSTOM_START + i;
16526 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
16529 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16531 int element = EL_GROUP_START + i;
16533 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
16536 for (i = 0; i < 4; i++)
16538 for (j = 0; j < NUM_BELT_PARTS; j++)
16540 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
16541 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
16543 graphic_info[graphic].anim_mode = anim_mode;
16547 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16549 InitRND(tape.random_seed);
16550 for (i = 0; i < num_random_calls; i++)
16554 if (game.num_random_calls != num_random_calls)
16556 Error("number of random calls out of sync");
16557 Error("number of random calls should be %d", num_random_calls);
16558 Error("number of random calls is %d", game.num_random_calls);
16560 Fail("this should not happen -- please debug");
16564 void FreeEngineSnapshotSingle(void)
16566 FreeSnapshotSingle();
16568 setString(&snapshot_level_identifier, NULL);
16569 snapshot_level_nr = -1;
16572 void FreeEngineSnapshotList(void)
16574 FreeSnapshotList();
16577 static ListNode *SaveEngineSnapshotBuffers(void)
16579 ListNode *buffers = NULL;
16581 // copy some special values to a structure better suited for the snapshot
16583 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16584 SaveEngineSnapshotValues_RND();
16585 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16586 SaveEngineSnapshotValues_EM();
16587 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16588 SaveEngineSnapshotValues_SP(&buffers);
16589 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16590 SaveEngineSnapshotValues_MM();
16592 // save values stored in special snapshot structure
16594 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16595 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
16596 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16597 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
16598 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16599 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
16600 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16601 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
16603 // save further RND engine values
16605 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
16606 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
16607 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
16609 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
16610 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
16611 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
16612 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16613 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTimeFrames));
16614 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16616 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16617 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16618 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16620 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16622 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16623 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16625 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16626 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16627 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16628 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16629 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16630 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16631 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16632 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16633 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16634 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16635 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16636 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16637 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16638 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16639 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16640 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16641 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16642 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16644 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16645 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16647 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16648 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16649 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16651 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16652 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16654 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16655 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16656 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16657 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16658 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16659 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16661 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16662 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16665 ListNode *node = engine_snapshot_list_rnd;
16668 while (node != NULL)
16670 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16675 Debug("game:playing:SaveEngineSnapshotBuffers",
16676 "size of engine snapshot: %d bytes", num_bytes);
16682 void SaveEngineSnapshotSingle(void)
16684 ListNode *buffers = SaveEngineSnapshotBuffers();
16686 // finally save all snapshot buffers to single snapshot
16687 SaveSnapshotSingle(buffers);
16689 // save level identification information
16690 setString(&snapshot_level_identifier, leveldir_current->identifier);
16691 snapshot_level_nr = level_nr;
16694 boolean CheckSaveEngineSnapshotToList(void)
16696 boolean save_snapshot =
16697 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16698 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16699 game.snapshot.changed_action) ||
16700 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16701 game.snapshot.collected_item));
16703 game.snapshot.changed_action = FALSE;
16704 game.snapshot.collected_item = FALSE;
16705 game.snapshot.save_snapshot = save_snapshot;
16707 return save_snapshot;
16710 void SaveEngineSnapshotToList(void)
16712 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16716 ListNode *buffers = SaveEngineSnapshotBuffers();
16718 // finally save all snapshot buffers to snapshot list
16719 SaveSnapshotToList(buffers);
16722 void SaveEngineSnapshotToListInitial(void)
16724 FreeEngineSnapshotList();
16726 SaveEngineSnapshotToList();
16729 static void LoadEngineSnapshotValues(void)
16731 // restore special values from snapshot structure
16733 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16734 LoadEngineSnapshotValues_RND();
16735 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16736 LoadEngineSnapshotValues_EM();
16737 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16738 LoadEngineSnapshotValues_SP();
16739 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16740 LoadEngineSnapshotValues_MM();
16743 void LoadEngineSnapshotSingle(void)
16745 LoadSnapshotSingle();
16747 LoadEngineSnapshotValues();
16750 static void LoadEngineSnapshot_Undo(int steps)
16752 LoadSnapshotFromList_Older(steps);
16754 LoadEngineSnapshotValues();
16757 static void LoadEngineSnapshot_Redo(int steps)
16759 LoadSnapshotFromList_Newer(steps);
16761 LoadEngineSnapshotValues();
16764 boolean CheckEngineSnapshotSingle(void)
16766 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16767 snapshot_level_nr == level_nr);
16770 boolean CheckEngineSnapshotList(void)
16772 return CheckSnapshotList();
16776 // ---------- new game button stuff -------------------------------------------
16783 boolean *setup_value;
16784 boolean allowed_on_tape;
16785 boolean is_touch_button;
16787 } gamebutton_info[NUM_GAME_BUTTONS] =
16790 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16791 GAME_CTRL_ID_STOP, NULL,
16792 TRUE, FALSE, "stop game"
16795 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16796 GAME_CTRL_ID_PAUSE, NULL,
16797 TRUE, FALSE, "pause game"
16800 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16801 GAME_CTRL_ID_PLAY, NULL,
16802 TRUE, FALSE, "play game"
16805 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16806 GAME_CTRL_ID_UNDO, NULL,
16807 TRUE, FALSE, "undo step"
16810 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16811 GAME_CTRL_ID_REDO, NULL,
16812 TRUE, FALSE, "redo step"
16815 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16816 GAME_CTRL_ID_SAVE, NULL,
16817 TRUE, FALSE, "save game"
16820 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16821 GAME_CTRL_ID_PAUSE2, NULL,
16822 TRUE, FALSE, "pause game"
16825 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16826 GAME_CTRL_ID_LOAD, NULL,
16827 TRUE, FALSE, "load game"
16830 IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart,
16831 GAME_CTRL_ID_RESTART, NULL,
16832 TRUE, FALSE, "restart game"
16835 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16836 GAME_CTRL_ID_PANEL_STOP, NULL,
16837 FALSE, FALSE, "stop game"
16840 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16841 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16842 FALSE, FALSE, "pause game"
16845 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16846 GAME_CTRL_ID_PANEL_PLAY, NULL,
16847 FALSE, FALSE, "play game"
16850 IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart,
16851 GAME_CTRL_ID_PANEL_RESTART, NULL,
16852 FALSE, FALSE, "restart game"
16855 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16856 GAME_CTRL_ID_TOUCH_STOP, NULL,
16857 FALSE, TRUE, "stop game"
16860 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16861 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16862 FALSE, TRUE, "pause game"
16865 IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart,
16866 GAME_CTRL_ID_TOUCH_RESTART, NULL,
16867 FALSE, TRUE, "restart game"
16870 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16871 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16872 TRUE, FALSE, "background music on/off"
16875 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16876 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16877 TRUE, FALSE, "sound loops on/off"
16880 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16881 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16882 TRUE, FALSE, "normal sounds on/off"
16885 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16886 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16887 FALSE, FALSE, "background music on/off"
16890 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16891 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16892 FALSE, FALSE, "sound loops on/off"
16895 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16896 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16897 FALSE, FALSE, "normal sounds on/off"
16901 void CreateGameButtons(void)
16905 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16907 int graphic = gamebutton_info[i].graphic;
16908 struct GraphicInfo *gfx = &graphic_info[graphic];
16909 struct XY *pos = gamebutton_info[i].pos;
16910 struct GadgetInfo *gi;
16913 unsigned int event_mask;
16914 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16915 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16916 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16917 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16918 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16919 int gd_x = gfx->src_x;
16920 int gd_y = gfx->src_y;
16921 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16922 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16923 int gd_xa = gfx->src_x + gfx->active_xoffset;
16924 int gd_ya = gfx->src_y + gfx->active_yoffset;
16925 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16926 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16927 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16928 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16931 // do not use touch buttons if overlay touch buttons are disabled
16932 if (is_touch_button && !setup.touch.overlay_buttons)
16935 if (gfx->bitmap == NULL)
16937 game_gadget[id] = NULL;
16942 if (id == GAME_CTRL_ID_STOP ||
16943 id == GAME_CTRL_ID_PANEL_STOP ||
16944 id == GAME_CTRL_ID_TOUCH_STOP ||
16945 id == GAME_CTRL_ID_PLAY ||
16946 id == GAME_CTRL_ID_PANEL_PLAY ||
16947 id == GAME_CTRL_ID_SAVE ||
16948 id == GAME_CTRL_ID_LOAD ||
16949 id == GAME_CTRL_ID_RESTART ||
16950 id == GAME_CTRL_ID_PANEL_RESTART ||
16951 id == GAME_CTRL_ID_TOUCH_RESTART)
16953 button_type = GD_TYPE_NORMAL_BUTTON;
16955 event_mask = GD_EVENT_RELEASED;
16957 else if (id == GAME_CTRL_ID_UNDO ||
16958 id == GAME_CTRL_ID_REDO)
16960 button_type = GD_TYPE_NORMAL_BUTTON;
16962 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16966 button_type = GD_TYPE_CHECK_BUTTON;
16967 checked = (gamebutton_info[i].setup_value != NULL ?
16968 *gamebutton_info[i].setup_value : FALSE);
16969 event_mask = GD_EVENT_PRESSED;
16972 gi = CreateGadget(GDI_CUSTOM_ID, id,
16973 GDI_IMAGE_ID, graphic,
16974 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16977 GDI_WIDTH, gfx->width,
16978 GDI_HEIGHT, gfx->height,
16979 GDI_TYPE, button_type,
16980 GDI_STATE, GD_BUTTON_UNPRESSED,
16981 GDI_CHECKED, checked,
16982 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16983 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16984 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16985 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16986 GDI_DIRECT_DRAW, FALSE,
16987 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16988 GDI_EVENT_MASK, event_mask,
16989 GDI_CALLBACK_ACTION, HandleGameButtons,
16993 Fail("cannot create gadget");
16995 game_gadget[id] = gi;
16999 void FreeGameButtons(void)
17003 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17004 FreeGadget(game_gadget[i]);
17007 static void UnmapGameButtonsAtSamePosition(int id)
17011 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17013 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
17014 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
17015 UnmapGadget(game_gadget[i]);
17018 static void UnmapGameButtonsAtSamePosition_All(void)
17020 if (setup.show_load_save_buttons)
17022 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
17023 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
17024 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
17026 else if (setup.show_undo_redo_buttons)
17028 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
17029 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
17030 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
17034 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
17035 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
17036 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
17038 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
17039 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
17040 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
17044 void MapLoadSaveButtons(void)
17046 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
17047 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
17049 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
17050 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
17053 void MapUndoRedoButtons(void)
17055 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
17056 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
17058 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
17059 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
17062 void ModifyPauseButtons(void)
17066 GAME_CTRL_ID_PAUSE,
17067 GAME_CTRL_ID_PAUSE2,
17068 GAME_CTRL_ID_PANEL_PAUSE,
17069 GAME_CTRL_ID_TOUCH_PAUSE,
17074 // do not redraw pause button on closed door (may happen when restarting game)
17075 if (!(GetDoorState() & DOOR_OPEN_1))
17078 for (i = 0; ids[i] > -1; i++)
17079 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
17082 static void MapGameButtonsExt(boolean on_tape)
17086 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17088 if ((i == GAME_CTRL_ID_UNDO ||
17089 i == GAME_CTRL_ID_REDO) &&
17090 game_status != GAME_MODE_PLAYING)
17093 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17094 MapGadget(game_gadget[i]);
17097 UnmapGameButtonsAtSamePosition_All();
17099 RedrawGameButtons();
17102 static void UnmapGameButtonsExt(boolean on_tape)
17106 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17107 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17108 UnmapGadget(game_gadget[i]);
17111 static void RedrawGameButtonsExt(boolean on_tape)
17115 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17116 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17117 RedrawGadget(game_gadget[i]);
17120 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
17125 gi->checked = state;
17128 static void RedrawSoundButtonGadget(int id)
17130 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
17131 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
17132 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
17133 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
17134 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
17135 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
17138 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
17139 RedrawGadget(game_gadget[id2]);
17142 void MapGameButtons(void)
17144 MapGameButtonsExt(FALSE);
17147 void UnmapGameButtons(void)
17149 UnmapGameButtonsExt(FALSE);
17152 void RedrawGameButtons(void)
17154 RedrawGameButtonsExt(FALSE);
17157 void MapGameButtonsOnTape(void)
17159 MapGameButtonsExt(TRUE);
17162 void UnmapGameButtonsOnTape(void)
17164 UnmapGameButtonsExt(TRUE);
17167 void RedrawGameButtonsOnTape(void)
17169 RedrawGameButtonsExt(TRUE);
17172 static void GameUndoRedoExt(void)
17174 ClearPlayerAction();
17176 tape.pausing = TRUE;
17179 UpdateAndDisplayGameControlValues();
17181 DrawCompleteVideoDisplay();
17182 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
17183 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
17184 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
17186 ModifyPauseButtons();
17191 static void GameUndo(int steps)
17193 if (!CheckEngineSnapshotList())
17196 int tape_property_bits = tape.property_bits;
17198 LoadEngineSnapshot_Undo(steps);
17200 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17205 static void GameRedo(int steps)
17207 if (!CheckEngineSnapshotList())
17210 int tape_property_bits = tape.property_bits;
17212 LoadEngineSnapshot_Redo(steps);
17214 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17219 static void HandleGameButtonsExt(int id, int button)
17221 static boolean game_undo_executed = FALSE;
17222 int steps = BUTTON_STEPSIZE(button);
17223 boolean handle_game_buttons =
17224 (game_status == GAME_MODE_PLAYING ||
17225 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
17227 if (!handle_game_buttons)
17232 case GAME_CTRL_ID_STOP:
17233 case GAME_CTRL_ID_PANEL_STOP:
17234 case GAME_CTRL_ID_TOUCH_STOP:
17239 case GAME_CTRL_ID_PAUSE:
17240 case GAME_CTRL_ID_PAUSE2:
17241 case GAME_CTRL_ID_PANEL_PAUSE:
17242 case GAME_CTRL_ID_TOUCH_PAUSE:
17243 if (network.enabled && game_status == GAME_MODE_PLAYING)
17246 SendToServer_ContinuePlaying();
17248 SendToServer_PausePlaying();
17251 TapeTogglePause(TAPE_TOGGLE_MANUAL);
17253 game_undo_executed = FALSE;
17257 case GAME_CTRL_ID_PLAY:
17258 case GAME_CTRL_ID_PANEL_PLAY:
17259 if (game_status == GAME_MODE_MAIN)
17261 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
17263 else if (tape.pausing)
17265 if (network.enabled)
17266 SendToServer_ContinuePlaying();
17268 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
17272 case GAME_CTRL_ID_UNDO:
17273 // Important: When using "save snapshot when collecting an item" mode,
17274 // load last (current) snapshot for first "undo" after pressing "pause"
17275 // (else the last-but-one snapshot would be loaded, because the snapshot
17276 // pointer already points to the last snapshot when pressing "pause",
17277 // which is fine for "every step/move" mode, but not for "every collect")
17278 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
17279 !game_undo_executed)
17282 game_undo_executed = TRUE;
17287 case GAME_CTRL_ID_REDO:
17291 case GAME_CTRL_ID_SAVE:
17295 case GAME_CTRL_ID_LOAD:
17299 case GAME_CTRL_ID_RESTART:
17300 case GAME_CTRL_ID_PANEL_RESTART:
17301 case GAME_CTRL_ID_TOUCH_RESTART:
17306 case SOUND_CTRL_ID_MUSIC:
17307 case SOUND_CTRL_ID_PANEL_MUSIC:
17308 if (setup.sound_music)
17310 setup.sound_music = FALSE;
17314 else if (audio.music_available)
17316 setup.sound = setup.sound_music = TRUE;
17318 SetAudioMode(setup.sound);
17320 if (game_status == GAME_MODE_PLAYING)
17324 RedrawSoundButtonGadget(id);
17328 case SOUND_CTRL_ID_LOOPS:
17329 case SOUND_CTRL_ID_PANEL_LOOPS:
17330 if (setup.sound_loops)
17331 setup.sound_loops = FALSE;
17332 else if (audio.loops_available)
17334 setup.sound = setup.sound_loops = TRUE;
17336 SetAudioMode(setup.sound);
17339 RedrawSoundButtonGadget(id);
17343 case SOUND_CTRL_ID_SIMPLE:
17344 case SOUND_CTRL_ID_PANEL_SIMPLE:
17345 if (setup.sound_simple)
17346 setup.sound_simple = FALSE;
17347 else if (audio.sound_available)
17349 setup.sound = setup.sound_simple = TRUE;
17351 SetAudioMode(setup.sound);
17354 RedrawSoundButtonGadget(id);
17363 static void HandleGameButtons(struct GadgetInfo *gi)
17365 HandleGameButtonsExt(gi->custom_id, gi->event.button);
17368 void HandleSoundButtonKeys(Key key)
17370 if (key == setup.shortcut.sound_simple)
17371 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
17372 else if (key == setup.shortcut.sound_loops)
17373 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
17374 else if (key == setup.shortcut.sound_music)
17375 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);