1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_INIT_PLAYER 1
27 #define DEBUG_PLAYER_ACTIONS 0
30 #define USE_NEW_AMOEBA_CODE FALSE
33 #define USE_QUICKSAND_BD_ROCK_BUGFIX 0
34 #define USE_QUICKSAND_IMPACT_BUGFIX 0
35 #define USE_DELAYED_GFX_REDRAW 0
36 #define USE_NEW_PLAYER_ASSIGNMENTS 1
38 #if USE_DELAYED_GFX_REDRAW
39 #define TEST_DrawLevelField(x, y) \
40 GfxRedraw[x][y] |= GFX_REDRAW_TILE
41 #define TEST_DrawLevelFieldCrumbled(x, y) \
42 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED
43 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
44 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS
45 #define TEST_DrawTwinkleOnField(x, y) \
46 GfxRedraw[x][y] |= GFX_REDRAW_TILE_TWINKLED
48 #define TEST_DrawLevelField(x, y) \
50 #define TEST_DrawLevelFieldCrumbled(x, y) \
51 DrawLevelFieldCrumbled(x, y)
52 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
53 DrawLevelFieldCrumbledNeighbours(x, y)
54 #define TEST_DrawTwinkleOnField(x, y) \
55 DrawTwinkleOnField(x, y)
65 #define MP_NO_ACTION 0
68 #define MP_DONT_RUN_INTO (MP_MOVING | MP_ACTION)
72 #define SCROLL_GO_ON 1
74 // for Bang()/Explode()
75 #define EX_PHASE_START 0
76 #define EX_TYPE_NONE 0
77 #define EX_TYPE_NORMAL (1 << 0)
78 #define EX_TYPE_CENTER (1 << 1)
79 #define EX_TYPE_BORDER (1 << 2)
80 #define EX_TYPE_CROSS (1 << 3)
81 #define EX_TYPE_DYNA (1 << 4)
82 #define EX_TYPE_SINGLE_TILE (EX_TYPE_CENTER | EX_TYPE_BORDER)
84 #define PANEL_OFF() (game.panel.active == FALSE)
85 #define PANEL_DEACTIVATED(p) ((p)->x < 0 || (p)->y < 0 || PANEL_OFF())
86 #define PANEL_XPOS(p) (DX + ALIGNED_TEXT_XPOS(p))
87 #define PANEL_YPOS(p) (DY + ALIGNED_TEXT_YPOS(p))
89 // game panel display and control definitions
90 #define GAME_PANEL_LEVEL_NUMBER 0
91 #define GAME_PANEL_GEMS 1
92 #define GAME_PANEL_GEMS_NEEDED 2
93 #define GAME_PANEL_GEMS_COLLECTED 3
94 #define GAME_PANEL_GEMS_SCORE 4
95 #define GAME_PANEL_INVENTORY_COUNT 5
96 #define GAME_PANEL_INVENTORY_FIRST_1 6
97 #define GAME_PANEL_INVENTORY_FIRST_2 7
98 #define GAME_PANEL_INVENTORY_FIRST_3 8
99 #define GAME_PANEL_INVENTORY_FIRST_4 9
100 #define GAME_PANEL_INVENTORY_FIRST_5 10
101 #define GAME_PANEL_INVENTORY_FIRST_6 11
102 #define GAME_PANEL_INVENTORY_FIRST_7 12
103 #define GAME_PANEL_INVENTORY_FIRST_8 13
104 #define GAME_PANEL_INVENTORY_LAST_1 14
105 #define GAME_PANEL_INVENTORY_LAST_2 15
106 #define GAME_PANEL_INVENTORY_LAST_3 16
107 #define GAME_PANEL_INVENTORY_LAST_4 17
108 #define GAME_PANEL_INVENTORY_LAST_5 18
109 #define GAME_PANEL_INVENTORY_LAST_6 19
110 #define GAME_PANEL_INVENTORY_LAST_7 20
111 #define GAME_PANEL_INVENTORY_LAST_8 21
112 #define GAME_PANEL_KEY_1 22
113 #define GAME_PANEL_KEY_2 23
114 #define GAME_PANEL_KEY_3 24
115 #define GAME_PANEL_KEY_4 25
116 #define GAME_PANEL_KEY_5 26
117 #define GAME_PANEL_KEY_6 27
118 #define GAME_PANEL_KEY_7 28
119 #define GAME_PANEL_KEY_8 29
120 #define GAME_PANEL_KEY_WHITE 30
121 #define GAME_PANEL_KEY_WHITE_COUNT 31
122 #define GAME_PANEL_SCORE 32
123 #define GAME_PANEL_HIGHSCORE 33
124 #define GAME_PANEL_TIME 34
125 #define GAME_PANEL_TIME_HH 35
126 #define GAME_PANEL_TIME_MM 36
127 #define GAME_PANEL_TIME_SS 37
128 #define GAME_PANEL_TIME_ANIM 38
129 #define GAME_PANEL_HEALTH 39
130 #define GAME_PANEL_HEALTH_ANIM 40
131 #define GAME_PANEL_FRAME 41
132 #define GAME_PANEL_SHIELD_NORMAL 42
133 #define GAME_PANEL_SHIELD_NORMAL_TIME 43
134 #define GAME_PANEL_SHIELD_DEADLY 44
135 #define GAME_PANEL_SHIELD_DEADLY_TIME 45
136 #define GAME_PANEL_EXIT 46
137 #define GAME_PANEL_EMC_MAGIC_BALL 47
138 #define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 48
139 #define GAME_PANEL_LIGHT_SWITCH 49
140 #define GAME_PANEL_LIGHT_SWITCH_TIME 50
141 #define GAME_PANEL_TIMEGATE_SWITCH 51
142 #define GAME_PANEL_TIMEGATE_SWITCH_TIME 52
143 #define GAME_PANEL_SWITCHGATE_SWITCH 53
144 #define GAME_PANEL_EMC_LENSES 54
145 #define GAME_PANEL_EMC_LENSES_TIME 55
146 #define GAME_PANEL_EMC_MAGNIFIER 56
147 #define GAME_PANEL_EMC_MAGNIFIER_TIME 57
148 #define GAME_PANEL_BALLOON_SWITCH 58
149 #define GAME_PANEL_DYNABOMB_NUMBER 59
150 #define GAME_PANEL_DYNABOMB_SIZE 60
151 #define GAME_PANEL_DYNABOMB_POWER 61
152 #define GAME_PANEL_PENGUINS 62
153 #define GAME_PANEL_SOKOBAN_OBJECTS 63
154 #define GAME_PANEL_SOKOBAN_FIELDS 64
155 #define GAME_PANEL_ROBOT_WHEEL 65
156 #define GAME_PANEL_CONVEYOR_BELT_1 66
157 #define GAME_PANEL_CONVEYOR_BELT_2 67
158 #define GAME_PANEL_CONVEYOR_BELT_3 68
159 #define GAME_PANEL_CONVEYOR_BELT_4 69
160 #define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 70
161 #define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 71
162 #define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 72
163 #define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 73
164 #define GAME_PANEL_MAGIC_WALL 74
165 #define GAME_PANEL_MAGIC_WALL_TIME 75
166 #define GAME_PANEL_GRAVITY_STATE 76
167 #define GAME_PANEL_GRAPHIC_1 77
168 #define GAME_PANEL_GRAPHIC_2 78
169 #define GAME_PANEL_GRAPHIC_3 79
170 #define GAME_PANEL_GRAPHIC_4 80
171 #define GAME_PANEL_GRAPHIC_5 81
172 #define GAME_PANEL_GRAPHIC_6 82
173 #define GAME_PANEL_GRAPHIC_7 83
174 #define GAME_PANEL_GRAPHIC_8 84
175 #define GAME_PANEL_ELEMENT_1 85
176 #define GAME_PANEL_ELEMENT_2 86
177 #define GAME_PANEL_ELEMENT_3 87
178 #define GAME_PANEL_ELEMENT_4 88
179 #define GAME_PANEL_ELEMENT_5 89
180 #define GAME_PANEL_ELEMENT_6 90
181 #define GAME_PANEL_ELEMENT_7 91
182 #define GAME_PANEL_ELEMENT_8 92
183 #define GAME_PANEL_ELEMENT_COUNT_1 93
184 #define GAME_PANEL_ELEMENT_COUNT_2 94
185 #define GAME_PANEL_ELEMENT_COUNT_3 95
186 #define GAME_PANEL_ELEMENT_COUNT_4 96
187 #define GAME_PANEL_ELEMENT_COUNT_5 97
188 #define GAME_PANEL_ELEMENT_COUNT_6 98
189 #define GAME_PANEL_ELEMENT_COUNT_7 99
190 #define GAME_PANEL_ELEMENT_COUNT_8 100
191 #define GAME_PANEL_CE_SCORE_1 101
192 #define GAME_PANEL_CE_SCORE_2 102
193 #define GAME_PANEL_CE_SCORE_3 103
194 #define GAME_PANEL_CE_SCORE_4 104
195 #define GAME_PANEL_CE_SCORE_5 105
196 #define GAME_PANEL_CE_SCORE_6 106
197 #define GAME_PANEL_CE_SCORE_7 107
198 #define GAME_PANEL_CE_SCORE_8 108
199 #define GAME_PANEL_CE_SCORE_1_ELEMENT 109
200 #define GAME_PANEL_CE_SCORE_2_ELEMENT 110
201 #define GAME_PANEL_CE_SCORE_3_ELEMENT 111
202 #define GAME_PANEL_CE_SCORE_4_ELEMENT 112
203 #define GAME_PANEL_CE_SCORE_5_ELEMENT 113
204 #define GAME_PANEL_CE_SCORE_6_ELEMENT 114
205 #define GAME_PANEL_CE_SCORE_7_ELEMENT 115
206 #define GAME_PANEL_CE_SCORE_8_ELEMENT 116
207 #define GAME_PANEL_PLAYER_NAME 117
208 #define GAME_PANEL_LEVEL_NAME 118
209 #define GAME_PANEL_LEVEL_AUTHOR 119
211 #define NUM_GAME_PANEL_CONTROLS 120
213 struct GamePanelOrderInfo
219 static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
221 struct GamePanelControlInfo
225 struct TextPosInfo *pos;
228 int graphic, graphic_active;
230 int value, last_value;
231 int frame, last_frame;
236 static struct GamePanelControlInfo game_panel_controls[] =
239 GAME_PANEL_LEVEL_NUMBER,
240 &game.panel.level_number,
249 GAME_PANEL_GEMS_NEEDED,
250 &game.panel.gems_needed,
254 GAME_PANEL_GEMS_COLLECTED,
255 &game.panel.gems_collected,
259 GAME_PANEL_GEMS_SCORE,
260 &game.panel.gems_score,
264 GAME_PANEL_INVENTORY_COUNT,
265 &game.panel.inventory_count,
269 GAME_PANEL_INVENTORY_FIRST_1,
270 &game.panel.inventory_first[0],
274 GAME_PANEL_INVENTORY_FIRST_2,
275 &game.panel.inventory_first[1],
279 GAME_PANEL_INVENTORY_FIRST_3,
280 &game.panel.inventory_first[2],
284 GAME_PANEL_INVENTORY_FIRST_4,
285 &game.panel.inventory_first[3],
289 GAME_PANEL_INVENTORY_FIRST_5,
290 &game.panel.inventory_first[4],
294 GAME_PANEL_INVENTORY_FIRST_6,
295 &game.panel.inventory_first[5],
299 GAME_PANEL_INVENTORY_FIRST_7,
300 &game.panel.inventory_first[6],
304 GAME_PANEL_INVENTORY_FIRST_8,
305 &game.panel.inventory_first[7],
309 GAME_PANEL_INVENTORY_LAST_1,
310 &game.panel.inventory_last[0],
314 GAME_PANEL_INVENTORY_LAST_2,
315 &game.panel.inventory_last[1],
319 GAME_PANEL_INVENTORY_LAST_3,
320 &game.panel.inventory_last[2],
324 GAME_PANEL_INVENTORY_LAST_4,
325 &game.panel.inventory_last[3],
329 GAME_PANEL_INVENTORY_LAST_5,
330 &game.panel.inventory_last[4],
334 GAME_PANEL_INVENTORY_LAST_6,
335 &game.panel.inventory_last[5],
339 GAME_PANEL_INVENTORY_LAST_7,
340 &game.panel.inventory_last[6],
344 GAME_PANEL_INVENTORY_LAST_8,
345 &game.panel.inventory_last[7],
389 GAME_PANEL_KEY_WHITE,
390 &game.panel.key_white,
394 GAME_PANEL_KEY_WHITE_COUNT,
395 &game.panel.key_white_count,
404 GAME_PANEL_HIGHSCORE,
405 &game.panel.highscore,
429 GAME_PANEL_TIME_ANIM,
430 &game.panel.time_anim,
433 IMG_GFX_GAME_PANEL_TIME_ANIM,
434 IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
442 GAME_PANEL_HEALTH_ANIM,
443 &game.panel.health_anim,
446 IMG_GFX_GAME_PANEL_HEALTH_ANIM,
447 IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
455 GAME_PANEL_SHIELD_NORMAL,
456 &game.panel.shield_normal,
460 GAME_PANEL_SHIELD_NORMAL_TIME,
461 &game.panel.shield_normal_time,
465 GAME_PANEL_SHIELD_DEADLY,
466 &game.panel.shield_deadly,
470 GAME_PANEL_SHIELD_DEADLY_TIME,
471 &game.panel.shield_deadly_time,
480 GAME_PANEL_EMC_MAGIC_BALL,
481 &game.panel.emc_magic_ball,
485 GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
486 &game.panel.emc_magic_ball_switch,
490 GAME_PANEL_LIGHT_SWITCH,
491 &game.panel.light_switch,
495 GAME_PANEL_LIGHT_SWITCH_TIME,
496 &game.panel.light_switch_time,
500 GAME_PANEL_TIMEGATE_SWITCH,
501 &game.panel.timegate_switch,
505 GAME_PANEL_TIMEGATE_SWITCH_TIME,
506 &game.panel.timegate_switch_time,
510 GAME_PANEL_SWITCHGATE_SWITCH,
511 &game.panel.switchgate_switch,
515 GAME_PANEL_EMC_LENSES,
516 &game.panel.emc_lenses,
520 GAME_PANEL_EMC_LENSES_TIME,
521 &game.panel.emc_lenses_time,
525 GAME_PANEL_EMC_MAGNIFIER,
526 &game.panel.emc_magnifier,
530 GAME_PANEL_EMC_MAGNIFIER_TIME,
531 &game.panel.emc_magnifier_time,
535 GAME_PANEL_BALLOON_SWITCH,
536 &game.panel.balloon_switch,
540 GAME_PANEL_DYNABOMB_NUMBER,
541 &game.panel.dynabomb_number,
545 GAME_PANEL_DYNABOMB_SIZE,
546 &game.panel.dynabomb_size,
550 GAME_PANEL_DYNABOMB_POWER,
551 &game.panel.dynabomb_power,
556 &game.panel.penguins,
560 GAME_PANEL_SOKOBAN_OBJECTS,
561 &game.panel.sokoban_objects,
565 GAME_PANEL_SOKOBAN_FIELDS,
566 &game.panel.sokoban_fields,
570 GAME_PANEL_ROBOT_WHEEL,
571 &game.panel.robot_wheel,
575 GAME_PANEL_CONVEYOR_BELT_1,
576 &game.panel.conveyor_belt[0],
580 GAME_PANEL_CONVEYOR_BELT_2,
581 &game.panel.conveyor_belt[1],
585 GAME_PANEL_CONVEYOR_BELT_3,
586 &game.panel.conveyor_belt[2],
590 GAME_PANEL_CONVEYOR_BELT_4,
591 &game.panel.conveyor_belt[3],
595 GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
596 &game.panel.conveyor_belt_switch[0],
600 GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
601 &game.panel.conveyor_belt_switch[1],
605 GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
606 &game.panel.conveyor_belt_switch[2],
610 GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
611 &game.panel.conveyor_belt_switch[3],
615 GAME_PANEL_MAGIC_WALL,
616 &game.panel.magic_wall,
620 GAME_PANEL_MAGIC_WALL_TIME,
621 &game.panel.magic_wall_time,
625 GAME_PANEL_GRAVITY_STATE,
626 &game.panel.gravity_state,
630 GAME_PANEL_GRAPHIC_1,
631 &game.panel.graphic[0],
635 GAME_PANEL_GRAPHIC_2,
636 &game.panel.graphic[1],
640 GAME_PANEL_GRAPHIC_3,
641 &game.panel.graphic[2],
645 GAME_PANEL_GRAPHIC_4,
646 &game.panel.graphic[3],
650 GAME_PANEL_GRAPHIC_5,
651 &game.panel.graphic[4],
655 GAME_PANEL_GRAPHIC_6,
656 &game.panel.graphic[5],
660 GAME_PANEL_GRAPHIC_7,
661 &game.panel.graphic[6],
665 GAME_PANEL_GRAPHIC_8,
666 &game.panel.graphic[7],
670 GAME_PANEL_ELEMENT_1,
671 &game.panel.element[0],
675 GAME_PANEL_ELEMENT_2,
676 &game.panel.element[1],
680 GAME_PANEL_ELEMENT_3,
681 &game.panel.element[2],
685 GAME_PANEL_ELEMENT_4,
686 &game.panel.element[3],
690 GAME_PANEL_ELEMENT_5,
691 &game.panel.element[4],
695 GAME_PANEL_ELEMENT_6,
696 &game.panel.element[5],
700 GAME_PANEL_ELEMENT_7,
701 &game.panel.element[6],
705 GAME_PANEL_ELEMENT_8,
706 &game.panel.element[7],
710 GAME_PANEL_ELEMENT_COUNT_1,
711 &game.panel.element_count[0],
715 GAME_PANEL_ELEMENT_COUNT_2,
716 &game.panel.element_count[1],
720 GAME_PANEL_ELEMENT_COUNT_3,
721 &game.panel.element_count[2],
725 GAME_PANEL_ELEMENT_COUNT_4,
726 &game.panel.element_count[3],
730 GAME_PANEL_ELEMENT_COUNT_5,
731 &game.panel.element_count[4],
735 GAME_PANEL_ELEMENT_COUNT_6,
736 &game.panel.element_count[5],
740 GAME_PANEL_ELEMENT_COUNT_7,
741 &game.panel.element_count[6],
745 GAME_PANEL_ELEMENT_COUNT_8,
746 &game.panel.element_count[7],
750 GAME_PANEL_CE_SCORE_1,
751 &game.panel.ce_score[0],
755 GAME_PANEL_CE_SCORE_2,
756 &game.panel.ce_score[1],
760 GAME_PANEL_CE_SCORE_3,
761 &game.panel.ce_score[2],
765 GAME_PANEL_CE_SCORE_4,
766 &game.panel.ce_score[3],
770 GAME_PANEL_CE_SCORE_5,
771 &game.panel.ce_score[4],
775 GAME_PANEL_CE_SCORE_6,
776 &game.panel.ce_score[5],
780 GAME_PANEL_CE_SCORE_7,
781 &game.panel.ce_score[6],
785 GAME_PANEL_CE_SCORE_8,
786 &game.panel.ce_score[7],
790 GAME_PANEL_CE_SCORE_1_ELEMENT,
791 &game.panel.ce_score_element[0],
795 GAME_PANEL_CE_SCORE_2_ELEMENT,
796 &game.panel.ce_score_element[1],
800 GAME_PANEL_CE_SCORE_3_ELEMENT,
801 &game.panel.ce_score_element[2],
805 GAME_PANEL_CE_SCORE_4_ELEMENT,
806 &game.panel.ce_score_element[3],
810 GAME_PANEL_CE_SCORE_5_ELEMENT,
811 &game.panel.ce_score_element[4],
815 GAME_PANEL_CE_SCORE_6_ELEMENT,
816 &game.panel.ce_score_element[5],
820 GAME_PANEL_CE_SCORE_7_ELEMENT,
821 &game.panel.ce_score_element[6],
825 GAME_PANEL_CE_SCORE_8_ELEMENT,
826 &game.panel.ce_score_element[7],
830 GAME_PANEL_PLAYER_NAME,
831 &game.panel.player_name,
835 GAME_PANEL_LEVEL_NAME,
836 &game.panel.level_name,
840 GAME_PANEL_LEVEL_AUTHOR,
841 &game.panel.level_author,
852 // values for delayed check of falling and moving elements and for collision
853 #define CHECK_DELAY_MOVING 3
854 #define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
855 #define CHECK_DELAY_COLLISION 2
856 #define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
858 // values for initial player move delay (initial delay counter value)
859 #define INITIAL_MOVE_DELAY_OFF -1
860 #define INITIAL_MOVE_DELAY_ON 0
862 // values for player movement speed (which is in fact a delay value)
863 #define MOVE_DELAY_MIN_SPEED 32
864 #define MOVE_DELAY_NORMAL_SPEED 8
865 #define MOVE_DELAY_HIGH_SPEED 4
866 #define MOVE_DELAY_MAX_SPEED 1
868 #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
869 #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
871 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
872 #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
874 // values for scroll positions
875 #define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
876 (x) > SBX_Right + MIDPOSX ? SBX_Right :\
878 #define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
879 (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
882 // values for other actions
883 #define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
884 #define MOVE_STEPSIZE_MIN (1)
885 #define MOVE_STEPSIZE_MAX (TILEX)
887 #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
888 #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
890 #define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
892 #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
893 RND(element_info[e].push_delay_random))
894 #define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
895 RND(element_info[e].drop_delay_random))
896 #define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
897 RND(element_info[e].move_delay_random))
898 #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
899 (element_info[e].move_delay_random))
900 #define GET_NEW_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
901 RND(element_info[e].step_delay_random))
902 #define GET_MAX_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
903 (element_info[e].step_delay_random))
904 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
905 RND(element_info[e].ce_value_random_initial))
906 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
907 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
908 RND((c)->delay_random * (c)->delay_frames))
909 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
910 RND((c)->delay_random))
913 #define GET_VALID_RUNTIME_ELEMENT(e) \
914 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
916 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
917 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
918 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
919 (be) + (e) - EL_SELF)
921 #define GET_PLAYER_FROM_BITS(p) \
922 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
924 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
925 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
926 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
927 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
928 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
929 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
930 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
931 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
932 RESOLVED_REFERENCE_ELEMENT(be, e) : \
935 #define CAN_GROW_INTO(e) \
936 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
938 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
939 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
942 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
943 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
944 (CAN_MOVE_INTO_ACID(e) && \
945 Tile[x][y] == EL_ACID) || \
948 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
949 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
950 (CAN_MOVE_INTO_ACID(e) && \
951 Tile[x][y] == EL_ACID) || \
954 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
955 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
957 (CAN_MOVE_INTO_ACID(e) && \
958 Tile[x][y] == EL_ACID) || \
959 (DONT_COLLIDE_WITH(e) && \
961 !PLAYER_ENEMY_PROTECTED(x, y))))
963 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
964 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
966 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
967 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
969 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
970 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
972 #define ANDROID_CAN_CLONE_FIELD(x, y) \
973 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
974 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
976 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
977 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
979 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
980 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
982 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
983 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
985 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
986 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
988 #define PIG_CAN_ENTER_FIELD(e, x, y) \
989 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
991 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
992 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
993 Tile[x][y] == EL_EM_EXIT_OPEN || \
994 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
995 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
996 IS_FOOD_PENGUIN(Tile[x][y])))
997 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
998 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
1000 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
1001 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
1003 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
1004 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
1006 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
1007 (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
1008 Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
1010 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
1012 #define CE_ENTER_FIELD_COND(e, x, y) \
1013 (!IS_PLAYER(x, y) && \
1014 IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
1016 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
1017 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
1019 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
1020 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1022 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1023 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1024 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1025 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1027 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1029 // game button identifiers
1030 #define GAME_CTRL_ID_STOP 0
1031 #define GAME_CTRL_ID_PAUSE 1
1032 #define GAME_CTRL_ID_PLAY 2
1033 #define GAME_CTRL_ID_UNDO 3
1034 #define GAME_CTRL_ID_REDO 4
1035 #define GAME_CTRL_ID_SAVE 5
1036 #define GAME_CTRL_ID_PAUSE2 6
1037 #define GAME_CTRL_ID_LOAD 7
1038 #define GAME_CTRL_ID_RESTART 8
1039 #define GAME_CTRL_ID_PANEL_STOP 9
1040 #define GAME_CTRL_ID_PANEL_PAUSE 10
1041 #define GAME_CTRL_ID_PANEL_PLAY 11
1042 #define GAME_CTRL_ID_PANEL_RESTART 12
1043 #define GAME_CTRL_ID_TOUCH_STOP 13
1044 #define GAME_CTRL_ID_TOUCH_PAUSE 14
1045 #define GAME_CTRL_ID_TOUCH_RESTART 15
1046 #define SOUND_CTRL_ID_MUSIC 16
1047 #define SOUND_CTRL_ID_LOOPS 17
1048 #define SOUND_CTRL_ID_SIMPLE 18
1049 #define SOUND_CTRL_ID_PANEL_MUSIC 19
1050 #define SOUND_CTRL_ID_PANEL_LOOPS 20
1051 #define SOUND_CTRL_ID_PANEL_SIMPLE 21
1053 #define NUM_GAME_BUTTONS 22
1056 // forward declaration for internal use
1058 static void CreateField(int, int, int);
1060 static void ResetGfxAnimation(int, int);
1062 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1063 static void AdvanceFrameAndPlayerCounters(int);
1065 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1066 static boolean MovePlayer(struct PlayerInfo *, int, int);
1067 static void ScrollPlayer(struct PlayerInfo *, int);
1068 static void ScrollScreen(struct PlayerInfo *, int);
1070 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1071 static boolean DigFieldByCE(int, int, int);
1072 static boolean SnapField(struct PlayerInfo *, int, int);
1073 static boolean DropElement(struct PlayerInfo *);
1075 static void InitBeltMovement(void);
1076 static void CloseAllOpenTimegates(void);
1077 static void CheckGravityMovement(struct PlayerInfo *);
1078 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1079 static void KillPlayerUnlessEnemyProtected(int, int);
1080 static void KillPlayerUnlessExplosionProtected(int, int);
1082 static void CheckNextToConditions(int, int);
1083 static void TestIfPlayerNextToCustomElement(int, int);
1084 static void TestIfPlayerTouchesCustomElement(int, int);
1085 static void TestIfElementNextToCustomElement(int, int);
1086 static void TestIfElementTouchesCustomElement(int, int);
1087 static void TestIfElementHitsCustomElement(int, int, int);
1089 static void HandleElementChange(int, int, int);
1090 static void ExecuteCustomElementAction(int, int, int, int);
1091 static boolean ChangeElement(int, int, int, int);
1093 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int, int, int);
1094 #define CheckTriggeredElementChange(x, y, e, ev) \
1095 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, -1)
1096 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1097 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1098 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1099 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1100 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1101 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1102 #define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
1103 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1105 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1106 #define CheckElementChange(x, y, e, te, ev) \
1107 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1108 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1109 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1110 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1111 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1112 #define CheckElementChangeByMouse(x, y, e, ev, s) \
1113 CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
1115 static void PlayLevelSound(int, int, int);
1116 static void PlayLevelSoundNearest(int, int, int);
1117 static void PlayLevelSoundAction(int, int, int);
1118 static void PlayLevelSoundElementAction(int, int, int, int);
1119 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1120 static void PlayLevelSoundActionIfLoop(int, int, int);
1121 static void StopLevelSoundActionIfLoop(int, int, int);
1122 static void PlayLevelMusic(void);
1123 static void FadeLevelSoundsAndMusic(void);
1125 static void HandleGameButtons(struct GadgetInfo *);
1127 int AmoebaNeighbourNr(int, int);
1128 void AmoebaToDiamond(int, int);
1129 void ContinueMoving(int, int);
1130 void Bang(int, int);
1131 void InitMovDir(int, int);
1132 void InitAmoebaNr(int, int);
1133 void NewHighScore(int, boolean);
1135 void TestIfGoodThingHitsBadThing(int, int, int);
1136 void TestIfBadThingHitsGoodThing(int, int, int);
1137 void TestIfPlayerTouchesBadThing(int, int);
1138 void TestIfPlayerRunsIntoBadThing(int, int, int);
1139 void TestIfBadThingTouchesPlayer(int, int);
1140 void TestIfBadThingRunsIntoPlayer(int, int, int);
1141 void TestIfFriendTouchesBadThing(int, int);
1142 void TestIfBadThingTouchesFriend(int, int);
1143 void TestIfBadThingTouchesOtherBadThing(int, int);
1144 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1146 void KillPlayer(struct PlayerInfo *);
1147 void BuryPlayer(struct PlayerInfo *);
1148 void RemovePlayer(struct PlayerInfo *);
1149 void ExitPlayer(struct PlayerInfo *);
1151 static int getInvisibleActiveFromInvisibleElement(int);
1152 static int getInvisibleFromInvisibleActiveElement(int);
1154 static void TestFieldAfterSnapping(int, int, int, int, int);
1156 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1158 // for detection of endless loops, caused by custom element programming
1159 // (using maximal playfield width x 10 is just a rough approximation)
1160 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1162 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1164 if (recursion_loop_detected) \
1167 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1169 recursion_loop_detected = TRUE; \
1170 recursion_loop_element = (e); \
1173 recursion_loop_depth++; \
1176 #define RECURSION_LOOP_DETECTION_END() \
1178 recursion_loop_depth--; \
1181 static int recursion_loop_depth;
1182 static boolean recursion_loop_detected;
1183 static boolean recursion_loop_element;
1185 static int map_player_action[MAX_PLAYERS];
1188 // ----------------------------------------------------------------------------
1189 // definition of elements that automatically change to other elements after
1190 // a specified time, eventually calling a function when changing
1191 // ----------------------------------------------------------------------------
1193 // forward declaration for changer functions
1194 static void InitBuggyBase(int, int);
1195 static void WarnBuggyBase(int, int);
1197 static void InitTrap(int, int);
1198 static void ActivateTrap(int, int);
1199 static void ChangeActiveTrap(int, int);
1201 static void InitRobotWheel(int, int);
1202 static void RunRobotWheel(int, int);
1203 static void StopRobotWheel(int, int);
1205 static void InitTimegateWheel(int, int);
1206 static void RunTimegateWheel(int, int);
1208 static void InitMagicBallDelay(int, int);
1209 static void ActivateMagicBall(int, int);
1211 struct ChangingElementInfo
1216 void (*pre_change_function)(int x, int y);
1217 void (*change_function)(int x, int y);
1218 void (*post_change_function)(int x, int y);
1221 static struct ChangingElementInfo change_delay_list[] =
1256 EL_STEEL_EXIT_OPENING,
1264 EL_STEEL_EXIT_CLOSING,
1265 EL_STEEL_EXIT_CLOSED,
1288 EL_EM_STEEL_EXIT_OPENING,
1289 EL_EM_STEEL_EXIT_OPEN,
1296 EL_EM_STEEL_EXIT_CLOSING,
1320 EL_SWITCHGATE_OPENING,
1328 EL_SWITCHGATE_CLOSING,
1329 EL_SWITCHGATE_CLOSED,
1336 EL_TIMEGATE_OPENING,
1344 EL_TIMEGATE_CLOSING,
1353 EL_ACID_SPLASH_LEFT,
1361 EL_ACID_SPLASH_RIGHT,
1370 EL_SP_BUGGY_BASE_ACTIVATING,
1377 EL_SP_BUGGY_BASE_ACTIVATING,
1378 EL_SP_BUGGY_BASE_ACTIVE,
1385 EL_SP_BUGGY_BASE_ACTIVE,
1409 EL_ROBOT_WHEEL_ACTIVE,
1417 EL_TIMEGATE_SWITCH_ACTIVE,
1425 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1426 EL_DC_TIMEGATE_SWITCH,
1433 EL_EMC_MAGIC_BALL_ACTIVE,
1434 EL_EMC_MAGIC_BALL_ACTIVE,
1441 EL_EMC_SPRING_BUMPER_ACTIVE,
1442 EL_EMC_SPRING_BUMPER,
1449 EL_DIAGONAL_SHRINKING,
1457 EL_DIAGONAL_GROWING,
1478 int push_delay_fixed, push_delay_random;
1482 { EL_SPRING, 0, 0 },
1483 { EL_BALLOON, 0, 0 },
1485 { EL_SOKOBAN_OBJECT, 2, 0 },
1486 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1487 { EL_SATELLITE, 2, 0 },
1488 { EL_SP_DISK_YELLOW, 2, 0 },
1490 { EL_UNDEFINED, 0, 0 },
1498 move_stepsize_list[] =
1500 { EL_AMOEBA_DROP, 2 },
1501 { EL_AMOEBA_DROPPING, 2 },
1502 { EL_QUICKSAND_FILLING, 1 },
1503 { EL_QUICKSAND_EMPTYING, 1 },
1504 { EL_QUICKSAND_FAST_FILLING, 2 },
1505 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1506 { EL_MAGIC_WALL_FILLING, 2 },
1507 { EL_MAGIC_WALL_EMPTYING, 2 },
1508 { EL_BD_MAGIC_WALL_FILLING, 2 },
1509 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1510 { EL_DC_MAGIC_WALL_FILLING, 2 },
1511 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1513 { EL_UNDEFINED, 0 },
1521 collect_count_list[] =
1524 { EL_BD_DIAMOND, 1 },
1525 { EL_EMERALD_YELLOW, 1 },
1526 { EL_EMERALD_RED, 1 },
1527 { EL_EMERALD_PURPLE, 1 },
1529 { EL_SP_INFOTRON, 1 },
1533 { EL_UNDEFINED, 0 },
1541 access_direction_list[] =
1543 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1544 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1545 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1546 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1547 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1548 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1549 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1550 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1551 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1552 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1553 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1555 { EL_SP_PORT_LEFT, MV_RIGHT },
1556 { EL_SP_PORT_RIGHT, MV_LEFT },
1557 { EL_SP_PORT_UP, MV_DOWN },
1558 { EL_SP_PORT_DOWN, MV_UP },
1559 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1560 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1561 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1562 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1563 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1564 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1565 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1566 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1567 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1568 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1569 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1570 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1571 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1572 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1573 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1575 { EL_UNDEFINED, MV_NONE }
1578 static struct XY xy_topdown[] =
1586 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1588 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1589 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1590 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1591 IS_JUST_CHANGING(x, y))
1593 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1595 // static variables for playfield scan mode (scanning forward or backward)
1596 static int playfield_scan_start_x = 0;
1597 static int playfield_scan_start_y = 0;
1598 static int playfield_scan_delta_x = 1;
1599 static int playfield_scan_delta_y = 1;
1601 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1602 (y) >= 0 && (y) <= lev_fieldy - 1; \
1603 (y) += playfield_scan_delta_y) \
1604 for ((x) = playfield_scan_start_x; \
1605 (x) >= 0 && (x) <= lev_fieldx - 1; \
1606 (x) += playfield_scan_delta_x)
1609 void DEBUG_SetMaximumDynamite(void)
1613 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1614 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1615 local_player->inventory_element[local_player->inventory_size++] =
1620 static void InitPlayfieldScanModeVars(void)
1622 if (game.use_reverse_scan_direction)
1624 playfield_scan_start_x = lev_fieldx - 1;
1625 playfield_scan_start_y = lev_fieldy - 1;
1627 playfield_scan_delta_x = -1;
1628 playfield_scan_delta_y = -1;
1632 playfield_scan_start_x = 0;
1633 playfield_scan_start_y = 0;
1635 playfield_scan_delta_x = 1;
1636 playfield_scan_delta_y = 1;
1640 static void InitPlayfieldScanMode(int mode)
1642 game.use_reverse_scan_direction =
1643 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1645 InitPlayfieldScanModeVars();
1648 static int get_move_delay_from_stepsize(int move_stepsize)
1651 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1653 // make sure that stepsize value is always a power of 2
1654 move_stepsize = (1 << log_2(move_stepsize));
1656 return TILEX / move_stepsize;
1659 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1662 int player_nr = player->index_nr;
1663 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1664 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1666 // do no immediately change move delay -- the player might just be moving
1667 player->move_delay_value_next = move_delay;
1669 // information if player can move must be set separately
1670 player->cannot_move = cannot_move;
1674 player->move_delay = game.initial_move_delay[player_nr];
1675 player->move_delay_value = game.initial_move_delay_value[player_nr];
1677 player->move_delay_value_next = -1;
1679 player->move_delay_reset_counter = 0;
1683 void GetPlayerConfig(void)
1685 GameFrameDelay = setup.game_frame_delay;
1687 if (!audio.sound_available)
1688 setup.sound_simple = FALSE;
1690 if (!audio.loops_available)
1691 setup.sound_loops = FALSE;
1693 if (!audio.music_available)
1694 setup.sound_music = FALSE;
1696 if (!video.fullscreen_available)
1697 setup.fullscreen = FALSE;
1699 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1701 SetAudioMode(setup.sound);
1704 int GetElementFromGroupElement(int element)
1706 if (IS_GROUP_ELEMENT(element))
1708 struct ElementGroupInfo *group = element_info[element].group;
1709 int last_anim_random_frame = gfx.anim_random_frame;
1712 if (group->choice_mode == ANIM_RANDOM)
1713 gfx.anim_random_frame = RND(group->num_elements_resolved);
1715 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1716 group->choice_mode, 0,
1719 if (group->choice_mode == ANIM_RANDOM)
1720 gfx.anim_random_frame = last_anim_random_frame;
1722 group->choice_pos++;
1724 element = group->element_resolved[element_pos];
1730 static void IncrementSokobanFieldsNeeded(void)
1732 if (level.sb_fields_needed)
1733 game.sokoban_fields_still_needed++;
1736 static void IncrementSokobanObjectsNeeded(void)
1738 if (level.sb_objects_needed)
1739 game.sokoban_objects_still_needed++;
1742 static void DecrementSokobanFieldsNeeded(void)
1744 if (game.sokoban_fields_still_needed > 0)
1745 game.sokoban_fields_still_needed--;
1748 static void DecrementSokobanObjectsNeeded(void)
1750 if (game.sokoban_objects_still_needed > 0)
1751 game.sokoban_objects_still_needed--;
1754 static void InitPlayerField(int x, int y, int element, boolean init_game)
1756 if (element == EL_SP_MURPHY)
1760 if (stored_player[0].present)
1762 Tile[x][y] = EL_SP_MURPHY_CLONE;
1768 stored_player[0].initial_element = element;
1769 stored_player[0].use_murphy = TRUE;
1771 if (!level.use_artwork_element[0])
1772 stored_player[0].artwork_element = EL_SP_MURPHY;
1775 Tile[x][y] = EL_PLAYER_1;
1781 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1782 int jx = player->jx, jy = player->jy;
1784 player->present = TRUE;
1786 player->block_last_field = (element == EL_SP_MURPHY ?
1787 level.sp_block_last_field :
1788 level.block_last_field);
1790 // ---------- initialize player's last field block delay ------------------
1792 // always start with reliable default value (no adjustment needed)
1793 player->block_delay_adjustment = 0;
1795 // special case 1: in Supaplex, Murphy blocks last field one more frame
1796 if (player->block_last_field && element == EL_SP_MURPHY)
1797 player->block_delay_adjustment = 1;
1799 // special case 2: in game engines before 3.1.1, blocking was different
1800 if (game.use_block_last_field_bug)
1801 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1803 if (!network.enabled || player->connected_network)
1805 player->active = TRUE;
1807 // remove potentially duplicate players
1808 if (IN_LEV_FIELD(jx, jy) && StorePlayer[jx][jy] == Tile[x][y])
1809 StorePlayer[jx][jy] = 0;
1811 StorePlayer[x][y] = Tile[x][y];
1813 #if DEBUG_INIT_PLAYER
1814 Debug("game:init:player", "- player element %d activated",
1815 player->element_nr);
1816 Debug("game:init:player", " (local player is %d and currently %s)",
1817 local_player->element_nr,
1818 local_player->active ? "active" : "not active");
1822 Tile[x][y] = EL_EMPTY;
1824 player->jx = player->last_jx = x;
1825 player->jy = player->last_jy = y;
1828 // always check if player was just killed and should be reanimated
1830 int player_nr = GET_PLAYER_NR(element);
1831 struct PlayerInfo *player = &stored_player[player_nr];
1833 if (player->active && player->killed)
1834 player->reanimated = TRUE; // if player was just killed, reanimate him
1838 static void InitFieldForEngine_RND(int x, int y)
1840 int element = Tile[x][y];
1842 // convert BD engine elements to corresponding R'n'D engine elements
1843 element = (element == EL_BD_EMPTY ? EL_EMPTY :
1844 element == EL_BD_PLAYER ? EL_PLAYER_1 :
1845 element == EL_BD_INBOX ? EL_PLAYER_1 :
1846 element == EL_BD_SAND ? EL_SAND :
1847 element == EL_BD_STEELWALL ? EL_STEELWALL :
1848 element == EL_BD_EXIT_CLOSED ? EL_EXIT_CLOSED :
1849 element == EL_BD_EXIT_OPEN ? EL_EXIT_OPEN :
1852 Tile[x][y] = element;
1855 static void InitFieldForEngine(int x, int y)
1857 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
1858 InitFieldForEngine_RND(x, y);
1861 static void InitField(int x, int y, boolean init_game)
1863 int element = Tile[x][y];
1872 InitPlayerField(x, y, element, init_game);
1875 case EL_SOKOBAN_FIELD_PLAYER:
1876 element = Tile[x][y] = EL_PLAYER_1;
1877 InitField(x, y, init_game);
1879 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1880 InitField(x, y, init_game);
1883 case EL_SOKOBAN_FIELD_EMPTY:
1884 IncrementSokobanFieldsNeeded();
1887 case EL_SOKOBAN_OBJECT:
1888 IncrementSokobanObjectsNeeded();
1892 if (x < lev_fieldx - 1 && Tile[x + 1][y] == EL_ACID)
1893 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1894 else if (x > 0 && Tile[x - 1][y] == EL_ACID)
1895 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1896 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPLEFT)
1897 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1898 else if (y > 0 && Tile[x][y - 1] == EL_ACID)
1899 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1900 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPRIGHT)
1901 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1910 case EL_SPACESHIP_RIGHT:
1911 case EL_SPACESHIP_UP:
1912 case EL_SPACESHIP_LEFT:
1913 case EL_SPACESHIP_DOWN:
1914 case EL_BD_BUTTERFLY:
1915 case EL_BD_BUTTERFLY_RIGHT:
1916 case EL_BD_BUTTERFLY_UP:
1917 case EL_BD_BUTTERFLY_LEFT:
1918 case EL_BD_BUTTERFLY_DOWN:
1920 case EL_BD_FIREFLY_RIGHT:
1921 case EL_BD_FIREFLY_UP:
1922 case EL_BD_FIREFLY_LEFT:
1923 case EL_BD_FIREFLY_DOWN:
1924 case EL_PACMAN_RIGHT:
1926 case EL_PACMAN_LEFT:
1927 case EL_PACMAN_DOWN:
1929 case EL_YAMYAM_LEFT:
1930 case EL_YAMYAM_RIGHT:
1932 case EL_YAMYAM_DOWN:
1933 case EL_DARK_YAMYAM:
1936 case EL_SP_SNIKSNAK:
1937 case EL_SP_ELECTRON:
1943 case EL_SPRING_LEFT:
1944 case EL_SPRING_RIGHT:
1948 case EL_AMOEBA_FULL:
1953 case EL_AMOEBA_DROP:
1954 if (y == lev_fieldy - 1)
1956 Tile[x][y] = EL_AMOEBA_GROWING;
1957 Store[x][y] = EL_AMOEBA_WET;
1961 case EL_DYNAMITE_ACTIVE:
1962 case EL_SP_DISK_RED_ACTIVE:
1963 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1964 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1965 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1966 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1967 MovDelay[x][y] = 96;
1970 case EL_EM_DYNAMITE_ACTIVE:
1971 MovDelay[x][y] = 32;
1975 game.lights_still_needed++;
1979 game.friends_still_needed++;
1984 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1987 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1988 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1989 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1990 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1991 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1992 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1993 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1994 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1995 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1996 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1997 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1998 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
2001 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
2002 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
2003 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
2005 if (game.belt_dir_nr[belt_nr] == 3) // initial value
2007 game.belt_dir[belt_nr] = belt_dir;
2008 game.belt_dir_nr[belt_nr] = belt_dir_nr;
2010 else // more than one switch -- set it like the first switch
2012 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
2017 case EL_LIGHT_SWITCH_ACTIVE:
2019 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
2022 case EL_INVISIBLE_STEELWALL:
2023 case EL_INVISIBLE_WALL:
2024 case EL_INVISIBLE_SAND:
2025 if (game.light_time_left > 0 ||
2026 game.lenses_time_left > 0)
2027 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
2030 case EL_EMC_MAGIC_BALL:
2031 if (game.ball_active)
2032 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
2035 case EL_EMC_MAGIC_BALL_SWITCH:
2036 if (game.ball_active)
2037 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
2040 case EL_TRIGGER_PLAYER:
2041 case EL_TRIGGER_ELEMENT:
2042 case EL_TRIGGER_CE_VALUE:
2043 case EL_TRIGGER_CE_SCORE:
2045 case EL_ANY_ELEMENT:
2046 case EL_CURRENT_CE_VALUE:
2047 case EL_CURRENT_CE_SCORE:
2064 // reference elements should not be used on the playfield
2065 Tile[x][y] = EL_EMPTY;
2069 if (IS_CUSTOM_ELEMENT(element))
2071 if (CAN_MOVE(element))
2074 if (!element_info[element].use_last_ce_value || init_game)
2075 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2077 else if (IS_GROUP_ELEMENT(element))
2079 Tile[x][y] = GetElementFromGroupElement(element);
2081 InitField(x, y, init_game);
2083 else if (IS_EMPTY_ELEMENT(element))
2085 GfxElementEmpty[x][y] = element;
2086 Tile[x][y] = EL_EMPTY;
2088 if (element_info[element].use_gfx_element)
2089 game.use_masked_elements = TRUE;
2096 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2099 static void InitField_WithBug1(int x, int y, boolean init_game)
2101 InitField(x, y, init_game);
2103 // not needed to call InitMovDir() -- already done by InitField()!
2104 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2105 CAN_MOVE(Tile[x][y]))
2109 static void InitField_WithBug2(int x, int y, boolean init_game)
2111 int old_element = Tile[x][y];
2113 InitField(x, y, init_game);
2115 // not needed to call InitMovDir() -- already done by InitField()!
2116 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2117 CAN_MOVE(old_element) &&
2118 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2121 /* this case is in fact a combination of not less than three bugs:
2122 first, it calls InitMovDir() for elements that can move, although this is
2123 already done by InitField(); then, it checks the element that was at this
2124 field _before_ the call to InitField() (which can change it); lastly, it
2125 was not called for "mole with direction" elements, which were treated as
2126 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2130 static int get_key_element_from_nr(int key_nr)
2132 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2133 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2134 EL_EM_KEY_1 : EL_KEY_1);
2136 return key_base_element + key_nr;
2139 static int get_next_dropped_element(struct PlayerInfo *player)
2141 return (player->inventory_size > 0 ?
2142 player->inventory_element[player->inventory_size - 1] :
2143 player->inventory_infinite_element != EL_UNDEFINED ?
2144 player->inventory_infinite_element :
2145 player->dynabombs_left > 0 ?
2146 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2150 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2152 // pos >= 0: get element from bottom of the stack;
2153 // pos < 0: get element from top of the stack
2157 int min_inventory_size = -pos;
2158 int inventory_pos = player->inventory_size - min_inventory_size;
2159 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2161 return (player->inventory_size >= min_inventory_size ?
2162 player->inventory_element[inventory_pos] :
2163 player->inventory_infinite_element != EL_UNDEFINED ?
2164 player->inventory_infinite_element :
2165 player->dynabombs_left >= min_dynabombs_left ?
2166 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2171 int min_dynabombs_left = pos + 1;
2172 int min_inventory_size = pos + 1 - player->dynabombs_left;
2173 int inventory_pos = pos - player->dynabombs_left;
2175 return (player->inventory_infinite_element != EL_UNDEFINED ?
2176 player->inventory_infinite_element :
2177 player->dynabombs_left >= min_dynabombs_left ?
2178 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2179 player->inventory_size >= min_inventory_size ?
2180 player->inventory_element[inventory_pos] :
2185 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2187 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2188 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2191 if (gpo1->sort_priority != gpo2->sort_priority)
2192 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2194 compare_result = gpo1->nr - gpo2->nr;
2196 return compare_result;
2199 int getPlayerInventorySize(int player_nr)
2201 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2202 return game_em.ply[player_nr]->dynamite;
2203 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2204 return game_sp.red_disk_count;
2206 return stored_player[player_nr].inventory_size;
2209 static void InitGameControlValues(void)
2213 for (i = 0; game_panel_controls[i].nr != -1; i++)
2215 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2216 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2217 struct TextPosInfo *pos = gpc->pos;
2219 int type = gpc->type;
2223 Error("'game_panel_controls' structure corrupted at %d", i);
2225 Fail("this should not happen -- please debug");
2228 // force update of game controls after initialization
2229 gpc->value = gpc->last_value = -1;
2230 gpc->frame = gpc->last_frame = -1;
2231 gpc->gfx_frame = -1;
2233 // determine panel value width for later calculation of alignment
2234 if (type == TYPE_INTEGER || type == TYPE_STRING)
2236 pos->width = pos->size * getFontWidth(pos->font);
2237 pos->height = getFontHeight(pos->font);
2239 else if (type == TYPE_ELEMENT)
2241 pos->width = pos->size;
2242 pos->height = pos->size;
2245 // fill structure for game panel draw order
2247 gpo->sort_priority = pos->sort_priority;
2250 // sort game panel controls according to sort_priority and control number
2251 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2252 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2255 static void UpdatePlayfieldElementCount(void)
2257 boolean use_element_count = FALSE;
2260 // first check if it is needed at all to calculate playfield element count
2261 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2262 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2263 use_element_count = TRUE;
2265 if (!use_element_count)
2268 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2269 element_info[i].element_count = 0;
2271 SCAN_PLAYFIELD(x, y)
2273 element_info[Tile[x][y]].element_count++;
2276 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2277 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2278 if (IS_IN_GROUP(j, i))
2279 element_info[EL_GROUP_START + i].element_count +=
2280 element_info[j].element_count;
2283 static void UpdateGameControlValues(void)
2286 int time = (game.LevelSolved ?
2287 game.LevelSolved_CountingTime :
2288 level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2289 game_bd.time_played :
2290 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2292 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2293 game_sp.time_played :
2294 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2295 game_mm.energy_left :
2296 game.no_level_time_limit ? TimePlayed : TimeLeft);
2297 int score = (game.LevelSolved ?
2298 game.LevelSolved_CountingScore :
2299 level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2301 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2302 game_em.lev->score :
2303 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2305 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2308 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2309 game_bd.gems_still_needed :
2310 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2311 game_em.lev->gems_needed :
2312 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2313 game_sp.infotrons_still_needed :
2314 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2315 game_mm.kettles_still_needed :
2316 game.gems_still_needed);
2317 int gems_needed = level.gems_needed;
2318 int gems_collected = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2319 game_bd.game->cave->diamonds_collected :
2320 gems_needed - gems);
2321 int gems_score = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2322 game_bd.game->cave->diamond_value :
2323 level.score[SC_EMERALD]);
2324 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2325 game_bd.gems_still_needed > 0 :
2326 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2327 game_em.lev->gems_needed > 0 :
2328 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2329 game_sp.infotrons_still_needed > 0 :
2330 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2331 game_mm.kettles_still_needed > 0 ||
2332 game_mm.lights_still_needed > 0 :
2333 game.gems_still_needed > 0 ||
2334 game.sokoban_fields_still_needed > 0 ||
2335 game.sokoban_objects_still_needed > 0 ||
2336 game.lights_still_needed > 0);
2337 int health = (game.LevelSolved ?
2338 game.LevelSolved_CountingHealth :
2339 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2340 MM_HEALTH(game_mm.laser_overload_value) :
2342 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2344 UpdatePlayfieldElementCount();
2346 // update game panel control values
2348 // used instead of "level_nr" (for network games)
2349 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2350 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2351 game_panel_controls[GAME_PANEL_GEMS_NEEDED].value = gems_needed;
2352 game_panel_controls[GAME_PANEL_GEMS_COLLECTED].value = gems_collected;
2353 game_panel_controls[GAME_PANEL_GEMS_SCORE].value = gems_score;
2355 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2356 for (i = 0; i < MAX_NUM_KEYS; i++)
2357 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2358 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2359 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2361 if (game.centered_player_nr == -1)
2363 for (i = 0; i < MAX_PLAYERS; i++)
2365 // only one player in Supaplex game engine
2366 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2369 for (k = 0; k < MAX_NUM_KEYS; k++)
2371 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2373 if (game_em.ply[i]->keys & (1 << k))
2374 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2375 get_key_element_from_nr(k);
2377 else if (stored_player[i].key[k])
2378 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2379 get_key_element_from_nr(k);
2382 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2383 getPlayerInventorySize(i);
2385 if (stored_player[i].num_white_keys > 0)
2386 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2389 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2390 stored_player[i].num_white_keys;
2395 int player_nr = game.centered_player_nr;
2397 for (k = 0; k < MAX_NUM_KEYS; k++)
2399 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2401 if (game_em.ply[player_nr]->keys & (1 << k))
2402 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2403 get_key_element_from_nr(k);
2405 else if (stored_player[player_nr].key[k])
2406 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2407 get_key_element_from_nr(k);
2410 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2411 getPlayerInventorySize(player_nr);
2413 if (stored_player[player_nr].num_white_keys > 0)
2414 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2416 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2417 stored_player[player_nr].num_white_keys;
2420 // re-arrange keys on game panel, if needed or if defined by style settings
2421 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2423 int nr = GAME_PANEL_KEY_1 + i;
2424 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2425 struct TextPosInfo *pos = gpc->pos;
2427 // skip check if key is not in the player's inventory
2428 if (gpc->value == EL_EMPTY)
2431 // check if keys should be arranged on panel from left to right
2432 if (pos->style == STYLE_LEFTMOST_POSITION)
2434 // check previous key positions (left from current key)
2435 for (k = 0; k < i; k++)
2437 int nr_new = GAME_PANEL_KEY_1 + k;
2439 if (game_panel_controls[nr_new].value == EL_EMPTY)
2441 game_panel_controls[nr_new].value = gpc->value;
2442 gpc->value = EL_EMPTY;
2449 // check if "undefined" keys can be placed at some other position
2450 if (pos->x == -1 && pos->y == -1)
2452 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2454 // 1st try: display key at the same position as normal or EM keys
2455 if (game_panel_controls[nr_new].value == EL_EMPTY)
2457 game_panel_controls[nr_new].value = gpc->value;
2461 // 2nd try: display key at the next free position in the key panel
2462 for (k = 0; k < STD_NUM_KEYS; k++)
2464 nr_new = GAME_PANEL_KEY_1 + k;
2466 if (game_panel_controls[nr_new].value == EL_EMPTY)
2468 game_panel_controls[nr_new].value = gpc->value;
2477 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2479 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2480 get_inventory_element_from_pos(local_player, i);
2481 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2482 get_inventory_element_from_pos(local_player, -i - 1);
2485 game_panel_controls[GAME_PANEL_SCORE].value = score;
2486 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2488 game_panel_controls[GAME_PANEL_TIME].value = time;
2490 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2491 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2492 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2494 if (level.time == 0)
2495 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2497 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2499 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2500 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2502 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2504 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2505 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2507 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2508 local_player->shield_normal_time_left;
2509 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2510 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2512 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2513 local_player->shield_deadly_time_left;
2515 game_panel_controls[GAME_PANEL_EXIT].value =
2516 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2518 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2519 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2520 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2521 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2522 EL_EMC_MAGIC_BALL_SWITCH);
2524 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2525 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2526 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2527 game.light_time_left;
2529 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2530 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2531 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2532 game.timegate_time_left;
2534 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2535 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2537 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2538 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2539 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2540 game.lenses_time_left;
2542 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2543 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2544 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2545 game.magnify_time_left;
2547 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2548 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2549 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2550 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2551 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2552 EL_BALLOON_SWITCH_NONE);
2554 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2555 local_player->dynabomb_count;
2556 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2557 local_player->dynabomb_size;
2558 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2559 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2561 game_panel_controls[GAME_PANEL_PENGUINS].value =
2562 game.friends_still_needed;
2564 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2565 game.sokoban_objects_still_needed;
2566 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2567 game.sokoban_fields_still_needed;
2569 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2570 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2572 for (i = 0; i < NUM_BELTS; i++)
2574 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2575 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2576 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2577 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2578 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2581 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2582 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2583 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2584 game.magic_wall_time_left;
2586 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2587 local_player->gravity;
2589 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2590 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2592 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2593 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2594 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2595 game.panel.element[i].id : EL_UNDEFINED);
2597 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2598 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2599 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2600 element_info[game.panel.element_count[i].id].element_count : 0);
2602 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2603 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2604 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2605 element_info[game.panel.ce_score[i].id].collect_score : 0);
2607 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2608 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2609 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2610 element_info[game.panel.ce_score_element[i].id].collect_score :
2613 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2614 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2615 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2617 // update game panel control frames
2619 for (i = 0; game_panel_controls[i].nr != -1; i++)
2621 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2623 if (gpc->type == TYPE_ELEMENT)
2625 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2627 int last_anim_random_frame = gfx.anim_random_frame;
2628 int element = gpc->value;
2629 int graphic = el2panelimg(element);
2630 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2632 graphic_info[graphic].anim_global_anim_sync ?
2633 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2635 if (gpc->value != gpc->last_value)
2638 gpc->gfx_random = init_gfx_random;
2644 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2645 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2646 gpc->gfx_random = init_gfx_random;
2649 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2650 gfx.anim_random_frame = gpc->gfx_random;
2652 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2653 gpc->gfx_frame = element_info[element].collect_score;
2655 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2657 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2658 gfx.anim_random_frame = last_anim_random_frame;
2661 else if (gpc->type == TYPE_GRAPHIC)
2663 if (gpc->graphic != IMG_UNDEFINED)
2665 int last_anim_random_frame = gfx.anim_random_frame;
2666 int graphic = gpc->graphic;
2667 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2669 graphic_info[graphic].anim_global_anim_sync ?
2670 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2672 if (gpc->value != gpc->last_value)
2675 gpc->gfx_random = init_gfx_random;
2681 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2682 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2683 gpc->gfx_random = init_gfx_random;
2686 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2687 gfx.anim_random_frame = gpc->gfx_random;
2689 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2691 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2692 gfx.anim_random_frame = last_anim_random_frame;
2698 static void DisplayGameControlValues(void)
2700 boolean redraw_panel = FALSE;
2703 for (i = 0; game_panel_controls[i].nr != -1; i++)
2705 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2707 if (PANEL_DEACTIVATED(gpc->pos))
2710 if (gpc->value == gpc->last_value &&
2711 gpc->frame == gpc->last_frame)
2714 redraw_panel = TRUE;
2720 // copy default game door content to main double buffer
2722 // !!! CHECK AGAIN !!!
2723 SetPanelBackground();
2724 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2725 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2727 // redraw game control buttons
2728 RedrawGameButtons();
2730 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2732 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2734 int nr = game_panel_order[i].nr;
2735 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2736 struct TextPosInfo *pos = gpc->pos;
2737 int type = gpc->type;
2738 int value = gpc->value;
2739 int frame = gpc->frame;
2740 int size = pos->size;
2741 int font = pos->font;
2742 boolean draw_masked = pos->draw_masked;
2743 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2745 if (PANEL_DEACTIVATED(pos))
2748 if (pos->class == get_hash_from_string("extra_panel_items") &&
2749 !setup.prefer_extra_panel_items)
2752 gpc->last_value = value;
2753 gpc->last_frame = frame;
2755 if (type == TYPE_INTEGER)
2757 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2758 nr == GAME_PANEL_INVENTORY_COUNT ||
2759 nr == GAME_PANEL_SCORE ||
2760 nr == GAME_PANEL_HIGHSCORE ||
2761 nr == GAME_PANEL_TIME)
2763 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2765 if (use_dynamic_size) // use dynamic number of digits
2767 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 :
2768 nr == GAME_PANEL_INVENTORY_COUNT ||
2769 nr == GAME_PANEL_TIME ? 1000 : 100000);
2770 int size_add = (nr == GAME_PANEL_LEVEL_NUMBER ||
2771 nr == GAME_PANEL_INVENTORY_COUNT ||
2772 nr == GAME_PANEL_TIME ? 1 : 2);
2773 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 :
2774 nr == GAME_PANEL_INVENTORY_COUNT ||
2775 nr == GAME_PANEL_TIME ? 3 : 5);
2776 int size2 = size1 + size_add;
2777 int font1 = pos->font;
2778 int font2 = pos->font_alt;
2780 size = (value < value_change ? size1 : size2);
2781 font = (value < value_change ? font1 : font2);
2785 // correct text size if "digits" is zero or less
2787 size = strlen(int2str(value, size));
2789 // dynamically correct text alignment
2790 pos->width = size * getFontWidth(font);
2792 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2793 int2str(value, size), font, mask_mode);
2795 else if (type == TYPE_ELEMENT)
2797 int element, graphic;
2801 int dst_x = PANEL_XPOS(pos);
2802 int dst_y = PANEL_YPOS(pos);
2804 if (value != EL_UNDEFINED && value != EL_EMPTY)
2807 graphic = el2panelimg(value);
2810 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2811 element, EL_NAME(element), size);
2814 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2817 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2820 width = graphic_info[graphic].width * size / TILESIZE;
2821 height = graphic_info[graphic].height * size / TILESIZE;
2824 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2827 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2831 else if (type == TYPE_GRAPHIC)
2833 int graphic = gpc->graphic;
2834 int graphic_active = gpc->graphic_active;
2838 int dst_x = PANEL_XPOS(pos);
2839 int dst_y = PANEL_YPOS(pos);
2840 boolean skip = (pos->class == get_hash_from_string("mm_engine_only") &&
2841 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2843 if (graphic != IMG_UNDEFINED && !skip)
2845 if (pos->style == STYLE_REVERSE)
2846 value = 100 - value;
2848 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2850 if (pos->direction & MV_HORIZONTAL)
2852 width = graphic_info[graphic_active].width * value / 100;
2853 height = graphic_info[graphic_active].height;
2855 if (pos->direction == MV_LEFT)
2857 src_x += graphic_info[graphic_active].width - width;
2858 dst_x += graphic_info[graphic_active].width - width;
2863 width = graphic_info[graphic_active].width;
2864 height = graphic_info[graphic_active].height * value / 100;
2866 if (pos->direction == MV_UP)
2868 src_y += graphic_info[graphic_active].height - height;
2869 dst_y += graphic_info[graphic_active].height - height;
2874 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2877 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2880 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2882 if (pos->direction & MV_HORIZONTAL)
2884 if (pos->direction == MV_RIGHT)
2891 dst_x = PANEL_XPOS(pos);
2894 width = graphic_info[graphic].width - width;
2898 if (pos->direction == MV_DOWN)
2905 dst_y = PANEL_YPOS(pos);
2908 height = graphic_info[graphic].height - height;
2912 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2915 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2919 else if (type == TYPE_STRING)
2921 boolean active = (value != 0);
2922 char *state_normal = "off";
2923 char *state_active = "on";
2924 char *state = (active ? state_active : state_normal);
2925 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2926 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2927 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2928 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2930 if (nr == GAME_PANEL_GRAVITY_STATE)
2932 int font1 = pos->font; // (used for normal state)
2933 int font2 = pos->font_alt; // (used for active state)
2935 font = (active ? font2 : font1);
2944 // don't truncate output if "chars" is zero or less
2947 // dynamically correct text alignment
2948 pos->width = size * getFontWidth(font);
2951 s_cut = getStringCopyN(s, size);
2953 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2954 s_cut, font, mask_mode);
2960 redraw_mask |= REDRAW_DOOR_1;
2963 SetGameStatus(GAME_MODE_PLAYING);
2966 void UpdateAndDisplayGameControlValues(void)
2968 if (tape.deactivate_display)
2971 UpdateGameControlValues();
2972 DisplayGameControlValues();
2975 void UpdateGameDoorValues(void)
2977 UpdateGameControlValues();
2980 void DrawGameDoorValues(void)
2982 DisplayGameControlValues();
2986 // ============================================================================
2988 // ----------------------------------------------------------------------------
2989 // initialize game engine due to level / tape version number
2990 // ============================================================================
2992 static void InitGameEngine(void)
2994 int i, j, k, l, x, y;
2996 // set game engine from tape file when re-playing, else from level file
2997 game.engine_version = (tape.playing ? tape.engine_version :
2998 level.game_version);
3000 // set single or multi-player game mode (needed for re-playing tapes)
3001 game.team_mode = setup.team_mode;
3005 int num_players = 0;
3007 for (i = 0; i < MAX_PLAYERS; i++)
3008 if (tape.player_participates[i])
3011 // multi-player tapes contain input data for more than one player
3012 game.team_mode = (num_players > 1);
3016 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
3017 level.game_version);
3018 Debug("game:init:level", " tape.file_version == %06d",
3020 Debug("game:init:level", " tape.game_version == %06d",
3022 Debug("game:init:level", " tape.engine_version == %06d",
3023 tape.engine_version);
3024 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
3025 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
3028 // --------------------------------------------------------------------------
3029 // set flags for bugs and changes according to active game engine version
3030 // --------------------------------------------------------------------------
3034 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
3036 Bug was introduced in version:
3039 Bug was fixed in version:
3043 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
3044 but the property "can fall" was missing, which caused some levels to be
3045 unsolvable. This was fixed in version 4.2.0.0.
3047 Affected levels/tapes:
3048 An example for a tape that was fixed by this bugfix is tape 029 from the
3049 level set "rnd_sam_bateman".
3050 The wrong behaviour will still be used for all levels or tapes that were
3051 created/recorded with it. An example for this is tape 023 from the level
3052 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
3055 boolean use_amoeba_dropping_cannot_fall_bug =
3056 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
3057 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
3059 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3060 tape.game_version < VERSION_IDENT(4,2,0,0)));
3063 Summary of bugfix/change:
3064 Fixed move speed of elements entering or leaving magic wall.
3066 Fixed/changed in version:
3070 Before 2.0.1, move speed of elements entering or leaving magic wall was
3071 twice as fast as it is now.
3072 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
3074 Affected levels/tapes:
3075 The first condition is generally needed for all levels/tapes before version
3076 2.0.1, which might use the old behaviour before it was changed; known tapes
3077 that are affected: Tape 014 from the level set "rnd_conor_mancone".
3078 The second condition is an exception from the above case and is needed for
3079 the special case of tapes recorded with game (not engine!) version 2.0.1 or
3080 above, but before it was known that this change would break tapes like the
3081 above and was fixed in 4.2.0.0, so that the changed behaviour was active
3082 although the engine version while recording maybe was before 2.0.1. There
3083 are a lot of tapes that are affected by this exception, like tape 006 from
3084 the level set "rnd_conor_mancone".
3087 boolean use_old_move_stepsize_for_magic_wall =
3088 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
3090 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3091 tape.game_version < VERSION_IDENT(4,2,0,0)));
3094 Summary of bugfix/change:
3095 Fixed handling for custom elements that change when pushed by the player.
3097 Fixed/changed in version:
3101 Before 3.1.0, custom elements that "change when pushing" changed directly
3102 after the player started pushing them (until then handled in "DigField()").
3103 Since 3.1.0, these custom elements are not changed until the "pushing"
3104 move of the element is finished (now handled in "ContinueMoving()").
3106 Affected levels/tapes:
3107 The first condition is generally needed for all levels/tapes before version
3108 3.1.0, which might use the old behaviour before it was changed; known tapes
3109 that are affected are some tapes from the level set "Walpurgis Gardens" by
3111 The second condition is an exception from the above case and is needed for
3112 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3113 above (including some development versions of 3.1.0), but before it was
3114 known that this change would break tapes like the above and was fixed in
3115 3.1.1, so that the changed behaviour was active although the engine version
3116 while recording maybe was before 3.1.0. There is at least one tape that is
3117 affected by this exception, which is the tape for the one-level set "Bug
3118 Machine" by Juergen Bonhagen.
3121 game.use_change_when_pushing_bug =
3122 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3124 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3125 tape.game_version < VERSION_IDENT(3,1,1,0)));
3128 Summary of bugfix/change:
3129 Fixed handling for blocking the field the player leaves when moving.
3131 Fixed/changed in version:
3135 Before 3.1.1, when "block last field when moving" was enabled, the field
3136 the player is leaving when moving was blocked for the time of the move,
3137 and was directly unblocked afterwards. This resulted in the last field
3138 being blocked for exactly one less than the number of frames of one player
3139 move. Additionally, even when blocking was disabled, the last field was
3140 blocked for exactly one frame.
3141 Since 3.1.1, due to changes in player movement handling, the last field
3142 is not blocked at all when blocking is disabled. When blocking is enabled,
3143 the last field is blocked for exactly the number of frames of one player
3144 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3145 last field is blocked for exactly one more than the number of frames of
3148 Affected levels/tapes:
3149 (!!! yet to be determined -- probably many !!!)
3152 game.use_block_last_field_bug =
3153 (game.engine_version < VERSION_IDENT(3,1,1,0));
3155 /* various special flags and settings for native Emerald Mine game engine */
3157 game_em.use_single_button =
3158 (game.engine_version > VERSION_IDENT(4,0,0,2));
3160 game_em.use_push_delay =
3161 (game.engine_version > VERSION_IDENT(4,3,7,1));
3163 game_em.use_snap_key_bug =
3164 (game.engine_version < VERSION_IDENT(4,0,1,0));
3166 game_em.use_random_bug =
3167 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3169 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3171 game_em.use_old_explosions = use_old_em_engine;
3172 game_em.use_old_android = use_old_em_engine;
3173 game_em.use_old_push_elements = use_old_em_engine;
3174 game_em.use_old_push_into_acid = use_old_em_engine;
3176 game_em.use_wrap_around = !use_old_em_engine;
3178 // --------------------------------------------------------------------------
3180 // set maximal allowed number of custom element changes per game frame
3181 game.max_num_changes_per_frame = 1;
3183 // default scan direction: scan playfield from top/left to bottom/right
3184 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3186 // dynamically adjust element properties according to game engine version
3187 InitElementPropertiesEngine(game.engine_version);
3189 // ---------- initialize special element properties -------------------------
3191 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3192 if (use_amoeba_dropping_cannot_fall_bug)
3193 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3195 // ---------- initialize player's initial move delay ------------------------
3197 // dynamically adjust player properties according to level information
3198 for (i = 0; i < MAX_PLAYERS; i++)
3199 game.initial_move_delay_value[i] =
3200 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3202 // dynamically adjust player properties according to game engine version
3203 for (i = 0; i < MAX_PLAYERS; i++)
3204 game.initial_move_delay[i] =
3205 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3206 game.initial_move_delay_value[i] : 0);
3208 // ---------- initialize player's initial push delay ------------------------
3210 // dynamically adjust player properties according to game engine version
3211 game.initial_push_delay_value =
3212 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3214 // ---------- initialize changing elements ----------------------------------
3216 // initialize changing elements information
3217 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3219 struct ElementInfo *ei = &element_info[i];
3221 // this pointer might have been changed in the level editor
3222 ei->change = &ei->change_page[0];
3224 if (!IS_CUSTOM_ELEMENT(i))
3226 ei->change->target_element = EL_EMPTY_SPACE;
3227 ei->change->delay_fixed = 0;
3228 ei->change->delay_random = 0;
3229 ei->change->delay_frames = 1;
3232 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3234 ei->has_change_event[j] = FALSE;
3236 ei->event_page_nr[j] = 0;
3237 ei->event_page[j] = &ei->change_page[0];
3241 // add changing elements from pre-defined list
3242 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3244 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3245 struct ElementInfo *ei = &element_info[ch_delay->element];
3247 ei->change->target_element = ch_delay->target_element;
3248 ei->change->delay_fixed = ch_delay->change_delay;
3250 ei->change->pre_change_function = ch_delay->pre_change_function;
3251 ei->change->change_function = ch_delay->change_function;
3252 ei->change->post_change_function = ch_delay->post_change_function;
3254 ei->change->can_change = TRUE;
3255 ei->change->can_change_or_has_action = TRUE;
3257 ei->has_change_event[CE_DELAY] = TRUE;
3259 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3260 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3263 // ---------- initialize if element can trigger global animations -----------
3265 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3267 struct ElementInfo *ei = &element_info[i];
3269 ei->has_anim_event = FALSE;
3272 InitGlobalAnimEventsForCustomElements();
3274 // ---------- initialize internal run-time variables ------------------------
3276 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3278 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3280 for (j = 0; j < ei->num_change_pages; j++)
3282 ei->change_page[j].can_change_or_has_action =
3283 (ei->change_page[j].can_change |
3284 ei->change_page[j].has_action);
3288 // add change events from custom element configuration
3289 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3291 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3293 for (j = 0; j < ei->num_change_pages; j++)
3295 if (!ei->change_page[j].can_change_or_has_action)
3298 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3300 // only add event page for the first page found with this event
3301 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3303 ei->has_change_event[k] = TRUE;
3305 ei->event_page_nr[k] = j;
3306 ei->event_page[k] = &ei->change_page[j];
3312 // ---------- initialize reference elements in change conditions ------------
3314 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3316 int element = EL_CUSTOM_START + i;
3317 struct ElementInfo *ei = &element_info[element];
3319 for (j = 0; j < ei->num_change_pages; j++)
3321 int trigger_element = ei->change_page[j].initial_trigger_element;
3323 if (trigger_element >= EL_PREV_CE_8 &&
3324 trigger_element <= EL_NEXT_CE_8)
3325 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3327 ei->change_page[j].trigger_element = trigger_element;
3331 // ---------- initialize run-time trigger player and element ----------------
3333 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3335 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3337 for (j = 0; j < ei->num_change_pages; j++)
3339 struct ElementChangeInfo *change = &ei->change_page[j];
3341 change->actual_trigger_element = EL_EMPTY;
3342 change->actual_trigger_player = EL_EMPTY;
3343 change->actual_trigger_player_bits = CH_PLAYER_NONE;
3344 change->actual_trigger_side = CH_SIDE_NONE;
3345 change->actual_trigger_ce_value = 0;
3346 change->actual_trigger_ce_score = 0;
3347 change->actual_trigger_x = -1;
3348 change->actual_trigger_y = -1;
3352 // ---------- initialize trigger events -------------------------------------
3354 // initialize trigger events information
3355 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3356 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3357 trigger_events[i][j] = FALSE;
3359 // add trigger events from element change event properties
3360 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3362 struct ElementInfo *ei = &element_info[i];
3364 for (j = 0; j < ei->num_change_pages; j++)
3366 struct ElementChangeInfo *change = &ei->change_page[j];
3368 if (!change->can_change_or_has_action)
3371 if (change->has_event[CE_BY_OTHER_ACTION])
3373 int trigger_element = change->trigger_element;
3375 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3377 if (change->has_event[k])
3379 if (IS_GROUP_ELEMENT(trigger_element))
3381 struct ElementGroupInfo *group =
3382 element_info[trigger_element].group;
3384 for (l = 0; l < group->num_elements_resolved; l++)
3385 trigger_events[group->element_resolved[l]][k] = TRUE;
3387 else if (trigger_element == EL_ANY_ELEMENT)
3388 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3389 trigger_events[l][k] = TRUE;
3391 trigger_events[trigger_element][k] = TRUE;
3398 // ---------- initialize push delay -----------------------------------------
3400 // initialize push delay values to default
3401 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3403 if (!IS_CUSTOM_ELEMENT(i))
3405 // set default push delay values (corrected since version 3.0.7-1)
3406 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3408 element_info[i].push_delay_fixed = 2;
3409 element_info[i].push_delay_random = 8;
3413 element_info[i].push_delay_fixed = 8;
3414 element_info[i].push_delay_random = 8;
3419 // set push delay value for certain elements from pre-defined list
3420 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3422 int e = push_delay_list[i].element;
3424 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3425 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3428 // set push delay value for Supaplex elements for newer engine versions
3429 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3431 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3433 if (IS_SP_ELEMENT(i))
3435 // set SP push delay to just enough to push under a falling zonk
3436 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3438 element_info[i].push_delay_fixed = delay;
3439 element_info[i].push_delay_random = 0;
3444 // ---------- initialize move stepsize --------------------------------------
3446 // initialize move stepsize values to default
3447 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3448 if (!IS_CUSTOM_ELEMENT(i))
3449 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3451 // set move stepsize value for certain elements from pre-defined list
3452 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3454 int e = move_stepsize_list[i].element;
3456 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3458 // set move stepsize value for certain elements for older engine versions
3459 if (use_old_move_stepsize_for_magic_wall)
3461 if (e == EL_MAGIC_WALL_FILLING ||
3462 e == EL_MAGIC_WALL_EMPTYING ||
3463 e == EL_BD_MAGIC_WALL_FILLING ||
3464 e == EL_BD_MAGIC_WALL_EMPTYING)
3465 element_info[e].move_stepsize *= 2;
3469 // ---------- initialize collect score --------------------------------------
3471 // initialize collect score values for custom elements from initial value
3472 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3473 if (IS_CUSTOM_ELEMENT(i))
3474 element_info[i].collect_score = element_info[i].collect_score_initial;
3476 // ---------- initialize collect count --------------------------------------
3478 // initialize collect count values for non-custom elements
3479 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3480 if (!IS_CUSTOM_ELEMENT(i))
3481 element_info[i].collect_count_initial = 0;
3483 // add collect count values for all elements from pre-defined list
3484 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3485 element_info[collect_count_list[i].element].collect_count_initial =
3486 collect_count_list[i].count;
3488 // ---------- initialize access direction -----------------------------------
3490 // initialize access direction values to default (access from every side)
3491 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3492 if (!IS_CUSTOM_ELEMENT(i))
3493 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3495 // set access direction value for certain elements from pre-defined list
3496 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3497 element_info[access_direction_list[i].element].access_direction =
3498 access_direction_list[i].direction;
3500 // ---------- initialize explosion content ----------------------------------
3501 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3503 if (IS_CUSTOM_ELEMENT(i))
3506 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3508 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3510 element_info[i].content.e[x][y] =
3511 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3512 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3513 i == EL_PLAYER_3 ? EL_EMERALD :
3514 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3515 i == EL_MOLE ? EL_EMERALD_RED :
3516 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3517 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3518 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3519 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3520 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3521 i == EL_WALL_EMERALD ? EL_EMERALD :
3522 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3523 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3524 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3525 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3526 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3527 i == EL_WALL_PEARL ? EL_PEARL :
3528 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3533 // ---------- initialize recursion detection --------------------------------
3534 recursion_loop_depth = 0;
3535 recursion_loop_detected = FALSE;
3536 recursion_loop_element = EL_UNDEFINED;
3538 // ---------- initialize graphics engine ------------------------------------
3539 game.scroll_delay_value =
3540 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3541 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3542 !setup.forced_scroll_delay ? 0 :
3543 setup.scroll_delay ? setup.scroll_delay_value : 0);
3544 if (game.forced_scroll_delay_value == -1)
3545 game.scroll_delay_value =
3546 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3548 // ---------- initialize game engine snapshots ------------------------------
3549 for (i = 0; i < MAX_PLAYERS; i++)
3550 game.snapshot.last_action[i] = 0;
3551 game.snapshot.changed_action = FALSE;
3552 game.snapshot.collected_item = FALSE;
3553 game.snapshot.mode =
3554 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3555 SNAPSHOT_MODE_EVERY_STEP :
3556 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3557 SNAPSHOT_MODE_EVERY_MOVE :
3558 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3559 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3560 game.snapshot.save_snapshot = FALSE;
3562 // ---------- initialize level time for Supaplex engine ---------------------
3563 // Supaplex levels with time limit currently unsupported -- should be added
3564 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3567 // ---------- initialize flags for handling game actions --------------------
3569 // set flags for game actions to default values
3570 game.use_key_actions = TRUE;
3571 game.use_mouse_actions = FALSE;
3573 // when using Mirror Magic game engine, handle mouse events only
3574 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3576 game.use_key_actions = FALSE;
3577 game.use_mouse_actions = TRUE;
3580 // check for custom elements with mouse click events
3581 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3583 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3585 int element = EL_CUSTOM_START + i;
3587 if (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3588 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3589 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3590 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3591 game.use_mouse_actions = TRUE;
3596 static int get_num_special_action(int element, int action_first,
3599 int num_special_action = 0;
3602 for (i = action_first; i <= action_last; i++)
3604 boolean found = FALSE;
3606 for (j = 0; j < NUM_DIRECTIONS; j++)
3607 if (el_act_dir2img(element, i, j) !=
3608 el_act_dir2img(element, ACTION_DEFAULT, j))
3612 num_special_action++;
3617 return num_special_action;
3621 // ============================================================================
3623 // ----------------------------------------------------------------------------
3624 // initialize and start new game
3625 // ============================================================================
3627 #if DEBUG_INIT_PLAYER
3628 static void DebugPrintPlayerStatus(char *message)
3635 Debug("game:init:player", "%s:", message);
3637 for (i = 0; i < MAX_PLAYERS; i++)
3639 struct PlayerInfo *player = &stored_player[i];
3641 Debug("game:init:player",
3642 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3646 player->connected_locally,
3647 player->connected_network,
3649 (local_player == player ? " (local player)" : ""));
3656 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3657 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3658 int fade_mask = REDRAW_FIELD;
3659 boolean restarting = (game_status == GAME_MODE_PLAYING);
3660 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3661 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3662 int initial_move_dir = MV_DOWN;
3665 // required here to update video display before fading (FIX THIS)
3666 DrawMaskedBorder(REDRAW_DOOR_2);
3668 if (!game.restart_level)
3669 CloseDoor(DOOR_CLOSE_1);
3673 // force fading out global animations displayed during game play
3674 SetGameStatus(GAME_MODE_PSEUDO_RESTARTING);
3678 SetGameStatus(GAME_MODE_PLAYING);
3681 if (level_editor_test_game)
3682 FadeSkipNextFadeOut();
3684 FadeSetEnterScreen();
3687 fade_mask = REDRAW_ALL;
3689 FadeLevelSoundsAndMusic();
3691 ExpireSoundLoops(TRUE);
3697 // force restarting global animations displayed during game play
3698 RestartGlobalAnimsByStatus(GAME_MODE_PSEUDO_RESTARTING);
3700 // this is required for "transforming" fade modes like cross-fading
3701 // (else global animations will be stopped, but not restarted here)
3702 SetAnimStatusBeforeFading(GAME_MODE_PSEUDO_RESTARTING);
3704 SetGameStatus(GAME_MODE_PLAYING);
3707 if (level_editor_test_game)
3708 FadeSkipNextFadeIn();
3710 // needed if different viewport properties defined for playing
3711 ChangeViewportPropertiesIfNeeded();
3715 DrawCompleteVideoDisplay();
3717 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3720 InitGameControlValues();
3724 // initialize tape actions from game when recording tape
3725 tape.use_key_actions = game.use_key_actions;
3726 tape.use_mouse_actions = game.use_mouse_actions;
3728 // initialize visible playfield size when recording tape (for team mode)
3729 tape.scr_fieldx = SCR_FIELDX;
3730 tape.scr_fieldy = SCR_FIELDY;
3733 // don't play tapes over network
3734 network_playing = (network.enabled && !tape.playing);
3736 for (i = 0; i < MAX_PLAYERS; i++)
3738 struct PlayerInfo *player = &stored_player[i];
3740 player->index_nr = i;
3741 player->index_bit = (1 << i);
3742 player->element_nr = EL_PLAYER_1 + i;
3744 player->present = FALSE;
3745 player->active = FALSE;
3746 player->mapped = FALSE;
3748 player->killed = FALSE;
3749 player->reanimated = FALSE;
3750 player->buried = FALSE;
3753 player->effective_action = 0;
3754 player->programmed_action = 0;
3755 player->snap_action = 0;
3757 player->mouse_action.lx = 0;
3758 player->mouse_action.ly = 0;
3759 player->mouse_action.button = 0;
3760 player->mouse_action.button_hint = 0;
3762 player->effective_mouse_action.lx = 0;
3763 player->effective_mouse_action.ly = 0;
3764 player->effective_mouse_action.button = 0;
3765 player->effective_mouse_action.button_hint = 0;
3767 for (j = 0; j < MAX_NUM_KEYS; j++)
3768 player->key[j] = FALSE;
3770 player->num_white_keys = 0;
3772 player->dynabomb_count = 0;
3773 player->dynabomb_size = 1;
3774 player->dynabombs_left = 0;
3775 player->dynabomb_xl = FALSE;
3777 player->MovDir = initial_move_dir;
3780 player->GfxDir = initial_move_dir;
3781 player->GfxAction = ACTION_DEFAULT;
3783 player->StepFrame = 0;
3785 player->initial_element = player->element_nr;
3786 player->artwork_element =
3787 (level.use_artwork_element[i] ? level.artwork_element[i] :
3788 player->element_nr);
3789 player->use_murphy = FALSE;
3791 player->block_last_field = FALSE; // initialized in InitPlayerField()
3792 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3794 player->gravity = level.initial_player_gravity[i];
3796 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3798 player->actual_frame_counter.count = 0;
3799 player->actual_frame_counter.value = 1;
3801 player->step_counter = 0;
3803 player->last_move_dir = initial_move_dir;
3805 player->is_active = FALSE;
3807 player->is_waiting = FALSE;
3808 player->is_moving = FALSE;
3809 player->is_auto_moving = FALSE;
3810 player->is_digging = FALSE;
3811 player->is_snapping = FALSE;
3812 player->is_collecting = FALSE;
3813 player->is_pushing = FALSE;
3814 player->is_switching = FALSE;
3815 player->is_dropping = FALSE;
3816 player->is_dropping_pressed = FALSE;
3818 player->is_bored = FALSE;
3819 player->is_sleeping = FALSE;
3821 player->was_waiting = TRUE;
3822 player->was_moving = FALSE;
3823 player->was_snapping = FALSE;
3824 player->was_dropping = FALSE;
3826 player->force_dropping = FALSE;
3828 player->frame_counter_bored = -1;
3829 player->frame_counter_sleeping = -1;
3831 player->anim_delay_counter = 0;
3832 player->post_delay_counter = 0;
3834 player->dir_waiting = initial_move_dir;
3835 player->action_waiting = ACTION_DEFAULT;
3836 player->last_action_waiting = ACTION_DEFAULT;
3837 player->special_action_bored = ACTION_DEFAULT;
3838 player->special_action_sleeping = ACTION_DEFAULT;
3840 player->switch_x = -1;
3841 player->switch_y = -1;
3843 player->drop_x = -1;
3844 player->drop_y = -1;
3846 player->show_envelope = 0;
3848 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3850 player->push_delay = -1; // initialized when pushing starts
3851 player->push_delay_value = game.initial_push_delay_value;
3853 player->drop_delay = 0;
3854 player->drop_pressed_delay = 0;
3856 player->last_jx = -1;
3857 player->last_jy = -1;
3861 player->shield_normal_time_left = 0;
3862 player->shield_deadly_time_left = 0;
3864 player->last_removed_element = EL_UNDEFINED;
3866 player->inventory_infinite_element = EL_UNDEFINED;
3867 player->inventory_size = 0;
3869 if (level.use_initial_inventory[i])
3871 for (j = 0; j < level.initial_inventory_size[i]; j++)
3873 int element = level.initial_inventory_content[i][j];
3874 int collect_count = element_info[element].collect_count_initial;
3877 if (!IS_CUSTOM_ELEMENT(element))
3880 if (collect_count == 0)
3881 player->inventory_infinite_element = element;
3883 for (k = 0; k < collect_count; k++)
3884 if (player->inventory_size < MAX_INVENTORY_SIZE)
3885 player->inventory_element[player->inventory_size++] = element;
3889 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3890 SnapField(player, 0, 0);
3892 map_player_action[i] = i;
3895 network_player_action_received = FALSE;
3897 // initial null action
3898 if (network_playing)
3899 SendToServer_MovePlayer(MV_NONE);
3904 TimeLeft = level.time;
3909 ScreenMovDir = MV_NONE;
3913 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3915 game.robot_wheel_x = -1;
3916 game.robot_wheel_y = -1;
3921 game.all_players_gone = FALSE;
3923 game.LevelSolved = FALSE;
3924 game.GameOver = FALSE;
3926 game.GamePlayed = !tape.playing;
3928 game.LevelSolved_GameWon = FALSE;
3929 game.LevelSolved_GameEnd = FALSE;
3930 game.LevelSolved_SaveTape = FALSE;
3931 game.LevelSolved_SaveScore = FALSE;
3933 game.LevelSolved_CountingTime = 0;
3934 game.LevelSolved_CountingScore = 0;
3935 game.LevelSolved_CountingHealth = 0;
3937 game.RestartGameRequested = FALSE;
3939 game.panel.active = TRUE;
3941 game.no_level_time_limit = (level.time == 0);
3942 game.time_limit = (leveldir_current->time_limit && setup.time_limit);
3944 game.yamyam_content_nr = 0;
3945 game.robot_wheel_active = FALSE;
3946 game.magic_wall_active = FALSE;
3947 game.magic_wall_time_left = 0;
3948 game.light_time_left = 0;
3949 game.timegate_time_left = 0;
3950 game.switchgate_pos = 0;
3951 game.wind_direction = level.wind_direction_initial;
3953 game.time_final = 0;
3954 game.score_time_final = 0;
3957 game.score_final = 0;
3959 game.health = MAX_HEALTH;
3960 game.health_final = MAX_HEALTH;
3962 game.gems_still_needed = level.gems_needed;
3963 game.sokoban_fields_still_needed = 0;
3964 game.sokoban_objects_still_needed = 0;
3965 game.lights_still_needed = 0;
3966 game.players_still_needed = 0;
3967 game.friends_still_needed = 0;
3969 game.lenses_time_left = 0;
3970 game.magnify_time_left = 0;
3972 game.ball_active = level.ball_active_initial;
3973 game.ball_content_nr = 0;
3975 game.explosions_delayed = TRUE;
3977 game.envelope_active = FALSE;
3979 // special case: set custom artwork setting to initial value
3980 game.use_masked_elements = game.use_masked_elements_initial;
3982 for (i = 0; i < NUM_BELTS; i++)
3984 game.belt_dir[i] = MV_NONE;
3985 game.belt_dir_nr[i] = 3; // not moving, next moving left
3988 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3989 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3991 #if DEBUG_INIT_PLAYER
3992 DebugPrintPlayerStatus("Player status at level initialization");
3995 SCAN_PLAYFIELD(x, y)
3997 Tile[x][y] = Last[x][y] = level.field[x][y];
3998 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3999 ChangeDelay[x][y] = 0;
4000 ChangePage[x][y] = -1;
4001 CustomValue[x][y] = 0; // initialized in InitField()
4002 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
4004 WasJustMoving[x][y] = 0;
4005 WasJustFalling[x][y] = 0;
4006 CheckCollision[x][y] = 0;
4007 CheckImpact[x][y] = 0;
4009 Pushed[x][y] = FALSE;
4011 ChangeCount[x][y] = 0;
4012 ChangeEvent[x][y] = -1;
4014 ExplodePhase[x][y] = 0;
4015 ExplodeDelay[x][y] = 0;
4016 ExplodeField[x][y] = EX_TYPE_NONE;
4018 RunnerVisit[x][y] = 0;
4019 PlayerVisit[x][y] = 0;
4022 GfxRandom[x][y] = INIT_GFX_RANDOM();
4023 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
4024 GfxElement[x][y] = EL_UNDEFINED;
4025 GfxElementEmpty[x][y] = EL_EMPTY;
4026 GfxAction[x][y] = ACTION_DEFAULT;
4027 GfxDir[x][y] = MV_NONE;
4028 GfxRedraw[x][y] = GFX_REDRAW_NONE;
4031 SCAN_PLAYFIELD(x, y)
4033 InitFieldForEngine(x, y);
4035 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
4037 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
4040 InitField(x, y, TRUE);
4042 ResetGfxAnimation(x, y);
4047 // required if level does not contain any "empty space" element
4048 if (element_info[EL_EMPTY].use_gfx_element)
4049 game.use_masked_elements = TRUE;
4051 for (i = 0; i < MAX_PLAYERS; i++)
4053 struct PlayerInfo *player = &stored_player[i];
4055 // set number of special actions for bored and sleeping animation
4056 player->num_special_action_bored =
4057 get_num_special_action(player->artwork_element,
4058 ACTION_BORING_1, ACTION_BORING_LAST);
4059 player->num_special_action_sleeping =
4060 get_num_special_action(player->artwork_element,
4061 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
4064 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
4065 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
4067 // initialize type of slippery elements
4068 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4070 if (!IS_CUSTOM_ELEMENT(i))
4072 // default: elements slip down either to the left or right randomly
4073 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
4075 // SP style elements prefer to slip down on the left side
4076 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
4077 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4079 // BD style elements prefer to slip down on the left side
4080 if (game.emulation == EMU_BOULDERDASH)
4081 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4085 // initialize explosion and ignition delay
4086 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4088 if (!IS_CUSTOM_ELEMENT(i))
4091 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
4092 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
4093 game.emulation == EMU_SUPAPLEX ? 3 : 2);
4094 int last_phase = (num_phase + 1) * delay;
4095 int half_phase = (num_phase / 2) * delay;
4097 element_info[i].explosion_delay = last_phase - 1;
4098 element_info[i].ignition_delay = half_phase;
4100 if (i == EL_BLACK_ORB)
4101 element_info[i].ignition_delay = 1;
4105 // correct non-moving belts to start moving left
4106 for (i = 0; i < NUM_BELTS; i++)
4107 if (game.belt_dir[i] == MV_NONE)
4108 game.belt_dir_nr[i] = 3; // not moving, next moving left
4110 #if USE_NEW_PLAYER_ASSIGNMENTS
4111 // use preferred player also in local single-player mode
4112 if (!network.enabled && !game.team_mode)
4114 int new_index_nr = setup.network_player_nr;
4116 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
4118 for (i = 0; i < MAX_PLAYERS; i++)
4119 stored_player[i].connected_locally = FALSE;
4121 stored_player[new_index_nr].connected_locally = TRUE;
4125 for (i = 0; i < MAX_PLAYERS; i++)
4127 stored_player[i].connected = FALSE;
4129 // in network game mode, the local player might not be the first player
4130 if (stored_player[i].connected_locally)
4131 local_player = &stored_player[i];
4134 if (!network.enabled)
4135 local_player->connected = TRUE;
4139 for (i = 0; i < MAX_PLAYERS; i++)
4140 stored_player[i].connected = tape.player_participates[i];
4142 else if (network.enabled)
4144 // add team mode players connected over the network (needed for correct
4145 // assignment of player figures from level to locally playing players)
4147 for (i = 0; i < MAX_PLAYERS; i++)
4148 if (stored_player[i].connected_network)
4149 stored_player[i].connected = TRUE;
4151 else if (game.team_mode)
4153 // try to guess locally connected team mode players (needed for correct
4154 // assignment of player figures from level to locally playing players)
4156 for (i = 0; i < MAX_PLAYERS; i++)
4157 if (setup.input[i].use_joystick ||
4158 setup.input[i].key.left != KSYM_UNDEFINED)
4159 stored_player[i].connected = TRUE;
4162 #if DEBUG_INIT_PLAYER
4163 DebugPrintPlayerStatus("Player status after level initialization");
4166 #if DEBUG_INIT_PLAYER
4167 Debug("game:init:player", "Reassigning players ...");
4170 // check if any connected player was not found in playfield
4171 for (i = 0; i < MAX_PLAYERS; i++)
4173 struct PlayerInfo *player = &stored_player[i];
4175 if (player->connected && !player->present)
4177 struct PlayerInfo *field_player = NULL;
4179 #if DEBUG_INIT_PLAYER
4180 Debug("game:init:player",
4181 "- looking for field player for player %d ...", i + 1);
4184 // assign first free player found that is present in the playfield
4186 // first try: look for unmapped playfield player that is not connected
4187 for (j = 0; j < MAX_PLAYERS; j++)
4188 if (field_player == NULL &&
4189 stored_player[j].present &&
4190 !stored_player[j].mapped &&
4191 !stored_player[j].connected)
4192 field_player = &stored_player[j];
4194 // second try: look for *any* unmapped playfield player
4195 for (j = 0; j < MAX_PLAYERS; j++)
4196 if (field_player == NULL &&
4197 stored_player[j].present &&
4198 !stored_player[j].mapped)
4199 field_player = &stored_player[j];
4201 if (field_player != NULL)
4203 int jx = field_player->jx, jy = field_player->jy;
4205 #if DEBUG_INIT_PLAYER
4206 Debug("game:init:player", "- found player %d",
4207 field_player->index_nr + 1);
4210 player->present = FALSE;
4211 player->active = FALSE;
4213 field_player->present = TRUE;
4214 field_player->active = TRUE;
4217 player->initial_element = field_player->initial_element;
4218 player->artwork_element = field_player->artwork_element;
4220 player->block_last_field = field_player->block_last_field;
4221 player->block_delay_adjustment = field_player->block_delay_adjustment;
4224 StorePlayer[jx][jy] = field_player->element_nr;
4226 field_player->jx = field_player->last_jx = jx;
4227 field_player->jy = field_player->last_jy = jy;
4229 if (local_player == player)
4230 local_player = field_player;
4232 map_player_action[field_player->index_nr] = i;
4234 field_player->mapped = TRUE;
4236 #if DEBUG_INIT_PLAYER
4237 Debug("game:init:player", "- map_player_action[%d] == %d",
4238 field_player->index_nr + 1, i + 1);
4243 if (player->connected && player->present)
4244 player->mapped = TRUE;
4247 #if DEBUG_INIT_PLAYER
4248 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4253 // check if any connected player was not found in playfield
4254 for (i = 0; i < MAX_PLAYERS; i++)
4256 struct PlayerInfo *player = &stored_player[i];
4258 if (player->connected && !player->present)
4260 for (j = 0; j < MAX_PLAYERS; j++)
4262 struct PlayerInfo *field_player = &stored_player[j];
4263 int jx = field_player->jx, jy = field_player->jy;
4265 // assign first free player found that is present in the playfield
4266 if (field_player->present && !field_player->connected)
4268 player->present = TRUE;
4269 player->active = TRUE;
4271 field_player->present = FALSE;
4272 field_player->active = FALSE;
4274 player->initial_element = field_player->initial_element;
4275 player->artwork_element = field_player->artwork_element;
4277 player->block_last_field = field_player->block_last_field;
4278 player->block_delay_adjustment = field_player->block_delay_adjustment;
4280 StorePlayer[jx][jy] = player->element_nr;
4282 player->jx = player->last_jx = jx;
4283 player->jy = player->last_jy = jy;
4293 Debug("game:init:player", "local_player->present == %d",
4294 local_player->present);
4297 // set focus to local player for network games, else to all players
4298 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4299 game.centered_player_nr_next = game.centered_player_nr;
4300 game.set_centered_player = FALSE;
4301 game.set_centered_player_wrap = FALSE;
4303 if (network_playing && tape.recording)
4305 // store client dependent player focus when recording network games
4306 tape.centered_player_nr_next = game.centered_player_nr_next;
4307 tape.set_centered_player = TRUE;
4312 // when playing a tape, eliminate all players who do not participate
4314 #if USE_NEW_PLAYER_ASSIGNMENTS
4316 if (!game.team_mode)
4318 for (i = 0; i < MAX_PLAYERS; i++)
4320 if (stored_player[i].active &&
4321 !tape.player_participates[map_player_action[i]])
4323 struct PlayerInfo *player = &stored_player[i];
4324 int jx = player->jx, jy = player->jy;
4326 #if DEBUG_INIT_PLAYER
4327 Debug("game:init:player", "Removing player %d at (%d, %d)",
4331 player->active = FALSE;
4332 StorePlayer[jx][jy] = 0;
4333 Tile[jx][jy] = EL_EMPTY;
4340 for (i = 0; i < MAX_PLAYERS; i++)
4342 if (stored_player[i].active &&
4343 !tape.player_participates[i])
4345 struct PlayerInfo *player = &stored_player[i];
4346 int jx = player->jx, jy = player->jy;
4348 player->active = FALSE;
4349 StorePlayer[jx][jy] = 0;
4350 Tile[jx][jy] = EL_EMPTY;
4355 else if (!network.enabled && !game.team_mode) // && !tape.playing
4357 // when in single player mode, eliminate all but the local player
4359 for (i = 0; i < MAX_PLAYERS; i++)
4361 struct PlayerInfo *player = &stored_player[i];
4363 if (player->active && player != local_player)
4365 int jx = player->jx, jy = player->jy;
4367 player->active = FALSE;
4368 player->present = FALSE;
4370 StorePlayer[jx][jy] = 0;
4371 Tile[jx][jy] = EL_EMPTY;
4376 for (i = 0; i < MAX_PLAYERS; i++)
4377 if (stored_player[i].active)
4378 game.players_still_needed++;
4380 if (level.solved_by_one_player)
4381 game.players_still_needed = 1;
4383 // when recording the game, store which players take part in the game
4386 #if USE_NEW_PLAYER_ASSIGNMENTS
4387 for (i = 0; i < MAX_PLAYERS; i++)
4388 if (stored_player[i].connected)
4389 tape.player_participates[i] = TRUE;
4391 for (i = 0; i < MAX_PLAYERS; i++)
4392 if (stored_player[i].active)
4393 tape.player_participates[i] = TRUE;
4397 #if DEBUG_INIT_PLAYER
4398 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4401 if (BorderElement == EL_EMPTY)
4404 SBX_Right = lev_fieldx - SCR_FIELDX;
4406 SBY_Lower = lev_fieldy - SCR_FIELDY;
4411 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4413 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4416 if (full_lev_fieldx <= SCR_FIELDX)
4417 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4418 if (full_lev_fieldy <= SCR_FIELDY)
4419 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4421 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4423 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4426 // if local player not found, look for custom element that might create
4427 // the player (make some assumptions about the right custom element)
4428 if (!local_player->present)
4430 int start_x = 0, start_y = 0;
4431 int found_rating = 0;
4432 int found_element = EL_UNDEFINED;
4433 int player_nr = local_player->index_nr;
4435 SCAN_PLAYFIELD(x, y)
4437 int element = Tile[x][y];
4442 if (level.use_start_element[player_nr] &&
4443 level.start_element[player_nr] == element &&
4450 found_element = element;
4453 if (!IS_CUSTOM_ELEMENT(element))
4456 if (CAN_CHANGE(element))
4458 for (i = 0; i < element_info[element].num_change_pages; i++)
4460 // check for player created from custom element as single target
4461 content = element_info[element].change_page[i].target_element;
4462 is_player = IS_PLAYER_ELEMENT(content);
4464 if (is_player && (found_rating < 3 ||
4465 (found_rating == 3 && element < found_element)))
4471 found_element = element;
4476 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4478 // check for player created from custom element as explosion content
4479 content = element_info[element].content.e[xx][yy];
4480 is_player = IS_PLAYER_ELEMENT(content);
4482 if (is_player && (found_rating < 2 ||
4483 (found_rating == 2 && element < found_element)))
4485 start_x = x + xx - 1;
4486 start_y = y + yy - 1;
4489 found_element = element;
4492 if (!CAN_CHANGE(element))
4495 for (i = 0; i < element_info[element].num_change_pages; i++)
4497 // check for player created from custom element as extended target
4499 element_info[element].change_page[i].target_content.e[xx][yy];
4501 is_player = IS_PLAYER_ELEMENT(content);
4503 if (is_player && (found_rating < 1 ||
4504 (found_rating == 1 && element < found_element)))
4506 start_x = x + xx - 1;
4507 start_y = y + yy - 1;
4510 found_element = element;
4516 scroll_x = SCROLL_POSITION_X(start_x);
4517 scroll_y = SCROLL_POSITION_Y(start_y);
4521 scroll_x = SCROLL_POSITION_X(local_player->jx);
4522 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4525 if (game.forced_scroll_x != ARG_UNDEFINED_VALUE)
4526 scroll_x = game.forced_scroll_x;
4527 if (game.forced_scroll_y != ARG_UNDEFINED_VALUE)
4528 scroll_y = game.forced_scroll_y;
4530 // !!! FIX THIS (START) !!!
4531 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
4533 InitGameEngine_BD();
4535 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4537 InitGameEngine_EM();
4539 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4541 InitGameEngine_SP();
4543 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4545 InitGameEngine_MM();
4549 DrawLevel(REDRAW_FIELD);
4552 // after drawing the level, correct some elements
4553 if (game.timegate_time_left == 0)
4554 CloseAllOpenTimegates();
4557 // blit playfield from scroll buffer to normal back buffer for fading in
4558 BlitScreenToBitmap(backbuffer);
4559 // !!! FIX THIS (END) !!!
4561 DrawMaskedBorder(fade_mask);
4566 // full screen redraw is required at this point in the following cases:
4567 // - special editor door undrawn when game was started from level editor
4568 // - drawing area (playfield) was changed and has to be removed completely
4569 redraw_mask = REDRAW_ALL;
4573 if (!game.restart_level)
4575 // copy default game door content to main double buffer
4577 // !!! CHECK AGAIN !!!
4578 SetPanelBackground();
4579 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4580 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4583 SetPanelBackground();
4584 SetDrawBackgroundMask(REDRAW_DOOR_1);
4586 UpdateAndDisplayGameControlValues();
4588 if (!game.restart_level)
4594 CreateGameButtons();
4599 // copy actual game door content to door double buffer for OpenDoor()
4600 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4602 OpenDoor(DOOR_OPEN_ALL);
4604 KeyboardAutoRepeatOffUnlessAutoplay();
4606 #if DEBUG_INIT_PLAYER
4607 DebugPrintPlayerStatus("Player status (final)");
4616 if (!game.restart_level && !tape.playing)
4618 LevelStats_incPlayed(level_nr);
4620 SaveLevelSetup_SeriesInfo();
4623 game.restart_level = FALSE;
4624 game.request_active = FALSE;
4626 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4627 InitGameActions_MM();
4629 SaveEngineSnapshotToListInitial();
4631 if (!game.restart_level)
4633 PlaySound(SND_GAME_STARTING);
4635 if (setup.sound_music)
4639 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4642 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4643 int actual_player_x, int actual_player_y)
4645 // this is used for non-R'n'D game engines to update certain engine values
4647 // needed to determine if sounds are played within the visible screen area
4648 scroll_x = actual_scroll_x;
4649 scroll_y = actual_scroll_y;
4651 // needed to get player position for "follow finger" playing input method
4652 local_player->jx = actual_player_x;
4653 local_player->jy = actual_player_y;
4656 void InitMovDir(int x, int y)
4658 int i, element = Tile[x][y];
4659 static int xy[4][2] =
4666 static int direction[3][4] =
4668 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4669 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4670 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4679 Tile[x][y] = EL_BUG;
4680 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4683 case EL_SPACESHIP_RIGHT:
4684 case EL_SPACESHIP_UP:
4685 case EL_SPACESHIP_LEFT:
4686 case EL_SPACESHIP_DOWN:
4687 Tile[x][y] = EL_SPACESHIP;
4688 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4691 case EL_BD_BUTTERFLY_RIGHT:
4692 case EL_BD_BUTTERFLY_UP:
4693 case EL_BD_BUTTERFLY_LEFT:
4694 case EL_BD_BUTTERFLY_DOWN:
4695 Tile[x][y] = EL_BD_BUTTERFLY;
4696 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4699 case EL_BD_FIREFLY_RIGHT:
4700 case EL_BD_FIREFLY_UP:
4701 case EL_BD_FIREFLY_LEFT:
4702 case EL_BD_FIREFLY_DOWN:
4703 Tile[x][y] = EL_BD_FIREFLY;
4704 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4707 case EL_PACMAN_RIGHT:
4709 case EL_PACMAN_LEFT:
4710 case EL_PACMAN_DOWN:
4711 Tile[x][y] = EL_PACMAN;
4712 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4715 case EL_YAMYAM_LEFT:
4716 case EL_YAMYAM_RIGHT:
4718 case EL_YAMYAM_DOWN:
4719 Tile[x][y] = EL_YAMYAM;
4720 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4723 case EL_SP_SNIKSNAK:
4724 MovDir[x][y] = MV_UP;
4727 case EL_SP_ELECTRON:
4728 MovDir[x][y] = MV_LEFT;
4735 Tile[x][y] = EL_MOLE;
4736 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4739 case EL_SPRING_LEFT:
4740 case EL_SPRING_RIGHT:
4741 Tile[x][y] = EL_SPRING;
4742 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4746 if (IS_CUSTOM_ELEMENT(element))
4748 struct ElementInfo *ei = &element_info[element];
4749 int move_direction_initial = ei->move_direction_initial;
4750 int move_pattern = ei->move_pattern;
4752 if (move_direction_initial == MV_START_PREVIOUS)
4754 if (MovDir[x][y] != MV_NONE)
4757 move_direction_initial = MV_START_AUTOMATIC;
4760 if (move_direction_initial == MV_START_RANDOM)
4761 MovDir[x][y] = 1 << RND(4);
4762 else if (move_direction_initial & MV_ANY_DIRECTION)
4763 MovDir[x][y] = move_direction_initial;
4764 else if (move_pattern == MV_ALL_DIRECTIONS ||
4765 move_pattern == MV_TURNING_LEFT ||
4766 move_pattern == MV_TURNING_RIGHT ||
4767 move_pattern == MV_TURNING_LEFT_RIGHT ||
4768 move_pattern == MV_TURNING_RIGHT_LEFT ||
4769 move_pattern == MV_TURNING_RANDOM)
4770 MovDir[x][y] = 1 << RND(4);
4771 else if (move_pattern == MV_HORIZONTAL)
4772 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4773 else if (move_pattern == MV_VERTICAL)
4774 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4775 else if (move_pattern & MV_ANY_DIRECTION)
4776 MovDir[x][y] = element_info[element].move_pattern;
4777 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4778 move_pattern == MV_ALONG_RIGHT_SIDE)
4780 // use random direction as default start direction
4781 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4782 MovDir[x][y] = 1 << RND(4);
4784 for (i = 0; i < NUM_DIRECTIONS; i++)
4786 int x1 = x + xy[i][0];
4787 int y1 = y + xy[i][1];
4789 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4791 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4792 MovDir[x][y] = direction[0][i];
4794 MovDir[x][y] = direction[1][i];
4803 MovDir[x][y] = 1 << RND(4);
4805 if (element != EL_BUG &&
4806 element != EL_SPACESHIP &&
4807 element != EL_BD_BUTTERFLY &&
4808 element != EL_BD_FIREFLY)
4811 for (i = 0; i < NUM_DIRECTIONS; i++)
4813 int x1 = x + xy[i][0];
4814 int y1 = y + xy[i][1];
4816 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4818 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4820 MovDir[x][y] = direction[0][i];
4823 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4824 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4826 MovDir[x][y] = direction[1][i];
4835 GfxDir[x][y] = MovDir[x][y];
4838 void InitAmoebaNr(int x, int y)
4841 int group_nr = AmoebaNeighbourNr(x, y);
4845 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4847 if (AmoebaCnt[i] == 0)
4855 AmoebaNr[x][y] = group_nr;
4856 AmoebaCnt[group_nr]++;
4857 AmoebaCnt2[group_nr]++;
4860 static void LevelSolved_SetFinalGameValues(void)
4862 game.time_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.time_played :
4863 game.no_level_time_limit ? TimePlayed : TimeLeft);
4864 game.score_time_final = (level.use_step_counter ? TimePlayed :
4865 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4867 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.score :
4868 level.game_engine_type == GAME_ENGINE_TYPE_EM ? game_em.lev->score :
4869 level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.score :
4872 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4873 MM_HEALTH(game_mm.laser_overload_value) :
4876 game.LevelSolved_CountingTime = game.time_final;
4877 game.LevelSolved_CountingScore = game.score_final;
4878 game.LevelSolved_CountingHealth = game.health_final;
4881 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4883 game.LevelSolved_CountingTime = time;
4884 game.LevelSolved_CountingScore = score;
4885 game.LevelSolved_CountingHealth = health;
4887 game_panel_controls[GAME_PANEL_TIME].value = time;
4888 game_panel_controls[GAME_PANEL_SCORE].value = score;
4889 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4891 DisplayGameControlValues();
4894 static void LevelSolved(void)
4896 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4897 game.players_still_needed > 0)
4900 game.LevelSolved = TRUE;
4901 game.GameOver = TRUE;
4905 // needed here to display correct panel values while player walks into exit
4906 LevelSolved_SetFinalGameValues();
4909 static boolean AdvanceToNextLevel(void)
4911 if (setup.increment_levels &&
4912 level_nr < leveldir_current->last_level &&
4915 level_nr++; // advance to next level
4916 TapeErase(); // start with empty tape
4918 if (setup.auto_play_next_level)
4920 scores.continue_playing = TRUE;
4921 scores.next_level_nr = level_nr;
4923 LoadLevel(level_nr);
4925 SaveLevelSetup_SeriesInfo();
4936 static int time_count_steps;
4937 static int time, time_final;
4938 static float score, score_final; // needed for time score < 10 for 10 seconds
4939 static int health, health_final;
4940 static int game_over_delay_1 = 0;
4941 static int game_over_delay_2 = 0;
4942 static int game_over_delay_3 = 0;
4943 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4944 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4946 if (!game.LevelSolved_GameWon)
4950 // do not start end game actions before the player stops moving (to exit)
4951 if (local_player->active && local_player->MovPos)
4954 // calculate final game values after player finished walking into exit
4955 LevelSolved_SetFinalGameValues();
4957 game.LevelSolved_GameWon = TRUE;
4958 game.LevelSolved_SaveTape = tape.recording;
4959 game.LevelSolved_SaveScore = !tape.playing;
4963 LevelStats_incSolved(level_nr);
4965 SaveLevelSetup_SeriesInfo();
4968 if (tape.auto_play) // tape might already be stopped here
4969 tape.auto_play_level_solved = TRUE;
4973 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4974 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4975 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4977 time = time_final = game.time_final;
4978 score = score_final = game.score_final;
4979 health = health_final = game.health_final;
4981 // update game panel values before (delayed) counting of score (if any)
4982 LevelSolved_DisplayFinalGameValues(time, score, health);
4984 // if level has time score defined, calculate new final game values
4987 int time_final_max = 999;
4988 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4989 int time_frames = 0;
4990 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4991 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4996 time_frames = time_frames_left;
4998 else if (game.no_level_time_limit && TimePlayed < time_final_max)
5000 time_final = time_final_max;
5001 time_frames = time_frames_final_max - time_frames_played;
5004 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
5006 time_count_steps = MAX(1, ABS(time_final - time) / 100);
5008 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
5010 // keep previous values (final values already processed here)
5012 score_final = score;
5014 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
5017 score_final += health * time_score;
5020 game.score_final = score_final;
5021 game.health_final = health_final;
5024 // if not counting score after game, immediately update game panel values
5025 if (level_editor_test_game || !setup.count_score_after_game ||
5026 level.game_engine_type == GAME_ENGINE_TYPE_BD)
5029 score = score_final;
5031 LevelSolved_DisplayFinalGameValues(time, score, health);
5034 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
5036 // check if last player has left the level
5037 if (game.exit_x >= 0 &&
5040 int x = game.exit_x;
5041 int y = game.exit_y;
5042 int element = Tile[x][y];
5044 // close exit door after last player
5045 if ((game.all_players_gone &&
5046 (element == EL_EXIT_OPEN ||
5047 element == EL_SP_EXIT_OPEN ||
5048 element == EL_STEEL_EXIT_OPEN)) ||
5049 element == EL_EM_EXIT_OPEN ||
5050 element == EL_EM_STEEL_EXIT_OPEN)
5054 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
5055 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
5056 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
5057 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
5058 EL_EM_STEEL_EXIT_CLOSING);
5060 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
5063 // player disappears
5064 DrawLevelField(x, y);
5067 for (i = 0; i < MAX_PLAYERS; i++)
5069 struct PlayerInfo *player = &stored_player[i];
5071 if (player->present)
5073 RemovePlayer(player);
5075 // player disappears
5076 DrawLevelField(player->jx, player->jy);
5081 PlaySound(SND_GAME_WINNING);
5084 if (setup.count_score_after_game)
5086 if (time != time_final)
5088 if (game_over_delay_1 > 0)
5090 game_over_delay_1--;
5095 int time_to_go = ABS(time_final - time);
5096 int time_count_dir = (time < time_final ? +1 : -1);
5098 if (time_to_go < time_count_steps)
5099 time_count_steps = 1;
5101 time += time_count_steps * time_count_dir;
5102 score += time_count_steps * time_score;
5104 // set final score to correct rounding differences after counting score
5105 if (time == time_final)
5106 score = score_final;
5108 LevelSolved_DisplayFinalGameValues(time, score, health);
5110 if (time == time_final)
5111 StopSound(SND_GAME_LEVELTIME_BONUS);
5112 else if (setup.sound_loops)
5113 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5115 PlaySound(SND_GAME_LEVELTIME_BONUS);
5120 if (health != health_final)
5122 if (game_over_delay_2 > 0)
5124 game_over_delay_2--;
5129 int health_count_dir = (health < health_final ? +1 : -1);
5131 health += health_count_dir;
5132 score += time_score;
5134 LevelSolved_DisplayFinalGameValues(time, score, health);
5136 if (health == health_final)
5137 StopSound(SND_GAME_LEVELTIME_BONUS);
5138 else if (setup.sound_loops)
5139 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5141 PlaySound(SND_GAME_LEVELTIME_BONUS);
5147 game.panel.active = FALSE;
5149 if (game_over_delay_3 > 0)
5151 game_over_delay_3--;
5161 // used instead of "level_nr" (needed for network games)
5162 int last_level_nr = levelset.level_nr;
5163 boolean tape_saved = FALSE;
5165 // Important note: This function is not only called after "GameWon()", but also after
5166 // "game over" (if automatically asking for restarting the game is disabled in setup)
5168 if (game.LevelSolved)
5169 game.LevelSolved_GameEnd = TRUE;
5171 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5173 // make sure that request dialog to save tape does not open door again
5174 if (!global.use_envelope_request)
5175 CloseDoor(DOOR_CLOSE_1);
5178 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5180 // set unique basename for score tape (also saved in high score table)
5181 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5184 // if no tape is to be saved, close both doors simultaneously
5185 CloseDoor(DOOR_CLOSE_ALL);
5187 if (level_editor_test_game || score_info_tape_play)
5189 SetGameStatus(GAME_MODE_MAIN);
5196 if (!game.GamePlayed || (!game.LevelSolved_SaveScore && !level.bd_intermission))
5198 SetGameStatus(GAME_MODE_MAIN);
5205 if (level_nr == leveldir_current->handicap_level)
5207 leveldir_current->handicap_level++;
5209 SaveLevelSetup_SeriesInfo();
5212 // save score and score tape before potentially erasing tape below
5213 if (game.LevelSolved_SaveScore)
5214 NewHighScore(last_level_nr, tape_saved);
5216 // increment and load next level (if possible and not configured otherwise)
5217 AdvanceToNextLevel();
5219 if (game.LevelSolved_SaveScore && scores.last_added >= 0 && setup.show_scores_after_game)
5221 SetGameStatus(GAME_MODE_SCORES);
5223 DrawHallOfFame(last_level_nr);
5225 else if (scores.continue_playing)
5227 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5231 SetGameStatus(GAME_MODE_MAIN);
5237 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5238 boolean one_score_entry_per_name)
5242 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5245 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5247 struct ScoreEntry *entry = &list->entry[i];
5248 boolean score_is_better = (new_entry->score > entry->score);
5249 boolean score_is_equal = (new_entry->score == entry->score);
5250 boolean time_is_better = (new_entry->time < entry->time);
5251 boolean time_is_equal = (new_entry->time == entry->time);
5252 boolean better_by_score = (score_is_better ||
5253 (score_is_equal && time_is_better));
5254 boolean better_by_time = (time_is_better ||
5255 (time_is_equal && score_is_better));
5256 boolean is_better = (level.rate_time_over_score ? better_by_time :
5258 boolean entry_is_empty = (entry->score == 0 &&
5261 // prevent adding server score entries if also existing in local score file
5262 // (special case: historic score entries have an empty tape basename entry)
5263 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5264 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5266 // add fields from server score entry not stored in local score entry
5267 // (currently, this means setting platform, version and country fields;
5268 // in rare cases, this may also correct an invalid score value, as
5269 // historic scores might have been truncated to 16-bit values locally)
5270 *entry = *new_entry;
5275 if (is_better || entry_is_empty)
5277 // player has made it to the hall of fame
5279 if (i < MAX_SCORE_ENTRIES - 1)
5281 int m = MAX_SCORE_ENTRIES - 1;
5284 if (one_score_entry_per_name)
5286 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5287 if (strEqual(list->entry[l].name, new_entry->name))
5290 if (m == i) // player's new highscore overwrites his old one
5294 for (l = m; l > i; l--)
5295 list->entry[l] = list->entry[l - 1];
5300 *entry = *new_entry;
5304 else if (one_score_entry_per_name &&
5305 strEqual(entry->name, new_entry->name))
5307 // player already in high score list with better score or time
5313 // special case: new score is beyond the last high score list position
5314 return MAX_SCORE_ENTRIES;
5317 void NewHighScore(int level_nr, boolean tape_saved)
5319 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5320 boolean one_per_name = FALSE;
5322 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5323 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5325 new_entry.score = game.score_final;
5326 new_entry.time = game.score_time_final;
5328 LoadScore(level_nr);
5330 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5332 if (scores.last_added >= MAX_SCORE_ENTRIES)
5334 scores.last_added = MAX_SCORE_ENTRIES - 1;
5335 scores.force_last_added = TRUE;
5337 scores.entry[scores.last_added] = new_entry;
5339 // store last added local score entry (before merging server scores)
5340 scores.last_added_local = scores.last_added;
5345 if (scores.last_added < 0)
5348 SaveScore(level_nr);
5350 // store last added local score entry (before merging server scores)
5351 scores.last_added_local = scores.last_added;
5353 if (!game.LevelSolved_SaveTape)
5356 SaveScoreTape(level_nr);
5358 if (setup.ask_for_using_api_server)
5360 setup.use_api_server =
5361 Request("Upload your score and tape to the high score server?", REQ_ASK);
5363 if (!setup.use_api_server)
5364 Request("Not using high score server! Use setup menu to enable again!",
5367 runtime.use_api_server = setup.use_api_server;
5369 // after asking for using API server once, do not ask again
5370 setup.ask_for_using_api_server = FALSE;
5372 SaveSetup_ServerSetup();
5375 SaveServerScore(level_nr, tape_saved);
5378 void MergeServerScore(void)
5380 struct ScoreEntry last_added_entry;
5381 boolean one_per_name = FALSE;
5384 if (scores.last_added >= 0)
5385 last_added_entry = scores.entry[scores.last_added];
5387 for (i = 0; i < server_scores.num_entries; i++)
5389 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5391 if (pos >= 0 && pos <= scores.last_added)
5392 scores.last_added++;
5395 if (scores.last_added >= MAX_SCORE_ENTRIES)
5397 scores.last_added = MAX_SCORE_ENTRIES - 1;
5398 scores.force_last_added = TRUE;
5400 scores.entry[scores.last_added] = last_added_entry;
5404 static int getElementMoveStepsizeExt(int x, int y, int direction)
5406 int element = Tile[x][y];
5407 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5408 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5409 int horiz_move = (dx != 0);
5410 int sign = (horiz_move ? dx : dy);
5411 int step = sign * element_info[element].move_stepsize;
5413 // special values for move stepsize for spring and things on conveyor belt
5416 if (CAN_FALL(element) &&
5417 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5418 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5419 else if (element == EL_SPRING)
5420 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5426 static int getElementMoveStepsize(int x, int y)
5428 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5431 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5433 if (player->GfxAction != action || player->GfxDir != dir)
5435 player->GfxAction = action;
5436 player->GfxDir = dir;
5438 player->StepFrame = 0;
5442 static void ResetGfxFrame(int x, int y)
5444 // profiling showed that "autotest" spends 10~20% of its time in this function
5445 if (DrawingDeactivatedField())
5448 int element = Tile[x][y];
5449 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5451 if (graphic_info[graphic].anim_global_sync)
5452 GfxFrame[x][y] = FrameCounter;
5453 else if (graphic_info[graphic].anim_global_anim_sync)
5454 GfxFrame[x][y] = getGlobalAnimSyncFrame();
5455 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5456 GfxFrame[x][y] = CustomValue[x][y];
5457 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5458 GfxFrame[x][y] = element_info[element].collect_score;
5459 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5460 GfxFrame[x][y] = ChangeDelay[x][y];
5463 static void ResetGfxAnimation(int x, int y)
5465 GfxAction[x][y] = ACTION_DEFAULT;
5466 GfxDir[x][y] = MovDir[x][y];
5469 ResetGfxFrame(x, y);
5472 static void ResetRandomAnimationValue(int x, int y)
5474 GfxRandom[x][y] = INIT_GFX_RANDOM();
5477 static void InitMovingField(int x, int y, int direction)
5479 int element = Tile[x][y];
5480 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5481 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5484 boolean is_moving_before, is_moving_after;
5486 // check if element was/is moving or being moved before/after mode change
5487 is_moving_before = (WasJustMoving[x][y] != 0);
5488 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5490 // reset animation only for moving elements which change direction of moving
5491 // or which just started or stopped moving
5492 // (else CEs with property "can move" / "not moving" are reset each frame)
5493 if (is_moving_before != is_moving_after ||
5494 direction != MovDir[x][y])
5495 ResetGfxAnimation(x, y);
5497 MovDir[x][y] = direction;
5498 GfxDir[x][y] = direction;
5500 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5501 direction == MV_DOWN && CAN_FALL(element) ?
5502 ACTION_FALLING : ACTION_MOVING);
5504 // this is needed for CEs with property "can move" / "not moving"
5506 if (is_moving_after)
5508 if (Tile[newx][newy] == EL_EMPTY)
5509 Tile[newx][newy] = EL_BLOCKED;
5511 MovDir[newx][newy] = MovDir[x][y];
5513 CustomValue[newx][newy] = CustomValue[x][y];
5515 GfxFrame[newx][newy] = GfxFrame[x][y];
5516 GfxRandom[newx][newy] = GfxRandom[x][y];
5517 GfxAction[newx][newy] = GfxAction[x][y];
5518 GfxDir[newx][newy] = GfxDir[x][y];
5522 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5524 int direction = MovDir[x][y];
5525 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5526 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5532 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5534 int direction = MovDir[x][y];
5535 int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0);
5536 int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0);
5538 *comes_from_x = oldx;
5539 *comes_from_y = oldy;
5542 static int MovingOrBlocked2Element(int x, int y)
5544 int element = Tile[x][y];
5546 if (element == EL_BLOCKED)
5550 Blocked2Moving(x, y, &oldx, &oldy);
5552 return Tile[oldx][oldy];
5558 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5560 // like MovingOrBlocked2Element(), but if element is moving
5561 // and (x, y) is the field the moving element is just leaving,
5562 // return EL_BLOCKED instead of the element value
5563 int element = Tile[x][y];
5565 if (IS_MOVING(x, y))
5567 if (element == EL_BLOCKED)
5571 Blocked2Moving(x, y, &oldx, &oldy);
5572 return Tile[oldx][oldy];
5581 static void RemoveField(int x, int y)
5583 Tile[x][y] = EL_EMPTY;
5589 CustomValue[x][y] = 0;
5592 ChangeDelay[x][y] = 0;
5593 ChangePage[x][y] = -1;
5594 Pushed[x][y] = FALSE;
5596 GfxElement[x][y] = EL_UNDEFINED;
5597 GfxAction[x][y] = ACTION_DEFAULT;
5598 GfxDir[x][y] = MV_NONE;
5601 static void RemoveMovingField(int x, int y)
5603 int oldx = x, oldy = y, newx = x, newy = y;
5604 int element = Tile[x][y];
5605 int next_element = EL_UNDEFINED;
5607 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5610 if (IS_MOVING(x, y))
5612 Moving2Blocked(x, y, &newx, &newy);
5614 if (Tile[newx][newy] != EL_BLOCKED)
5616 // element is moving, but target field is not free (blocked), but
5617 // already occupied by something different (example: acid pool);
5618 // in this case, only remove the moving field, but not the target
5620 RemoveField(oldx, oldy);
5622 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5624 TEST_DrawLevelField(oldx, oldy);
5629 else if (element == EL_BLOCKED)
5631 Blocked2Moving(x, y, &oldx, &oldy);
5632 if (!IS_MOVING(oldx, oldy))
5636 if (element == EL_BLOCKED &&
5637 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5638 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5639 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5640 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5641 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5642 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5643 next_element = get_next_element(Tile[oldx][oldy]);
5645 RemoveField(oldx, oldy);
5646 RemoveField(newx, newy);
5648 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5650 if (next_element != EL_UNDEFINED)
5651 Tile[oldx][oldy] = next_element;
5653 TEST_DrawLevelField(oldx, oldy);
5654 TEST_DrawLevelField(newx, newy);
5657 void DrawDynamite(int x, int y)
5659 int sx = SCREENX(x), sy = SCREENY(y);
5660 int graphic = el2img(Tile[x][y]);
5663 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5666 if (IS_WALKABLE_INSIDE(Back[x][y]))
5670 DrawLevelElement(x, y, Back[x][y]);
5671 else if (Store[x][y])
5672 DrawLevelElement(x, y, Store[x][y]);
5673 else if (game.use_masked_elements)
5674 DrawLevelElement(x, y, EL_EMPTY);
5676 frame = getGraphicAnimationFrameXY(graphic, x, y);
5678 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5679 DrawGraphicThruMask(sx, sy, graphic, frame);
5681 DrawGraphic(sx, sy, graphic, frame);
5684 static void CheckDynamite(int x, int y)
5686 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5690 if (MovDelay[x][y] != 0)
5693 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5699 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5704 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5706 boolean num_checked_players = 0;
5709 for (i = 0; i < MAX_PLAYERS; i++)
5711 if (stored_player[i].active)
5713 int sx = stored_player[i].jx;
5714 int sy = stored_player[i].jy;
5716 if (num_checked_players == 0)
5723 *sx1 = MIN(*sx1, sx);
5724 *sy1 = MIN(*sy1, sy);
5725 *sx2 = MAX(*sx2, sx);
5726 *sy2 = MAX(*sy2, sy);
5729 num_checked_players++;
5734 static boolean checkIfAllPlayersFitToScreen_RND(void)
5736 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5738 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5740 return (sx2 - sx1 < SCR_FIELDX &&
5741 sy2 - sy1 < SCR_FIELDY);
5744 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5746 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5748 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5750 *sx = (sx1 + sx2) / 2;
5751 *sy = (sy1 + sy2) / 2;
5754 static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
5755 boolean center_screen, boolean quick_relocation)
5757 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5758 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5759 boolean no_delay = (tape.warp_forward);
5760 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5761 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5762 int new_scroll_x, new_scroll_y;
5764 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5766 // case 1: quick relocation inside visible screen (without scrolling)
5773 if (!level.shifted_relocation || center_screen)
5775 // relocation _with_ centering of screen
5777 new_scroll_x = SCROLL_POSITION_X(x);
5778 new_scroll_y = SCROLL_POSITION_Y(y);
5782 // relocation _without_ centering of screen
5784 // apply distance between old and new player position to scroll position
5785 int shifted_scroll_x = scroll_x + (x - old_x);
5786 int shifted_scroll_y = scroll_y + (y - old_y);
5788 // make sure that shifted scroll position does not scroll beyond screen
5789 new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
5790 new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
5792 // special case for teleporting from one end of the playfield to the other
5793 // (this kludge prevents the destination area to be shifted by half a tile
5794 // against the source destination for even screen width or screen height;
5795 // probably most useful when used with high "game.forced_scroll_delay_value"
5796 // in combination with "game.forced_scroll_x" and "game.forced_scroll_y")
5797 if (quick_relocation)
5799 if (EVEN(SCR_FIELDX))
5801 // relocate (teleport) between left and right border (half or full)
5802 if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1)
5803 new_scroll_x = SBX_Right;
5804 else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right)
5805 new_scroll_x = SBX_Right - 1;
5806 else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1)
5807 new_scroll_x = SBX_Left;
5808 else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left)
5809 new_scroll_x = SBX_Left + 1;
5812 if (EVEN(SCR_FIELDY))
5814 // relocate (teleport) between top and bottom border (half or full)
5815 if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1)
5816 new_scroll_y = SBY_Lower;
5817 else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower)
5818 new_scroll_y = SBY_Lower - 1;
5819 else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1)
5820 new_scroll_y = SBY_Upper;
5821 else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper)
5822 new_scroll_y = SBY_Upper + 1;
5827 if (quick_relocation)
5829 // case 2: quick relocation (redraw without visible scrolling)
5831 scroll_x = new_scroll_x;
5832 scroll_y = new_scroll_y;
5839 // case 3: visible relocation (with scrolling to new position)
5841 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5843 SetVideoFrameDelay(wait_delay_value);
5845 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5847 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5848 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5850 if (dx == 0 && dy == 0) // no scrolling needed at all
5856 // set values for horizontal/vertical screen scrolling (half tile size)
5857 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5858 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5859 int pos_x = dx * TILEX / 2;
5860 int pos_y = dy * TILEY / 2;
5861 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5862 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5864 ScrollLevel(dx, dy);
5867 // scroll in two steps of half tile size to make things smoother
5868 BlitScreenToBitmapExt_RND(window, fx, fy);
5870 // scroll second step to align at full tile size
5871 BlitScreenToBitmap(window);
5877 SetVideoFrameDelay(frame_delay_value_old);
5880 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5882 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5883 int player_nr = GET_PLAYER_NR(el_player);
5884 struct PlayerInfo *player = &stored_player[player_nr];
5885 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5886 boolean no_delay = (tape.warp_forward);
5887 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5888 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5889 int old_jx = player->jx;
5890 int old_jy = player->jy;
5891 int old_element = Tile[old_jx][old_jy];
5892 int element = Tile[jx][jy];
5893 boolean player_relocated = (old_jx != jx || old_jy != jy);
5895 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5896 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5897 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5898 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5899 int leave_side_horiz = move_dir_horiz;
5900 int leave_side_vert = move_dir_vert;
5901 int enter_side = enter_side_horiz | enter_side_vert;
5902 int leave_side = leave_side_horiz | leave_side_vert;
5904 if (player->buried) // do not reanimate dead player
5907 if (!player_relocated) // no need to relocate the player
5910 if (IS_PLAYER(jx, jy)) // player already placed at new position
5912 RemoveField(jx, jy); // temporarily remove newly placed player
5913 DrawLevelField(jx, jy);
5916 if (player->present)
5918 while (player->MovPos)
5920 ScrollPlayer(player, SCROLL_GO_ON);
5921 ScrollScreen(NULL, SCROLL_GO_ON);
5923 AdvanceFrameAndPlayerCounters(player->index_nr);
5927 BackToFront_WithFrameDelay(wait_delay_value);
5930 DrawPlayer(player); // needed here only to cleanup last field
5931 DrawLevelField(player->jx, player->jy); // remove player graphic
5933 player->is_moving = FALSE;
5936 if (IS_CUSTOM_ELEMENT(old_element))
5937 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5939 player->index_bit, leave_side);
5941 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5943 player->index_bit, leave_side);
5945 Tile[jx][jy] = el_player;
5946 InitPlayerField(jx, jy, el_player, TRUE);
5948 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5949 possible that the relocation target field did not contain a player element,
5950 but a walkable element, to which the new player was relocated -- in this
5951 case, restore that (already initialized!) element on the player field */
5952 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5954 Tile[jx][jy] = element; // restore previously existing element
5957 // only visually relocate centered player
5958 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy,
5959 FALSE, level.instant_relocation);
5961 TestIfPlayerTouchesBadThing(jx, jy);
5962 TestIfPlayerTouchesCustomElement(jx, jy);
5964 if (IS_CUSTOM_ELEMENT(element))
5965 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5966 player->index_bit, enter_side);
5968 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5969 player->index_bit, enter_side);
5971 if (player->is_switching)
5973 /* ensure that relocation while still switching an element does not cause
5974 a new element to be treated as also switched directly after relocation
5975 (this is important for teleporter switches that teleport the player to
5976 a place where another teleporter switch is in the same direction, which
5977 would then incorrectly be treated as immediately switched before the
5978 direction key that caused the switch was released) */
5980 player->switch_x += jx - old_jx;
5981 player->switch_y += jy - old_jy;
5985 static void Explode(int ex, int ey, int phase, int mode)
5991 if (game.explosions_delayed)
5993 ExplodeField[ex][ey] = mode;
5997 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5999 int center_element = Tile[ex][ey];
6000 int ce_value = CustomValue[ex][ey];
6001 int ce_score = element_info[center_element].collect_score;
6002 int artwork_element, explosion_element; // set these values later
6004 // remove things displayed in background while burning dynamite
6005 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
6008 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
6010 // put moving element to center field (and let it explode there)
6011 center_element = MovingOrBlocked2Element(ex, ey);
6012 RemoveMovingField(ex, ey);
6013 Tile[ex][ey] = center_element;
6016 // now "center_element" is finally determined -- set related values now
6017 artwork_element = center_element; // for custom player artwork
6018 explosion_element = center_element; // for custom player artwork
6020 if (IS_PLAYER(ex, ey))
6022 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
6024 artwork_element = stored_player[player_nr].artwork_element;
6026 if (level.use_explosion_element[player_nr])
6028 explosion_element = level.explosion_element[player_nr];
6029 artwork_element = explosion_element;
6033 if (mode == EX_TYPE_NORMAL ||
6034 mode == EX_TYPE_CENTER ||
6035 mode == EX_TYPE_CROSS)
6036 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
6038 last_phase = element_info[explosion_element].explosion_delay + 1;
6040 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
6042 int xx = x - ex + 1;
6043 int yy = y - ey + 1;
6046 if (!IN_LEV_FIELD(x, y) ||
6047 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
6048 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
6051 element = Tile[x][y];
6053 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
6055 element = MovingOrBlocked2Element(x, y);
6057 if (!IS_EXPLOSION_PROOF(element))
6058 RemoveMovingField(x, y);
6061 // indestructible elements can only explode in center (but not flames)
6062 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
6063 mode == EX_TYPE_BORDER)) ||
6064 element == EL_FLAMES)
6067 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
6068 behaviour, for example when touching a yamyam that explodes to rocks
6069 with active deadly shield, a rock is created under the player !!! */
6070 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
6072 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
6073 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
6074 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
6076 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
6079 if (IS_ACTIVE_BOMB(element))
6081 // re-activate things under the bomb like gate or penguin
6082 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
6089 // save walkable background elements while explosion on same tile
6090 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
6091 (x != ex || y != ey || mode == EX_TYPE_BORDER))
6092 Back[x][y] = element;
6094 // ignite explodable elements reached by other explosion
6095 if (element == EL_EXPLOSION)
6096 element = Store2[x][y];
6098 if (AmoebaNr[x][y] &&
6099 (element == EL_AMOEBA_FULL ||
6100 element == EL_BD_AMOEBA ||
6101 element == EL_AMOEBA_GROWING))
6103 AmoebaCnt[AmoebaNr[x][y]]--;
6104 AmoebaCnt2[AmoebaNr[x][y]]--;
6109 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
6111 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
6113 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
6115 if (PLAYERINFO(ex, ey)->use_murphy)
6116 Store[x][y] = EL_EMPTY;
6119 // !!! check this case -- currently needed for rnd_rado_negundo_v,
6120 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
6121 else if (IS_PLAYER_ELEMENT(center_element))
6122 Store[x][y] = EL_EMPTY;
6123 else if (center_element == EL_YAMYAM)
6124 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
6125 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
6126 Store[x][y] = element_info[center_element].content.e[xx][yy];
6128 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
6129 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
6130 // otherwise) -- FIX THIS !!!
6131 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
6132 Store[x][y] = element_info[element].content.e[1][1];
6134 else if (!CAN_EXPLODE(element))
6135 Store[x][y] = element_info[element].content.e[1][1];
6138 Store[x][y] = EL_EMPTY;
6140 if (IS_CUSTOM_ELEMENT(center_element))
6141 Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value :
6142 Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score :
6143 Store[x][y] >= EL_PREV_CE_8 &&
6144 Store[x][y] <= EL_NEXT_CE_8 ?
6145 RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) :
6148 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
6149 center_element == EL_AMOEBA_TO_DIAMOND)
6150 Store2[x][y] = element;
6152 Tile[x][y] = EL_EXPLOSION;
6153 GfxElement[x][y] = artwork_element;
6155 ExplodePhase[x][y] = 1;
6156 ExplodeDelay[x][y] = last_phase;
6161 if (center_element == EL_YAMYAM)
6162 game.yamyam_content_nr =
6163 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
6175 GfxFrame[x][y] = 0; // restart explosion animation
6177 last_phase = ExplodeDelay[x][y];
6179 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
6181 // this can happen if the player leaves an explosion just in time
6182 if (GfxElement[x][y] == EL_UNDEFINED)
6183 GfxElement[x][y] = EL_EMPTY;
6185 border_element = Store2[x][y];
6186 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6187 border_element = StorePlayer[x][y];
6189 if (phase == element_info[border_element].ignition_delay ||
6190 phase == last_phase)
6192 boolean border_explosion = FALSE;
6194 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6195 !PLAYER_EXPLOSION_PROTECTED(x, y))
6197 KillPlayerUnlessExplosionProtected(x, y);
6198 border_explosion = TRUE;
6200 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6202 Tile[x][y] = Store2[x][y];
6205 border_explosion = TRUE;
6207 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6209 AmoebaToDiamond(x, y);
6211 border_explosion = TRUE;
6214 // if an element just explodes due to another explosion (chain-reaction),
6215 // do not immediately end the new explosion when it was the last frame of
6216 // the explosion (as it would be done in the following "if"-statement!)
6217 if (border_explosion && phase == last_phase)
6221 // this can happen if the player was just killed by an explosion
6222 if (GfxElement[x][y] == EL_UNDEFINED)
6223 GfxElement[x][y] = EL_EMPTY;
6225 if (phase == last_phase)
6229 element = Tile[x][y] = Store[x][y];
6230 Store[x][y] = Store2[x][y] = 0;
6231 GfxElement[x][y] = EL_UNDEFINED;
6233 // player can escape from explosions and might therefore be still alive
6234 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6235 element <= EL_PLAYER_IS_EXPLODING_4)
6237 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6238 int explosion_element = EL_PLAYER_1 + player_nr;
6239 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6240 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6242 if (level.use_explosion_element[player_nr])
6243 explosion_element = level.explosion_element[player_nr];
6245 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6246 element_info[explosion_element].content.e[xx][yy]);
6249 // restore probably existing indestructible background element
6250 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6251 element = Tile[x][y] = Back[x][y];
6254 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6255 GfxDir[x][y] = MV_NONE;
6256 ChangeDelay[x][y] = 0;
6257 ChangePage[x][y] = -1;
6259 CustomValue[x][y] = 0;
6261 InitField_WithBug2(x, y, FALSE);
6263 TEST_DrawLevelField(x, y);
6265 TestIfElementTouchesCustomElement(x, y);
6267 if (GFX_CRUMBLED(element))
6268 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6270 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6271 StorePlayer[x][y] = 0;
6273 if (IS_PLAYER_ELEMENT(element))
6274 RelocatePlayer(x, y, element);
6276 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6278 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6279 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6282 TEST_DrawLevelFieldCrumbled(x, y);
6284 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6286 DrawLevelElement(x, y, Back[x][y]);
6287 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6289 else if (IS_WALKABLE_UNDER(Back[x][y]))
6291 DrawLevelGraphic(x, y, graphic, frame);
6292 DrawLevelElementThruMask(x, y, Back[x][y]);
6294 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6295 DrawLevelGraphic(x, y, graphic, frame);
6299 static void DynaExplode(int ex, int ey)
6302 int dynabomb_element = Tile[ex][ey];
6303 int dynabomb_size = 1;
6304 boolean dynabomb_xl = FALSE;
6305 struct PlayerInfo *player;
6306 struct XY *xy = xy_topdown;
6308 if (IS_ACTIVE_BOMB(dynabomb_element))
6310 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6311 dynabomb_size = player->dynabomb_size;
6312 dynabomb_xl = player->dynabomb_xl;
6313 player->dynabombs_left++;
6316 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6318 for (i = 0; i < NUM_DIRECTIONS; i++)
6320 for (j = 1; j <= dynabomb_size; j++)
6322 int x = ex + j * xy[i].x;
6323 int y = ey + j * xy[i].y;
6326 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6329 element = Tile[x][y];
6331 // do not restart explosions of fields with active bombs
6332 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6335 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6337 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6338 !IS_DIGGABLE(element) && !dynabomb_xl)
6344 void Bang(int x, int y)
6346 int element = MovingOrBlocked2Element(x, y);
6347 int explosion_type = EX_TYPE_NORMAL;
6349 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6351 struct PlayerInfo *player = PLAYERINFO(x, y);
6353 element = Tile[x][y] = player->initial_element;
6355 if (level.use_explosion_element[player->index_nr])
6357 int explosion_element = level.explosion_element[player->index_nr];
6359 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6360 explosion_type = EX_TYPE_CROSS;
6361 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6362 explosion_type = EX_TYPE_CENTER;
6370 case EL_BD_BUTTERFLY:
6373 case EL_DARK_YAMYAM:
6377 RaiseScoreElement(element);
6380 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6381 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6382 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6383 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6384 case EL_DYNABOMB_INCREASE_NUMBER:
6385 case EL_DYNABOMB_INCREASE_SIZE:
6386 case EL_DYNABOMB_INCREASE_POWER:
6387 explosion_type = EX_TYPE_DYNA;
6390 case EL_DC_LANDMINE:
6391 explosion_type = EX_TYPE_CENTER;
6396 case EL_LAMP_ACTIVE:
6397 case EL_AMOEBA_TO_DIAMOND:
6398 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6399 explosion_type = EX_TYPE_CENTER;
6403 if (element_info[element].explosion_type == EXPLODES_CROSS)
6404 explosion_type = EX_TYPE_CROSS;
6405 else if (element_info[element].explosion_type == EXPLODES_1X1)
6406 explosion_type = EX_TYPE_CENTER;
6410 if (explosion_type == EX_TYPE_DYNA)
6413 Explode(x, y, EX_PHASE_START, explosion_type);
6415 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6418 static void SplashAcid(int x, int y)
6420 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6421 (!IN_LEV_FIELD(x - 1, y - 2) ||
6422 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6423 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6425 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6426 (!IN_LEV_FIELD(x + 1, y - 2) ||
6427 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6428 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6430 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6433 static void InitBeltMovement(void)
6435 static int belt_base_element[4] =
6437 EL_CONVEYOR_BELT_1_LEFT,
6438 EL_CONVEYOR_BELT_2_LEFT,
6439 EL_CONVEYOR_BELT_3_LEFT,
6440 EL_CONVEYOR_BELT_4_LEFT
6442 static int belt_base_active_element[4] =
6444 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6445 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6446 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6447 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6452 // set frame order for belt animation graphic according to belt direction
6453 for (i = 0; i < NUM_BELTS; i++)
6457 for (j = 0; j < NUM_BELT_PARTS; j++)
6459 int element = belt_base_active_element[belt_nr] + j;
6460 int graphic_1 = el2img(element);
6461 int graphic_2 = el2panelimg(element);
6463 if (game.belt_dir[i] == MV_LEFT)
6465 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6466 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6470 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6471 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6476 SCAN_PLAYFIELD(x, y)
6478 int element = Tile[x][y];
6480 for (i = 0; i < NUM_BELTS; i++)
6482 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6484 int e_belt_nr = getBeltNrFromBeltElement(element);
6487 if (e_belt_nr == belt_nr)
6489 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6491 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6498 static void ToggleBeltSwitch(int x, int y)
6500 static int belt_base_element[4] =
6502 EL_CONVEYOR_BELT_1_LEFT,
6503 EL_CONVEYOR_BELT_2_LEFT,
6504 EL_CONVEYOR_BELT_3_LEFT,
6505 EL_CONVEYOR_BELT_4_LEFT
6507 static int belt_base_active_element[4] =
6509 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6510 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6511 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6512 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6514 static int belt_base_switch_element[4] =
6516 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6517 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6518 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6519 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6521 static int belt_move_dir[4] =
6529 int element = Tile[x][y];
6530 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6531 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6532 int belt_dir = belt_move_dir[belt_dir_nr];
6535 if (!IS_BELT_SWITCH(element))
6538 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6539 game.belt_dir[belt_nr] = belt_dir;
6541 if (belt_dir_nr == 3)
6544 // set frame order for belt animation graphic according to belt direction
6545 for (i = 0; i < NUM_BELT_PARTS; i++)
6547 int element = belt_base_active_element[belt_nr] + i;
6548 int graphic_1 = el2img(element);
6549 int graphic_2 = el2panelimg(element);
6551 if (belt_dir == MV_LEFT)
6553 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6554 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6558 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6559 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6563 SCAN_PLAYFIELD(xx, yy)
6565 int element = Tile[xx][yy];
6567 if (IS_BELT_SWITCH(element))
6569 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6571 if (e_belt_nr == belt_nr)
6573 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6574 TEST_DrawLevelField(xx, yy);
6577 else if (IS_BELT(element) && belt_dir != MV_NONE)
6579 int e_belt_nr = getBeltNrFromBeltElement(element);
6581 if (e_belt_nr == belt_nr)
6583 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6585 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6586 TEST_DrawLevelField(xx, yy);
6589 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6591 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6593 if (e_belt_nr == belt_nr)
6595 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6597 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6598 TEST_DrawLevelField(xx, yy);
6604 static void ToggleSwitchgateSwitch(void)
6608 game.switchgate_pos = !game.switchgate_pos;
6610 SCAN_PLAYFIELD(xx, yy)
6612 int element = Tile[xx][yy];
6614 if (element == EL_SWITCHGATE_SWITCH_UP)
6616 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6617 TEST_DrawLevelField(xx, yy);
6619 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6621 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6622 TEST_DrawLevelField(xx, yy);
6624 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6626 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6627 TEST_DrawLevelField(xx, yy);
6629 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6631 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6632 TEST_DrawLevelField(xx, yy);
6634 else if (element == EL_SWITCHGATE_OPEN ||
6635 element == EL_SWITCHGATE_OPENING)
6637 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6639 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6641 else if (element == EL_SWITCHGATE_CLOSED ||
6642 element == EL_SWITCHGATE_CLOSING)
6644 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6646 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6651 static int getInvisibleActiveFromInvisibleElement(int element)
6653 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6654 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6655 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6659 static int getInvisibleFromInvisibleActiveElement(int element)
6661 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6662 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6663 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6667 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6671 SCAN_PLAYFIELD(x, y)
6673 int element = Tile[x][y];
6675 if (element == EL_LIGHT_SWITCH &&
6676 game.light_time_left > 0)
6678 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6679 TEST_DrawLevelField(x, y);
6681 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6682 game.light_time_left == 0)
6684 Tile[x][y] = EL_LIGHT_SWITCH;
6685 TEST_DrawLevelField(x, y);
6687 else if (element == EL_EMC_DRIPPER &&
6688 game.light_time_left > 0)
6690 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6691 TEST_DrawLevelField(x, y);
6693 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6694 game.light_time_left == 0)
6696 Tile[x][y] = EL_EMC_DRIPPER;
6697 TEST_DrawLevelField(x, y);
6699 else if (element == EL_INVISIBLE_STEELWALL ||
6700 element == EL_INVISIBLE_WALL ||
6701 element == EL_INVISIBLE_SAND)
6703 if (game.light_time_left > 0)
6704 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6706 TEST_DrawLevelField(x, y);
6708 // uncrumble neighbour fields, if needed
6709 if (element == EL_INVISIBLE_SAND)
6710 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6712 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6713 element == EL_INVISIBLE_WALL_ACTIVE ||
6714 element == EL_INVISIBLE_SAND_ACTIVE)
6716 if (game.light_time_left == 0)
6717 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6719 TEST_DrawLevelField(x, y);
6721 // re-crumble neighbour fields, if needed
6722 if (element == EL_INVISIBLE_SAND)
6723 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6728 static void RedrawAllInvisibleElementsForLenses(void)
6732 SCAN_PLAYFIELD(x, y)
6734 int element = Tile[x][y];
6736 if (element == EL_EMC_DRIPPER &&
6737 game.lenses_time_left > 0)
6739 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6740 TEST_DrawLevelField(x, y);
6742 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6743 game.lenses_time_left == 0)
6745 Tile[x][y] = EL_EMC_DRIPPER;
6746 TEST_DrawLevelField(x, y);
6748 else if (element == EL_INVISIBLE_STEELWALL ||
6749 element == EL_INVISIBLE_WALL ||
6750 element == EL_INVISIBLE_SAND)
6752 if (game.lenses_time_left > 0)
6753 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6755 TEST_DrawLevelField(x, y);
6757 // uncrumble neighbour fields, if needed
6758 if (element == EL_INVISIBLE_SAND)
6759 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6761 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6762 element == EL_INVISIBLE_WALL_ACTIVE ||
6763 element == EL_INVISIBLE_SAND_ACTIVE)
6765 if (game.lenses_time_left == 0)
6766 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6768 TEST_DrawLevelField(x, y);
6770 // re-crumble neighbour fields, if needed
6771 if (element == EL_INVISIBLE_SAND)
6772 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6777 static void RedrawAllInvisibleElementsForMagnifier(void)
6781 SCAN_PLAYFIELD(x, y)
6783 int element = Tile[x][y];
6785 if (element == EL_EMC_FAKE_GRASS &&
6786 game.magnify_time_left > 0)
6788 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6789 TEST_DrawLevelField(x, y);
6791 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6792 game.magnify_time_left == 0)
6794 Tile[x][y] = EL_EMC_FAKE_GRASS;
6795 TEST_DrawLevelField(x, y);
6797 else if (IS_GATE_GRAY(element) &&
6798 game.magnify_time_left > 0)
6800 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6801 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6802 IS_EM_GATE_GRAY(element) ?
6803 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6804 IS_EMC_GATE_GRAY(element) ?
6805 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6806 IS_DC_GATE_GRAY(element) ?
6807 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6809 TEST_DrawLevelField(x, y);
6811 else if (IS_GATE_GRAY_ACTIVE(element) &&
6812 game.magnify_time_left == 0)
6814 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6815 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6816 IS_EM_GATE_GRAY_ACTIVE(element) ?
6817 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6818 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6819 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6820 IS_DC_GATE_GRAY_ACTIVE(element) ?
6821 EL_DC_GATE_WHITE_GRAY :
6823 TEST_DrawLevelField(x, y);
6828 static void ToggleLightSwitch(int x, int y)
6830 int element = Tile[x][y];
6832 game.light_time_left =
6833 (element == EL_LIGHT_SWITCH ?
6834 level.time_light * FRAMES_PER_SECOND : 0);
6836 RedrawAllLightSwitchesAndInvisibleElements();
6839 static void ActivateTimegateSwitch(int x, int y)
6843 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6845 SCAN_PLAYFIELD(xx, yy)
6847 int element = Tile[xx][yy];
6849 if (element == EL_TIMEGATE_CLOSED ||
6850 element == EL_TIMEGATE_CLOSING)
6852 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6853 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6857 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6859 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6860 TEST_DrawLevelField(xx, yy);
6866 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6867 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6870 static void Impact(int x, int y)
6872 boolean last_line = (y == lev_fieldy - 1);
6873 boolean object_hit = FALSE;
6874 boolean impact = (last_line || object_hit);
6875 int element = Tile[x][y];
6876 int smashed = EL_STEELWALL;
6878 if (!last_line) // check if element below was hit
6880 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6883 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6884 MovDir[x][y + 1] != MV_DOWN ||
6885 MovPos[x][y + 1] <= TILEY / 2));
6887 // do not smash moving elements that left the smashed field in time
6888 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6889 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6892 #if USE_QUICKSAND_IMPACT_BUGFIX
6893 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6895 RemoveMovingField(x, y + 1);
6896 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6897 Tile[x][y + 2] = EL_ROCK;
6898 TEST_DrawLevelField(x, y + 2);
6903 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6905 RemoveMovingField(x, y + 1);
6906 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6907 Tile[x][y + 2] = EL_ROCK;
6908 TEST_DrawLevelField(x, y + 2);
6915 smashed = MovingOrBlocked2Element(x, y + 1);
6917 impact = (last_line || object_hit);
6920 if (!last_line && smashed == EL_ACID) // element falls into acid
6922 SplashAcid(x, y + 1);
6926 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6927 // only reset graphic animation if graphic really changes after impact
6929 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6931 ResetGfxAnimation(x, y);
6932 TEST_DrawLevelField(x, y);
6935 if (impact && CAN_EXPLODE_IMPACT(element))
6940 else if (impact && element == EL_PEARL &&
6941 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6943 ResetGfxAnimation(x, y);
6945 Tile[x][y] = EL_PEARL_BREAKING;
6946 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6949 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6951 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6956 if (impact && element == EL_AMOEBA_DROP)
6958 if (object_hit && IS_PLAYER(x, y + 1))
6959 KillPlayerUnlessEnemyProtected(x, y + 1);
6960 else if (object_hit && smashed == EL_PENGUIN)
6964 Tile[x][y] = EL_AMOEBA_GROWING;
6965 Store[x][y] = EL_AMOEBA_WET;
6967 ResetRandomAnimationValue(x, y);
6972 if (object_hit) // check which object was hit
6974 if ((CAN_PASS_MAGIC_WALL(element) &&
6975 (smashed == EL_MAGIC_WALL ||
6976 smashed == EL_BD_MAGIC_WALL)) ||
6977 (CAN_PASS_DC_MAGIC_WALL(element) &&
6978 smashed == EL_DC_MAGIC_WALL))
6981 int activated_magic_wall =
6982 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6983 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6984 EL_DC_MAGIC_WALL_ACTIVE);
6986 // activate magic wall / mill
6987 SCAN_PLAYFIELD(xx, yy)
6989 if (Tile[xx][yy] == smashed)
6990 Tile[xx][yy] = activated_magic_wall;
6993 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6994 game.magic_wall_active = TRUE;
6996 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6997 SND_MAGIC_WALL_ACTIVATING :
6998 smashed == EL_BD_MAGIC_WALL ?
6999 SND_BD_MAGIC_WALL_ACTIVATING :
7000 SND_DC_MAGIC_WALL_ACTIVATING));
7003 if (IS_PLAYER(x, y + 1))
7005 if (CAN_SMASH_PLAYER(element))
7007 KillPlayerUnlessEnemyProtected(x, y + 1);
7011 else if (smashed == EL_PENGUIN)
7013 if (CAN_SMASH_PLAYER(element))
7019 else if (element == EL_BD_DIAMOND)
7021 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
7027 else if (((element == EL_SP_INFOTRON ||
7028 element == EL_SP_ZONK) &&
7029 (smashed == EL_SP_SNIKSNAK ||
7030 smashed == EL_SP_ELECTRON ||
7031 smashed == EL_SP_DISK_ORANGE)) ||
7032 (element == EL_SP_INFOTRON &&
7033 smashed == EL_SP_DISK_YELLOW))
7038 else if (CAN_SMASH_EVERYTHING(element))
7040 if (IS_CLASSIC_ENEMY(smashed) ||
7041 CAN_EXPLODE_SMASHED(smashed))
7046 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
7048 if (smashed == EL_LAMP ||
7049 smashed == EL_LAMP_ACTIVE)
7054 else if (smashed == EL_NUT)
7056 Tile[x][y + 1] = EL_NUT_BREAKING;
7057 PlayLevelSound(x, y, SND_NUT_BREAKING);
7058 RaiseScoreElement(EL_NUT);
7061 else if (smashed == EL_PEARL)
7063 ResetGfxAnimation(x, y);
7065 Tile[x][y + 1] = EL_PEARL_BREAKING;
7066 PlayLevelSound(x, y, SND_PEARL_BREAKING);
7069 else if (smashed == EL_DIAMOND)
7071 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
7072 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
7075 else if (IS_BELT_SWITCH(smashed))
7077 ToggleBeltSwitch(x, y + 1);
7079 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
7080 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
7081 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
7082 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
7084 ToggleSwitchgateSwitch();
7086 else if (smashed == EL_LIGHT_SWITCH ||
7087 smashed == EL_LIGHT_SWITCH_ACTIVE)
7089 ToggleLightSwitch(x, y + 1);
7093 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7095 CheckElementChangeBySide(x, y + 1, smashed, element,
7096 CE_SWITCHED, CH_SIDE_TOP);
7097 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
7103 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7108 // play sound of magic wall / mill
7110 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7111 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
7112 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
7114 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7115 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
7116 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7117 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
7118 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7119 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
7124 // play sound of object that hits the ground
7125 if (last_line || object_hit)
7126 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
7129 static void TurnRoundExt(int x, int y)
7141 { 0, 0 }, { 0, 0 }, { 0, 0 },
7146 int left, right, back;
7150 { MV_DOWN, MV_UP, MV_RIGHT },
7151 { MV_UP, MV_DOWN, MV_LEFT },
7153 { MV_LEFT, MV_RIGHT, MV_DOWN },
7157 { MV_RIGHT, MV_LEFT, MV_UP }
7160 int element = Tile[x][y];
7161 int move_pattern = element_info[element].move_pattern;
7163 int old_move_dir = MovDir[x][y];
7164 int left_dir = turn[old_move_dir].left;
7165 int right_dir = turn[old_move_dir].right;
7166 int back_dir = turn[old_move_dir].back;
7168 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
7169 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
7170 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
7171 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
7173 int left_x = x + left_dx, left_y = y + left_dy;
7174 int right_x = x + right_dx, right_y = y + right_dy;
7175 int move_x = x + move_dx, move_y = y + move_dy;
7179 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
7181 TestIfBadThingTouchesOtherBadThing(x, y);
7183 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
7184 MovDir[x][y] = right_dir;
7185 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7186 MovDir[x][y] = left_dir;
7188 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7190 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7193 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7195 TestIfBadThingTouchesOtherBadThing(x, y);
7197 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7198 MovDir[x][y] = left_dir;
7199 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7200 MovDir[x][y] = right_dir;
7202 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7204 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7207 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7209 TestIfBadThingTouchesOtherBadThing(x, y);
7211 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7212 MovDir[x][y] = left_dir;
7213 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7214 MovDir[x][y] = right_dir;
7216 if (MovDir[x][y] != old_move_dir)
7219 else if (element == EL_YAMYAM)
7221 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7222 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7224 if (can_turn_left && can_turn_right)
7225 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7226 else if (can_turn_left)
7227 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7228 else if (can_turn_right)
7229 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7231 MovDir[x][y] = back_dir;
7233 MovDelay[x][y] = 16 + 16 * RND(3);
7235 else if (element == EL_DARK_YAMYAM)
7237 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7239 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7242 if (can_turn_left && can_turn_right)
7243 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7244 else if (can_turn_left)
7245 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7246 else if (can_turn_right)
7247 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7249 MovDir[x][y] = back_dir;
7251 MovDelay[x][y] = 16 + 16 * RND(3);
7253 else if (element == EL_PACMAN)
7255 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7256 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7258 if (can_turn_left && can_turn_right)
7259 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7260 else if (can_turn_left)
7261 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7262 else if (can_turn_right)
7263 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7265 MovDir[x][y] = back_dir;
7267 MovDelay[x][y] = 6 + RND(40);
7269 else if (element == EL_PIG)
7271 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7272 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7273 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7274 boolean should_turn_left, should_turn_right, should_move_on;
7276 int rnd = RND(rnd_value);
7278 should_turn_left = (can_turn_left &&
7280 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7281 y + back_dy + left_dy)));
7282 should_turn_right = (can_turn_right &&
7284 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7285 y + back_dy + right_dy)));
7286 should_move_on = (can_move_on &&
7289 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7290 y + move_dy + left_dy) ||
7291 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7292 y + move_dy + right_dy)));
7294 if (should_turn_left || should_turn_right || should_move_on)
7296 if (should_turn_left && should_turn_right && should_move_on)
7297 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7298 rnd < 2 * rnd_value / 3 ? right_dir :
7300 else if (should_turn_left && should_turn_right)
7301 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7302 else if (should_turn_left && should_move_on)
7303 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7304 else if (should_turn_right && should_move_on)
7305 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7306 else if (should_turn_left)
7307 MovDir[x][y] = left_dir;
7308 else if (should_turn_right)
7309 MovDir[x][y] = right_dir;
7310 else if (should_move_on)
7311 MovDir[x][y] = old_move_dir;
7313 else if (can_move_on && rnd > rnd_value / 8)
7314 MovDir[x][y] = old_move_dir;
7315 else if (can_turn_left && can_turn_right)
7316 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7317 else if (can_turn_left && rnd > rnd_value / 8)
7318 MovDir[x][y] = left_dir;
7319 else if (can_turn_right && rnd > rnd_value/8)
7320 MovDir[x][y] = right_dir;
7322 MovDir[x][y] = back_dir;
7324 xx = x + move_xy[MovDir[x][y]].dx;
7325 yy = y + move_xy[MovDir[x][y]].dy;
7327 if (!IN_LEV_FIELD(xx, yy) ||
7328 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7329 MovDir[x][y] = old_move_dir;
7333 else if (element == EL_DRAGON)
7335 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7336 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7337 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7339 int rnd = RND(rnd_value);
7341 if (can_move_on && rnd > rnd_value / 8)
7342 MovDir[x][y] = old_move_dir;
7343 else if (can_turn_left && can_turn_right)
7344 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7345 else if (can_turn_left && rnd > rnd_value / 8)
7346 MovDir[x][y] = left_dir;
7347 else if (can_turn_right && rnd > rnd_value / 8)
7348 MovDir[x][y] = right_dir;
7350 MovDir[x][y] = back_dir;
7352 xx = x + move_xy[MovDir[x][y]].dx;
7353 yy = y + move_xy[MovDir[x][y]].dy;
7355 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7356 MovDir[x][y] = old_move_dir;
7360 else if (element == EL_MOLE)
7362 boolean can_move_on =
7363 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7364 IS_AMOEBOID(Tile[move_x][move_y]) ||
7365 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7368 boolean can_turn_left =
7369 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7370 IS_AMOEBOID(Tile[left_x][left_y])));
7372 boolean can_turn_right =
7373 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7374 IS_AMOEBOID(Tile[right_x][right_y])));
7376 if (can_turn_left && can_turn_right)
7377 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7378 else if (can_turn_left)
7379 MovDir[x][y] = left_dir;
7381 MovDir[x][y] = right_dir;
7384 if (MovDir[x][y] != old_move_dir)
7387 else if (element == EL_BALLOON)
7389 MovDir[x][y] = game.wind_direction;
7392 else if (element == EL_SPRING)
7394 if (MovDir[x][y] & MV_HORIZONTAL)
7396 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7397 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7399 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7400 ResetGfxAnimation(move_x, move_y);
7401 TEST_DrawLevelField(move_x, move_y);
7403 MovDir[x][y] = back_dir;
7405 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7406 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7407 MovDir[x][y] = MV_NONE;
7412 else if (element == EL_ROBOT ||
7413 element == EL_SATELLITE ||
7414 element == EL_PENGUIN ||
7415 element == EL_EMC_ANDROID)
7417 int attr_x = -1, attr_y = -1;
7419 if (game.all_players_gone)
7421 attr_x = game.exit_x;
7422 attr_y = game.exit_y;
7428 for (i = 0; i < MAX_PLAYERS; i++)
7430 struct PlayerInfo *player = &stored_player[i];
7431 int jx = player->jx, jy = player->jy;
7433 if (!player->active)
7437 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7445 if (element == EL_ROBOT &&
7446 game.robot_wheel_x >= 0 &&
7447 game.robot_wheel_y >= 0 &&
7448 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7449 game.engine_version < VERSION_IDENT(3,1,0,0)))
7451 attr_x = game.robot_wheel_x;
7452 attr_y = game.robot_wheel_y;
7455 if (element == EL_PENGUIN)
7458 struct XY *xy = xy_topdown;
7460 for (i = 0; i < NUM_DIRECTIONS; i++)
7462 int ex = x + xy[i].x;
7463 int ey = y + xy[i].y;
7465 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7466 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7467 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7468 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7477 MovDir[x][y] = MV_NONE;
7479 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7480 else if (attr_x > x)
7481 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7483 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7484 else if (attr_y > y)
7485 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7487 if (element == EL_ROBOT)
7491 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7492 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7493 Moving2Blocked(x, y, &newx, &newy);
7495 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7496 MovDelay[x][y] = 8 + 8 * !RND(3);
7498 MovDelay[x][y] = 16;
7500 else if (element == EL_PENGUIN)
7506 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7508 boolean first_horiz = RND(2);
7509 int new_move_dir = MovDir[x][y];
7512 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7513 Moving2Blocked(x, y, &newx, &newy);
7515 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7519 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7520 Moving2Blocked(x, y, &newx, &newy);
7522 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7525 MovDir[x][y] = old_move_dir;
7529 else if (element == EL_SATELLITE)
7535 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7537 boolean first_horiz = RND(2);
7538 int new_move_dir = MovDir[x][y];
7541 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7542 Moving2Blocked(x, y, &newx, &newy);
7544 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7548 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7549 Moving2Blocked(x, y, &newx, &newy);
7551 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7554 MovDir[x][y] = old_move_dir;
7558 else if (element == EL_EMC_ANDROID)
7560 static int check_pos[16] =
7562 -1, // 0 => (invalid)
7565 -1, // 3 => (invalid)
7567 0, // 5 => MV_LEFT | MV_UP
7568 2, // 6 => MV_RIGHT | MV_UP
7569 -1, // 7 => (invalid)
7571 6, // 9 => MV_LEFT | MV_DOWN
7572 4, // 10 => MV_RIGHT | MV_DOWN
7573 -1, // 11 => (invalid)
7574 -1, // 12 => (invalid)
7575 -1, // 13 => (invalid)
7576 -1, // 14 => (invalid)
7577 -1, // 15 => (invalid)
7585 { -1, -1, MV_LEFT | MV_UP },
7587 { +1, -1, MV_RIGHT | MV_UP },
7588 { +1, 0, MV_RIGHT },
7589 { +1, +1, MV_RIGHT | MV_DOWN },
7591 { -1, +1, MV_LEFT | MV_DOWN },
7594 int start_pos, check_order;
7595 boolean can_clone = FALSE;
7598 // check if there is any free field around current position
7599 for (i = 0; i < 8; i++)
7601 int newx = x + check_xy[i].dx;
7602 int newy = y + check_xy[i].dy;
7604 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7612 if (can_clone) // randomly find an element to clone
7616 start_pos = check_pos[RND(8)];
7617 check_order = (RND(2) ? -1 : +1);
7619 for (i = 0; i < 8; i++)
7621 int pos_raw = start_pos + i * check_order;
7622 int pos = (pos_raw + 8) % 8;
7623 int newx = x + check_xy[pos].dx;
7624 int newy = y + check_xy[pos].dy;
7626 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7628 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7629 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7631 Store[x][y] = Tile[newx][newy];
7640 if (can_clone) // randomly find a direction to move
7644 start_pos = check_pos[RND(8)];
7645 check_order = (RND(2) ? -1 : +1);
7647 for (i = 0; i < 8; i++)
7649 int pos_raw = start_pos + i * check_order;
7650 int pos = (pos_raw + 8) % 8;
7651 int newx = x + check_xy[pos].dx;
7652 int newy = y + check_xy[pos].dy;
7653 int new_move_dir = check_xy[pos].dir;
7655 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7657 MovDir[x][y] = new_move_dir;
7658 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7667 if (can_clone) // cloning and moving successful
7670 // cannot clone -- try to move towards player
7672 start_pos = check_pos[MovDir[x][y] & 0x0f];
7673 check_order = (RND(2) ? -1 : +1);
7675 for (i = 0; i < 3; i++)
7677 // first check start_pos, then previous/next or (next/previous) pos
7678 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7679 int pos = (pos_raw + 8) % 8;
7680 int newx = x + check_xy[pos].dx;
7681 int newy = y + check_xy[pos].dy;
7682 int new_move_dir = check_xy[pos].dir;
7684 if (IS_PLAYER(newx, newy))
7687 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7689 MovDir[x][y] = new_move_dir;
7690 MovDelay[x][y] = level.android_move_time * 8 + 1;
7697 else if (move_pattern == MV_TURNING_LEFT ||
7698 move_pattern == MV_TURNING_RIGHT ||
7699 move_pattern == MV_TURNING_LEFT_RIGHT ||
7700 move_pattern == MV_TURNING_RIGHT_LEFT ||
7701 move_pattern == MV_TURNING_RANDOM ||
7702 move_pattern == MV_ALL_DIRECTIONS)
7704 boolean can_turn_left =
7705 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7706 boolean can_turn_right =
7707 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y);
7709 if (element_info[element].move_stepsize == 0) // "not moving"
7712 if (move_pattern == MV_TURNING_LEFT)
7713 MovDir[x][y] = left_dir;
7714 else if (move_pattern == MV_TURNING_RIGHT)
7715 MovDir[x][y] = right_dir;
7716 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7717 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7718 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7719 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7720 else if (move_pattern == MV_TURNING_RANDOM)
7721 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7722 can_turn_right && !can_turn_left ? right_dir :
7723 RND(2) ? left_dir : right_dir);
7724 else if (can_turn_left && can_turn_right)
7725 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7726 else if (can_turn_left)
7727 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7728 else if (can_turn_right)
7729 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7731 MovDir[x][y] = back_dir;
7733 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7735 else if (move_pattern == MV_HORIZONTAL ||
7736 move_pattern == MV_VERTICAL)
7738 if (move_pattern & old_move_dir)
7739 MovDir[x][y] = back_dir;
7740 else if (move_pattern == MV_HORIZONTAL)
7741 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7742 else if (move_pattern == MV_VERTICAL)
7743 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7745 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7747 else if (move_pattern & MV_ANY_DIRECTION)
7749 MovDir[x][y] = move_pattern;
7750 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7752 else if (move_pattern & MV_WIND_DIRECTION)
7754 MovDir[x][y] = game.wind_direction;
7755 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7757 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7759 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7760 MovDir[x][y] = left_dir;
7761 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7762 MovDir[x][y] = right_dir;
7764 if (MovDir[x][y] != old_move_dir)
7765 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7767 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7769 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7770 MovDir[x][y] = right_dir;
7771 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7772 MovDir[x][y] = left_dir;
7774 if (MovDir[x][y] != old_move_dir)
7775 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7777 else if (move_pattern == MV_TOWARDS_PLAYER ||
7778 move_pattern == MV_AWAY_FROM_PLAYER)
7780 int attr_x = -1, attr_y = -1;
7782 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7784 if (game.all_players_gone)
7786 attr_x = game.exit_x;
7787 attr_y = game.exit_y;
7793 for (i = 0; i < MAX_PLAYERS; i++)
7795 struct PlayerInfo *player = &stored_player[i];
7796 int jx = player->jx, jy = player->jy;
7798 if (!player->active)
7802 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7810 MovDir[x][y] = MV_NONE;
7812 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7813 else if (attr_x > x)
7814 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7816 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7817 else if (attr_y > y)
7818 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7820 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7822 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7824 boolean first_horiz = RND(2);
7825 int new_move_dir = MovDir[x][y];
7827 if (element_info[element].move_stepsize == 0) // "not moving"
7829 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7830 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7836 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7837 Moving2Blocked(x, y, &newx, &newy);
7839 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7843 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7844 Moving2Blocked(x, y, &newx, &newy);
7846 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7849 MovDir[x][y] = old_move_dir;
7852 else if (move_pattern == MV_WHEN_PUSHED ||
7853 move_pattern == MV_WHEN_DROPPED)
7855 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7856 MovDir[x][y] = MV_NONE;
7860 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7862 struct XY *test_xy = xy_topdown;
7863 static int test_dir[4] =
7870 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7871 int move_preference = -1000000; // start with very low preference
7872 int new_move_dir = MV_NONE;
7873 int start_test = RND(4);
7876 for (i = 0; i < NUM_DIRECTIONS; i++)
7878 int j = (start_test + i) % 4;
7879 int move_dir = test_dir[j];
7880 int move_dir_preference;
7882 xx = x + test_xy[j].x;
7883 yy = y + test_xy[j].y;
7885 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7886 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7888 new_move_dir = move_dir;
7893 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7896 move_dir_preference = -1 * RunnerVisit[xx][yy];
7897 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7898 move_dir_preference = PlayerVisit[xx][yy];
7900 if (move_dir_preference > move_preference)
7902 // prefer field that has not been visited for the longest time
7903 move_preference = move_dir_preference;
7904 new_move_dir = move_dir;
7906 else if (move_dir_preference == move_preference &&
7907 move_dir == old_move_dir)
7909 // prefer last direction when all directions are preferred equally
7910 move_preference = move_dir_preference;
7911 new_move_dir = move_dir;
7915 MovDir[x][y] = new_move_dir;
7916 if (old_move_dir != new_move_dir)
7917 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7921 static void TurnRound(int x, int y)
7923 int direction = MovDir[x][y];
7927 GfxDir[x][y] = MovDir[x][y];
7929 if (direction != MovDir[x][y])
7933 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7935 ResetGfxFrame(x, y);
7938 static boolean JustBeingPushed(int x, int y)
7942 for (i = 0; i < MAX_PLAYERS; i++)
7944 struct PlayerInfo *player = &stored_player[i];
7946 if (player->active && player->is_pushing && player->MovPos)
7948 int next_jx = player->jx + (player->jx - player->last_jx);
7949 int next_jy = player->jy + (player->jy - player->last_jy);
7951 if (x == next_jx && y == next_jy)
7959 static void StartMoving(int x, int y)
7961 boolean started_moving = FALSE; // some elements can fall _and_ move
7962 int element = Tile[x][y];
7967 if (MovDelay[x][y] == 0)
7968 GfxAction[x][y] = ACTION_DEFAULT;
7970 if (CAN_FALL(element) && y < lev_fieldy - 1)
7972 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7973 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7974 if (JustBeingPushed(x, y))
7977 if (element == EL_QUICKSAND_FULL)
7979 if (IS_FREE(x, y + 1))
7981 InitMovingField(x, y, MV_DOWN);
7982 started_moving = TRUE;
7984 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7985 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7986 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7987 Store[x][y] = EL_ROCK;
7989 Store[x][y] = EL_ROCK;
7992 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7994 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7996 if (!MovDelay[x][y])
7998 MovDelay[x][y] = TILEY + 1;
8000 ResetGfxAnimation(x, y);
8001 ResetGfxAnimation(x, y + 1);
8006 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
8007 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8014 Tile[x][y] = EL_QUICKSAND_EMPTY;
8015 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8016 Store[x][y + 1] = Store[x][y];
8019 PlayLevelSoundAction(x, y, ACTION_FILLING);
8021 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8023 if (!MovDelay[x][y])
8025 MovDelay[x][y] = TILEY + 1;
8027 ResetGfxAnimation(x, y);
8028 ResetGfxAnimation(x, y + 1);
8033 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
8034 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8041 Tile[x][y] = EL_QUICKSAND_EMPTY;
8042 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8043 Store[x][y + 1] = Store[x][y];
8046 PlayLevelSoundAction(x, y, ACTION_FILLING);
8049 else if (element == EL_QUICKSAND_FAST_FULL)
8051 if (IS_FREE(x, y + 1))
8053 InitMovingField(x, y, MV_DOWN);
8054 started_moving = TRUE;
8056 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
8057 #if USE_QUICKSAND_BD_ROCK_BUGFIX
8058 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
8059 Store[x][y] = EL_ROCK;
8061 Store[x][y] = EL_ROCK;
8064 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
8066 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8068 if (!MovDelay[x][y])
8070 MovDelay[x][y] = TILEY + 1;
8072 ResetGfxAnimation(x, y);
8073 ResetGfxAnimation(x, y + 1);
8078 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8079 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8086 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8087 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8088 Store[x][y + 1] = Store[x][y];
8091 PlayLevelSoundAction(x, y, ACTION_FILLING);
8093 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8095 if (!MovDelay[x][y])
8097 MovDelay[x][y] = TILEY + 1;
8099 ResetGfxAnimation(x, y);
8100 ResetGfxAnimation(x, y + 1);
8105 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8106 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8113 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8114 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8115 Store[x][y + 1] = Store[x][y];
8118 PlayLevelSoundAction(x, y, ACTION_FILLING);
8121 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8122 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8124 InitMovingField(x, y, MV_DOWN);
8125 started_moving = TRUE;
8127 Tile[x][y] = EL_QUICKSAND_FILLING;
8128 Store[x][y] = element;
8130 PlayLevelSoundAction(x, y, ACTION_FILLING);
8132 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8133 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8135 InitMovingField(x, y, MV_DOWN);
8136 started_moving = TRUE;
8138 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
8139 Store[x][y] = element;
8141 PlayLevelSoundAction(x, y, ACTION_FILLING);
8143 else if (element == EL_MAGIC_WALL_FULL)
8145 if (IS_FREE(x, y + 1))
8147 InitMovingField(x, y, MV_DOWN);
8148 started_moving = TRUE;
8150 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
8151 Store[x][y] = EL_CHANGED(Store[x][y]);
8153 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
8155 if (!MovDelay[x][y])
8156 MovDelay[x][y] = TILEY / 4 + 1;
8165 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
8166 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
8167 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8171 else if (element == EL_BD_MAGIC_WALL_FULL)
8173 if (IS_FREE(x, y + 1))
8175 InitMovingField(x, y, MV_DOWN);
8176 started_moving = TRUE;
8178 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8179 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8181 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8183 if (!MovDelay[x][y])
8184 MovDelay[x][y] = TILEY / 4 + 1;
8193 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8194 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8195 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8199 else if (element == EL_DC_MAGIC_WALL_FULL)
8201 if (IS_FREE(x, y + 1))
8203 InitMovingField(x, y, MV_DOWN);
8204 started_moving = TRUE;
8206 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8207 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8209 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8211 if (!MovDelay[x][y])
8212 MovDelay[x][y] = TILEY / 4 + 1;
8221 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8222 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8223 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8227 else if ((CAN_PASS_MAGIC_WALL(element) &&
8228 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8229 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8230 (CAN_PASS_DC_MAGIC_WALL(element) &&
8231 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8234 InitMovingField(x, y, MV_DOWN);
8235 started_moving = TRUE;
8238 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8239 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8240 EL_DC_MAGIC_WALL_FILLING);
8241 Store[x][y] = element;
8243 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8245 SplashAcid(x, y + 1);
8247 InitMovingField(x, y, MV_DOWN);
8248 started_moving = TRUE;
8250 Store[x][y] = EL_ACID;
8253 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8254 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8255 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8256 CAN_FALL(element) && WasJustFalling[x][y] &&
8257 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8259 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8260 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8261 (Tile[x][y + 1] == EL_BLOCKED)))
8263 /* this is needed for a special case not covered by calling "Impact()"
8264 from "ContinueMoving()": if an element moves to a tile directly below
8265 another element which was just falling on that tile (which was empty
8266 in the previous frame), the falling element above would just stop
8267 instead of smashing the element below (in previous version, the above
8268 element was just checked for "moving" instead of "falling", resulting
8269 in incorrect smashes caused by horizontal movement of the above
8270 element; also, the case of the player being the element to smash was
8271 simply not covered here... :-/ ) */
8273 CheckCollision[x][y] = 0;
8274 CheckImpact[x][y] = 0;
8278 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8280 if (MovDir[x][y] == MV_NONE)
8282 InitMovingField(x, y, MV_DOWN);
8283 started_moving = TRUE;
8286 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8288 if (WasJustFalling[x][y]) // prevent animation from being restarted
8289 MovDir[x][y] = MV_DOWN;
8291 InitMovingField(x, y, MV_DOWN);
8292 started_moving = TRUE;
8294 else if (element == EL_AMOEBA_DROP)
8296 Tile[x][y] = EL_AMOEBA_GROWING;
8297 Store[x][y] = EL_AMOEBA_WET;
8299 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8300 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8301 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8302 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8304 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8305 (IS_FREE(x - 1, y + 1) ||
8306 Tile[x - 1][y + 1] == EL_ACID));
8307 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8308 (IS_FREE(x + 1, y + 1) ||
8309 Tile[x + 1][y + 1] == EL_ACID));
8310 boolean can_fall_any = (can_fall_left || can_fall_right);
8311 boolean can_fall_both = (can_fall_left && can_fall_right);
8312 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8314 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8316 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8317 can_fall_right = FALSE;
8318 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8319 can_fall_left = FALSE;
8320 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8321 can_fall_right = FALSE;
8322 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8323 can_fall_left = FALSE;
8325 can_fall_any = (can_fall_left || can_fall_right);
8326 can_fall_both = FALSE;
8331 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8332 can_fall_right = FALSE; // slip down on left side
8334 can_fall_left = !(can_fall_right = RND(2));
8336 can_fall_both = FALSE;
8341 // if not determined otherwise, prefer left side for slipping down
8342 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8343 started_moving = TRUE;
8346 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8348 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8349 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8350 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8351 int belt_dir = game.belt_dir[belt_nr];
8353 if ((belt_dir == MV_LEFT && left_is_free) ||
8354 (belt_dir == MV_RIGHT && right_is_free))
8356 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8358 InitMovingField(x, y, belt_dir);
8359 started_moving = TRUE;
8361 Pushed[x][y] = TRUE;
8362 Pushed[nextx][y] = TRUE;
8364 GfxAction[x][y] = ACTION_DEFAULT;
8368 MovDir[x][y] = 0; // if element was moving, stop it
8373 // not "else if" because of elements that can fall and move (EL_SPRING)
8374 if (CAN_MOVE(element) && !started_moving)
8376 int move_pattern = element_info[element].move_pattern;
8379 Moving2Blocked(x, y, &newx, &newy);
8381 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8384 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8385 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8387 WasJustMoving[x][y] = 0;
8388 CheckCollision[x][y] = 0;
8390 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8392 if (Tile[x][y] != element) // element has changed
8396 if (!MovDelay[x][y]) // start new movement phase
8398 // all objects that can change their move direction after each step
8399 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8401 if (element != EL_YAMYAM &&
8402 element != EL_DARK_YAMYAM &&
8403 element != EL_PACMAN &&
8404 !(move_pattern & MV_ANY_DIRECTION) &&
8405 move_pattern != MV_TURNING_LEFT &&
8406 move_pattern != MV_TURNING_RIGHT &&
8407 move_pattern != MV_TURNING_LEFT_RIGHT &&
8408 move_pattern != MV_TURNING_RIGHT_LEFT &&
8409 move_pattern != MV_TURNING_RANDOM)
8413 if (MovDelay[x][y] && (element == EL_BUG ||
8414 element == EL_SPACESHIP ||
8415 element == EL_SP_SNIKSNAK ||
8416 element == EL_SP_ELECTRON ||
8417 element == EL_MOLE))
8418 TEST_DrawLevelField(x, y);
8422 if (MovDelay[x][y]) // wait some time before next movement
8426 if (element == EL_ROBOT ||
8427 element == EL_YAMYAM ||
8428 element == EL_DARK_YAMYAM)
8430 DrawLevelElementAnimationIfNeeded(x, y, element);
8431 PlayLevelSoundAction(x, y, ACTION_WAITING);
8433 else if (element == EL_SP_ELECTRON)
8434 DrawLevelElementAnimationIfNeeded(x, y, element);
8435 else if (element == EL_DRAGON)
8438 int dir = MovDir[x][y];
8439 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8440 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8441 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8442 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8443 dir == MV_UP ? IMG_FLAMES_1_UP :
8444 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8445 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8447 GfxAction[x][y] = ACTION_ATTACKING;
8449 if (IS_PLAYER(x, y))
8450 DrawPlayerField(x, y);
8452 TEST_DrawLevelField(x, y);
8454 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8456 for (i = 1; i <= 3; i++)
8458 int xx = x + i * dx;
8459 int yy = y + i * dy;
8460 int sx = SCREENX(xx);
8461 int sy = SCREENY(yy);
8462 int flame_graphic = graphic + (i - 1);
8464 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8469 int flamed = MovingOrBlocked2Element(xx, yy);
8471 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8474 RemoveMovingField(xx, yy);
8476 ChangeDelay[xx][yy] = 0;
8478 Tile[xx][yy] = EL_FLAMES;
8480 if (IN_SCR_FIELD(sx, sy))
8482 TEST_DrawLevelFieldCrumbled(xx, yy);
8483 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8488 if (Tile[xx][yy] == EL_FLAMES)
8489 Tile[xx][yy] = EL_EMPTY;
8490 TEST_DrawLevelField(xx, yy);
8495 if (MovDelay[x][y]) // element still has to wait some time
8497 PlayLevelSoundAction(x, y, ACTION_WAITING);
8503 // now make next step
8505 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8507 if (DONT_COLLIDE_WITH(element) &&
8508 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8509 !PLAYER_ENEMY_PROTECTED(newx, newy))
8511 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8516 else if (CAN_MOVE_INTO_ACID(element) &&
8517 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8518 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8519 (MovDir[x][y] == MV_DOWN ||
8520 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8522 SplashAcid(newx, newy);
8523 Store[x][y] = EL_ACID;
8525 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8527 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8528 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8529 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8530 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8533 TEST_DrawLevelField(x, y);
8535 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8536 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8537 DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0);
8539 game.friends_still_needed--;
8540 if (!game.friends_still_needed &&
8542 game.all_players_gone)
8547 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8549 if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING)
8550 TEST_DrawLevelField(newx, newy);
8552 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8554 else if (!IS_FREE(newx, newy))
8556 GfxAction[x][y] = ACTION_WAITING;
8558 if (IS_PLAYER(x, y))
8559 DrawPlayerField(x, y);
8561 TEST_DrawLevelField(x, y);
8566 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8568 if (IS_FOOD_PIG(Tile[newx][newy]))
8570 if (IS_MOVING(newx, newy))
8571 RemoveMovingField(newx, newy);
8574 Tile[newx][newy] = EL_EMPTY;
8575 TEST_DrawLevelField(newx, newy);
8578 PlayLevelSound(x, y, SND_PIG_DIGGING);
8580 else if (!IS_FREE(newx, newy))
8582 if (IS_PLAYER(x, y))
8583 DrawPlayerField(x, y);
8585 TEST_DrawLevelField(x, y);
8590 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8592 if (Store[x][y] != EL_EMPTY)
8594 boolean can_clone = FALSE;
8597 // check if element to clone is still there
8598 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8600 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8608 // cannot clone or target field not free anymore -- do not clone
8609 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8610 Store[x][y] = EL_EMPTY;
8613 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8615 if (IS_MV_DIAGONAL(MovDir[x][y]))
8617 int diagonal_move_dir = MovDir[x][y];
8618 int stored = Store[x][y];
8619 int change_delay = 8;
8622 // android is moving diagonally
8624 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8626 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8627 GfxElement[x][y] = EL_EMC_ANDROID;
8628 GfxAction[x][y] = ACTION_SHRINKING;
8629 GfxDir[x][y] = diagonal_move_dir;
8630 ChangeDelay[x][y] = change_delay;
8632 if (Store[x][y] == EL_EMPTY)
8633 Store[x][y] = GfxElementEmpty[x][y];
8635 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8638 DrawLevelGraphicAnimation(x, y, graphic);
8639 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8641 if (Tile[newx][newy] == EL_ACID)
8643 SplashAcid(newx, newy);
8648 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8650 Store[newx][newy] = EL_EMC_ANDROID;
8651 GfxElement[newx][newy] = EL_EMC_ANDROID;
8652 GfxAction[newx][newy] = ACTION_GROWING;
8653 GfxDir[newx][newy] = diagonal_move_dir;
8654 ChangeDelay[newx][newy] = change_delay;
8656 graphic = el_act_dir2img(GfxElement[newx][newy],
8657 GfxAction[newx][newy], GfxDir[newx][newy]);
8659 DrawLevelGraphicAnimation(newx, newy, graphic);
8660 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8666 Tile[newx][newy] = EL_EMPTY;
8667 TEST_DrawLevelField(newx, newy);
8669 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8672 else if (!IS_FREE(newx, newy))
8677 else if (IS_CUSTOM_ELEMENT(element) &&
8678 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8680 if (!DigFieldByCE(newx, newy, element))
8683 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8685 RunnerVisit[x][y] = FrameCounter;
8686 PlayerVisit[x][y] /= 8; // expire player visit path
8689 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8691 if (!IS_FREE(newx, newy))
8693 if (IS_PLAYER(x, y))
8694 DrawPlayerField(x, y);
8696 TEST_DrawLevelField(x, y);
8702 boolean wanna_flame = !RND(10);
8703 int dx = newx - x, dy = newy - y;
8704 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8705 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8706 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8707 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8708 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8709 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8712 IS_CLASSIC_ENEMY(element1) ||
8713 IS_CLASSIC_ENEMY(element2)) &&
8714 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8715 element1 != EL_FLAMES && element2 != EL_FLAMES)
8717 ResetGfxAnimation(x, y);
8718 GfxAction[x][y] = ACTION_ATTACKING;
8720 if (IS_PLAYER(x, y))
8721 DrawPlayerField(x, y);
8723 TEST_DrawLevelField(x, y);
8725 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8727 MovDelay[x][y] = 50;
8729 Tile[newx][newy] = EL_FLAMES;
8730 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8731 Tile[newx1][newy1] = EL_FLAMES;
8732 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8733 Tile[newx2][newy2] = EL_FLAMES;
8739 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8740 Tile[newx][newy] == EL_DIAMOND)
8742 if (IS_MOVING(newx, newy))
8743 RemoveMovingField(newx, newy);
8746 Tile[newx][newy] = EL_EMPTY;
8747 TEST_DrawLevelField(newx, newy);
8750 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8752 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8753 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8755 if (AmoebaNr[newx][newy])
8757 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8758 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8759 Tile[newx][newy] == EL_BD_AMOEBA)
8760 AmoebaCnt[AmoebaNr[newx][newy]]--;
8763 if (IS_MOVING(newx, newy))
8765 RemoveMovingField(newx, newy);
8769 Tile[newx][newy] = EL_EMPTY;
8770 TEST_DrawLevelField(newx, newy);
8773 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8775 else if ((element == EL_PACMAN || element == EL_MOLE)
8776 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8778 if (AmoebaNr[newx][newy])
8780 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8781 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8782 Tile[newx][newy] == EL_BD_AMOEBA)
8783 AmoebaCnt[AmoebaNr[newx][newy]]--;
8786 if (element == EL_MOLE)
8788 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8789 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8791 ResetGfxAnimation(x, y);
8792 GfxAction[x][y] = ACTION_DIGGING;
8793 TEST_DrawLevelField(x, y);
8795 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8797 return; // wait for shrinking amoeba
8799 else // element == EL_PACMAN
8801 Tile[newx][newy] = EL_EMPTY;
8802 TEST_DrawLevelField(newx, newy);
8803 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8806 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8807 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8808 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8810 // wait for shrinking amoeba to completely disappear
8813 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8815 // object was running against a wall
8819 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8820 DrawLevelElementAnimation(x, y, element);
8822 if (DONT_TOUCH(element))
8823 TestIfBadThingTouchesPlayer(x, y);
8828 InitMovingField(x, y, MovDir[x][y]);
8830 PlayLevelSoundAction(x, y, ACTION_MOVING);
8834 ContinueMoving(x, y);
8837 void ContinueMoving(int x, int y)
8839 int element = Tile[x][y];
8840 struct ElementInfo *ei = &element_info[element];
8841 int direction = MovDir[x][y];
8842 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8843 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8844 int newx = x + dx, newy = y + dy;
8845 int stored = Store[x][y];
8846 int stored_new = Store[newx][newy];
8847 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8848 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8849 boolean last_line = (newy == lev_fieldy - 1);
8850 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8852 if (pushed_by_player) // special case: moving object pushed by player
8854 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos));
8856 else if (use_step_delay) // special case: moving object has step delay
8858 if (!MovDelay[x][y])
8859 MovPos[x][y] += getElementMoveStepsize(x, y);
8864 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8868 TEST_DrawLevelField(x, y);
8870 return; // element is still waiting
8873 else // normal case: generically moving object
8875 MovPos[x][y] += getElementMoveStepsize(x, y);
8878 if (ABS(MovPos[x][y]) < TILEX)
8880 TEST_DrawLevelField(x, y);
8882 return; // element is still moving
8885 // element reached destination field
8887 Tile[x][y] = EL_EMPTY;
8888 Tile[newx][newy] = element;
8889 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8891 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8893 element = Tile[newx][newy] = EL_ACID;
8895 else if (element == EL_MOLE)
8897 Tile[x][y] = EL_SAND;
8899 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8901 else if (element == EL_QUICKSAND_FILLING)
8903 element = Tile[newx][newy] = get_next_element(element);
8904 Store[newx][newy] = Store[x][y];
8906 else if (element == EL_QUICKSAND_EMPTYING)
8908 Tile[x][y] = get_next_element(element);
8909 element = Tile[newx][newy] = Store[x][y];
8911 else if (element == EL_QUICKSAND_FAST_FILLING)
8913 element = Tile[newx][newy] = get_next_element(element);
8914 Store[newx][newy] = Store[x][y];
8916 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8918 Tile[x][y] = get_next_element(element);
8919 element = Tile[newx][newy] = Store[x][y];
8921 else if (element == EL_MAGIC_WALL_FILLING)
8923 element = Tile[newx][newy] = get_next_element(element);
8924 if (!game.magic_wall_active)
8925 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8926 Store[newx][newy] = Store[x][y];
8928 else if (element == EL_MAGIC_WALL_EMPTYING)
8930 Tile[x][y] = get_next_element(element);
8931 if (!game.magic_wall_active)
8932 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8933 element = Tile[newx][newy] = Store[x][y];
8935 InitField(newx, newy, FALSE);
8937 else if (element == EL_BD_MAGIC_WALL_FILLING)
8939 element = Tile[newx][newy] = get_next_element(element);
8940 if (!game.magic_wall_active)
8941 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8942 Store[newx][newy] = Store[x][y];
8944 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8946 Tile[x][y] = get_next_element(element);
8947 if (!game.magic_wall_active)
8948 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8949 element = Tile[newx][newy] = Store[x][y];
8951 InitField(newx, newy, FALSE);
8953 else if (element == EL_DC_MAGIC_WALL_FILLING)
8955 element = Tile[newx][newy] = get_next_element(element);
8956 if (!game.magic_wall_active)
8957 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8958 Store[newx][newy] = Store[x][y];
8960 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8962 Tile[x][y] = get_next_element(element);
8963 if (!game.magic_wall_active)
8964 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8965 element = Tile[newx][newy] = Store[x][y];
8967 InitField(newx, newy, FALSE);
8969 else if (element == EL_AMOEBA_DROPPING)
8971 Tile[x][y] = get_next_element(element);
8972 element = Tile[newx][newy] = Store[x][y];
8974 else if (element == EL_SOKOBAN_OBJECT)
8977 Tile[x][y] = Back[x][y];
8979 if (Back[newx][newy])
8980 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8982 Back[x][y] = Back[newx][newy] = 0;
8985 Store[x][y] = EL_EMPTY;
8990 MovDelay[newx][newy] = 0;
8992 if (CAN_CHANGE_OR_HAS_ACTION(element))
8994 // copy element change control values to new field
8995 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8996 ChangePage[newx][newy] = ChangePage[x][y];
8997 ChangeCount[newx][newy] = ChangeCount[x][y];
8998 ChangeEvent[newx][newy] = ChangeEvent[x][y];
9001 CustomValue[newx][newy] = CustomValue[x][y];
9003 ChangeDelay[x][y] = 0;
9004 ChangePage[x][y] = -1;
9005 ChangeCount[x][y] = 0;
9006 ChangeEvent[x][y] = -1;
9008 CustomValue[x][y] = 0;
9010 // copy animation control values to new field
9011 GfxFrame[newx][newy] = GfxFrame[x][y];
9012 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
9013 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
9014 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
9016 Pushed[x][y] = Pushed[newx][newy] = FALSE;
9018 // some elements can leave other elements behind after moving
9019 if (ei->move_leave_element != EL_EMPTY &&
9020 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
9021 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
9023 int move_leave_element = ei->move_leave_element;
9025 // this makes it possible to leave the removed element again
9026 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
9027 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
9029 Tile[x][y] = move_leave_element;
9031 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
9032 MovDir[x][y] = direction;
9034 InitField(x, y, FALSE);
9036 if (GFX_CRUMBLED(Tile[x][y]))
9037 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
9039 if (IS_PLAYER_ELEMENT(move_leave_element))
9040 RelocatePlayer(x, y, move_leave_element);
9043 // do this after checking for left-behind element
9044 ResetGfxAnimation(x, y); // reset animation values for old field
9046 if (!CAN_MOVE(element) ||
9047 (CAN_FALL(element) && direction == MV_DOWN &&
9048 (element == EL_SPRING ||
9049 element_info[element].move_pattern == MV_WHEN_PUSHED ||
9050 element_info[element].move_pattern == MV_WHEN_DROPPED)))
9051 GfxDir[x][y] = MovDir[newx][newy] = 0;
9053 TEST_DrawLevelField(x, y);
9054 TEST_DrawLevelField(newx, newy);
9056 Stop[newx][newy] = TRUE; // ignore this element until the next frame
9058 // prevent pushed element from moving on in pushed direction
9059 if (pushed_by_player && CAN_MOVE(element) &&
9060 element_info[element].move_pattern & MV_ANY_DIRECTION &&
9061 !(element_info[element].move_pattern & direction))
9062 TurnRound(newx, newy);
9064 // prevent elements on conveyor belt from moving on in last direction
9065 if (pushed_by_conveyor && CAN_FALL(element) &&
9066 direction & MV_HORIZONTAL)
9067 MovDir[newx][newy] = 0;
9069 if (!pushed_by_player)
9071 int nextx = newx + dx, nexty = newy + dy;
9072 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
9074 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
9076 if (CAN_FALL(element) && direction == MV_DOWN)
9077 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
9079 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
9080 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
9082 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
9083 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
9086 if (DONT_TOUCH(element)) // object may be nasty to player or others
9088 TestIfBadThingTouchesPlayer(newx, newy);
9089 TestIfBadThingTouchesFriend(newx, newy);
9091 if (!IS_CUSTOM_ELEMENT(element))
9092 TestIfBadThingTouchesOtherBadThing(newx, newy);
9094 else if (element == EL_PENGUIN)
9095 TestIfFriendTouchesBadThing(newx, newy);
9097 if (DONT_GET_HIT_BY(element))
9099 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
9102 // give the player one last chance (one more frame) to move away
9103 if (CAN_FALL(element) && direction == MV_DOWN &&
9104 (last_line || (!IS_FREE(x, newy + 1) &&
9105 (!IS_PLAYER(x, newy + 1) ||
9106 game.engine_version < VERSION_IDENT(3,1,1,0)))))
9109 if (pushed_by_player && !game.use_change_when_pushing_bug)
9111 int push_side = MV_DIR_OPPOSITE(direction);
9112 struct PlayerInfo *player = PLAYERINFO(x, y);
9114 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
9115 player->index_bit, push_side);
9116 CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X,
9117 player->index_bit, push_side);
9120 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
9121 MovDelay[newx][newy] = 1;
9123 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
9125 TestIfElementTouchesCustomElement(x, y); // empty or new element
9126 TestIfElementHitsCustomElement(newx, newy, direction);
9127 TestIfPlayerTouchesCustomElement(newx, newy);
9128 TestIfElementTouchesCustomElement(newx, newy);
9130 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
9131 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
9132 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
9133 MV_DIR_OPPOSITE(direction));
9136 int AmoebaNeighbourNr(int ax, int ay)
9139 int element = Tile[ax][ay];
9141 struct XY *xy = xy_topdown;
9143 for (i = 0; i < NUM_DIRECTIONS; i++)
9145 int x = ax + xy[i].x;
9146 int y = ay + xy[i].y;
9148 if (!IN_LEV_FIELD(x, y))
9151 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
9152 group_nr = AmoebaNr[x][y];
9158 static void AmoebaMerge(int ax, int ay)
9160 int i, x, y, xx, yy;
9161 int new_group_nr = AmoebaNr[ax][ay];
9162 struct XY *xy = xy_topdown;
9164 if (new_group_nr == 0)
9167 for (i = 0; i < NUM_DIRECTIONS; i++)
9172 if (!IN_LEV_FIELD(x, y))
9175 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9176 Tile[x][y] == EL_BD_AMOEBA ||
9177 Tile[x][y] == EL_AMOEBA_DEAD) &&
9178 AmoebaNr[x][y] != new_group_nr)
9180 int old_group_nr = AmoebaNr[x][y];
9182 if (old_group_nr == 0)
9185 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9186 AmoebaCnt[old_group_nr] = 0;
9187 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9188 AmoebaCnt2[old_group_nr] = 0;
9190 SCAN_PLAYFIELD(xx, yy)
9192 if (AmoebaNr[xx][yy] == old_group_nr)
9193 AmoebaNr[xx][yy] = new_group_nr;
9199 void AmoebaToDiamond(int ax, int ay)
9203 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9205 int group_nr = AmoebaNr[ax][ay];
9210 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9211 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9217 SCAN_PLAYFIELD(x, y)
9219 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9222 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9226 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9227 SND_AMOEBA_TURNING_TO_GEM :
9228 SND_AMOEBA_TURNING_TO_ROCK));
9233 struct XY *xy = xy_topdown;
9235 for (i = 0; i < NUM_DIRECTIONS; i++)
9240 if (!IN_LEV_FIELD(x, y))
9243 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9245 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9246 SND_AMOEBA_TURNING_TO_GEM :
9247 SND_AMOEBA_TURNING_TO_ROCK));
9254 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9257 int group_nr = AmoebaNr[ax][ay];
9258 boolean done = FALSE;
9263 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9264 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9270 SCAN_PLAYFIELD(x, y)
9272 if (AmoebaNr[x][y] == group_nr &&
9273 (Tile[x][y] == EL_AMOEBA_DEAD ||
9274 Tile[x][y] == EL_BD_AMOEBA ||
9275 Tile[x][y] == EL_AMOEBA_GROWING))
9278 Tile[x][y] = new_element;
9279 InitField(x, y, FALSE);
9280 TEST_DrawLevelField(x, y);
9286 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9287 SND_BD_AMOEBA_TURNING_TO_ROCK :
9288 SND_BD_AMOEBA_TURNING_TO_GEM));
9291 static void AmoebaGrowing(int x, int y)
9293 static DelayCounter sound_delay = { 0 };
9295 if (!MovDelay[x][y]) // start new growing cycle
9299 if (DelayReached(&sound_delay))
9301 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9302 sound_delay.value = 30;
9306 if (MovDelay[x][y]) // wait some time before growing bigger
9309 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9311 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9312 6 - MovDelay[x][y]);
9314 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9317 if (!MovDelay[x][y])
9319 Tile[x][y] = Store[x][y];
9321 TEST_DrawLevelField(x, y);
9326 static void AmoebaShrinking(int x, int y)
9328 static DelayCounter sound_delay = { 0 };
9330 if (!MovDelay[x][y]) // start new shrinking cycle
9334 if (DelayReached(&sound_delay))
9335 sound_delay.value = 30;
9338 if (MovDelay[x][y]) // wait some time before shrinking
9341 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9343 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9344 6 - MovDelay[x][y]);
9346 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9349 if (!MovDelay[x][y])
9351 Tile[x][y] = EL_EMPTY;
9352 TEST_DrawLevelField(x, y);
9354 // don't let mole enter this field in this cycle;
9355 // (give priority to objects falling to this field from above)
9361 static void AmoebaReproduce(int ax, int ay)
9364 int element = Tile[ax][ay];
9365 int graphic = el2img(element);
9366 int newax = ax, neway = ay;
9367 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9368 struct XY *xy = xy_topdown;
9370 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9372 Tile[ax][ay] = EL_AMOEBA_DEAD;
9373 TEST_DrawLevelField(ax, ay);
9377 if (IS_ANIMATED(graphic))
9378 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9380 if (!MovDelay[ax][ay]) // start making new amoeba field
9381 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9383 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9386 if (MovDelay[ax][ay])
9390 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9393 int x = ax + xy[start].x;
9394 int y = ay + xy[start].y;
9396 if (!IN_LEV_FIELD(x, y))
9399 if (IS_FREE(x, y) ||
9400 CAN_GROW_INTO(Tile[x][y]) ||
9401 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9402 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9408 if (newax == ax && neway == ay)
9411 else // normal or "filled" (BD style) amoeba
9414 boolean waiting_for_player = FALSE;
9416 for (i = 0; i < NUM_DIRECTIONS; i++)
9418 int j = (start + i) % 4;
9419 int x = ax + xy[j].x;
9420 int y = ay + xy[j].y;
9422 if (!IN_LEV_FIELD(x, y))
9425 if (IS_FREE(x, y) ||
9426 CAN_GROW_INTO(Tile[x][y]) ||
9427 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9428 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9434 else if (IS_PLAYER(x, y))
9435 waiting_for_player = TRUE;
9438 if (newax == ax && neway == ay) // amoeba cannot grow
9440 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9442 Tile[ax][ay] = EL_AMOEBA_DEAD;
9443 TEST_DrawLevelField(ax, ay);
9444 AmoebaCnt[AmoebaNr[ax][ay]]--;
9446 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9448 if (element == EL_AMOEBA_FULL)
9449 AmoebaToDiamond(ax, ay);
9450 else if (element == EL_BD_AMOEBA)
9451 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9456 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9458 // amoeba gets larger by growing in some direction
9460 int new_group_nr = AmoebaNr[ax][ay];
9463 if (new_group_nr == 0)
9465 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9467 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9473 AmoebaNr[newax][neway] = new_group_nr;
9474 AmoebaCnt[new_group_nr]++;
9475 AmoebaCnt2[new_group_nr]++;
9477 // if amoeba touches other amoeba(s) after growing, unify them
9478 AmoebaMerge(newax, neway);
9480 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9482 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9488 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9489 (neway == lev_fieldy - 1 && newax != ax))
9491 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9492 Store[newax][neway] = element;
9494 else if (neway == ay || element == EL_EMC_DRIPPER)
9496 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9498 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9502 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9503 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9504 Store[ax][ay] = EL_AMOEBA_DROP;
9505 ContinueMoving(ax, ay);
9509 TEST_DrawLevelField(newax, neway);
9512 static void Life(int ax, int ay)
9516 int element = Tile[ax][ay];
9517 int graphic = el2img(element);
9518 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9520 boolean changed = FALSE;
9522 if (IS_ANIMATED(graphic))
9523 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9528 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9529 MovDelay[ax][ay] = life_time;
9531 if (MovDelay[ax][ay]) // wait some time before next cycle
9534 if (MovDelay[ax][ay])
9538 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9540 int xx = ax + x1, yy = ay + y1;
9541 int old_element = Tile[xx][yy];
9542 int num_neighbours = 0;
9544 if (!IN_LEV_FIELD(xx, yy))
9547 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9549 int x = xx + x2, y = yy + y2;
9551 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9554 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9555 boolean is_neighbour = FALSE;
9557 if (level.use_life_bugs)
9559 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9560 (IS_FREE(x, y) && Stop[x][y]));
9563 (Last[x][y] == element || is_player_cell);
9569 boolean is_free = FALSE;
9571 if (level.use_life_bugs)
9572 is_free = (IS_FREE(xx, yy));
9574 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9576 if (xx == ax && yy == ay) // field in the middle
9578 if (num_neighbours < life_parameter[0] ||
9579 num_neighbours > life_parameter[1])
9581 Tile[xx][yy] = EL_EMPTY;
9582 if (Tile[xx][yy] != old_element)
9583 TEST_DrawLevelField(xx, yy);
9584 Stop[xx][yy] = TRUE;
9588 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9589 { // free border field
9590 if (num_neighbours >= life_parameter[2] &&
9591 num_neighbours <= life_parameter[3])
9593 Tile[xx][yy] = element;
9594 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9595 if (Tile[xx][yy] != old_element)
9596 TEST_DrawLevelField(xx, yy);
9597 Stop[xx][yy] = TRUE;
9604 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9605 SND_GAME_OF_LIFE_GROWING);
9608 static void InitRobotWheel(int x, int y)
9610 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9613 static void RunRobotWheel(int x, int y)
9615 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9618 static void StopRobotWheel(int x, int y)
9620 if (game.robot_wheel_x == x &&
9621 game.robot_wheel_y == y)
9623 game.robot_wheel_x = -1;
9624 game.robot_wheel_y = -1;
9625 game.robot_wheel_active = FALSE;
9629 static void InitTimegateWheel(int x, int y)
9631 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9634 static void RunTimegateWheel(int x, int y)
9636 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9639 static void InitMagicBallDelay(int x, int y)
9641 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9644 static void ActivateMagicBall(int bx, int by)
9648 if (level.ball_random)
9650 int pos_border = RND(8); // select one of the eight border elements
9651 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9652 int xx = pos_content % 3;
9653 int yy = pos_content / 3;
9658 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9659 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9663 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9665 int xx = x - bx + 1;
9666 int yy = y - by + 1;
9668 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9669 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9673 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9676 static void CheckExit(int x, int y)
9678 if (game.gems_still_needed > 0 ||
9679 game.sokoban_fields_still_needed > 0 ||
9680 game.sokoban_objects_still_needed > 0 ||
9681 game.lights_still_needed > 0)
9683 int element = Tile[x][y];
9684 int graphic = el2img(element);
9686 if (IS_ANIMATED(graphic))
9687 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9692 // do not re-open exit door closed after last player
9693 if (game.all_players_gone)
9696 Tile[x][y] = EL_EXIT_OPENING;
9698 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9701 static void CheckExitEM(int x, int y)
9703 if (game.gems_still_needed > 0 ||
9704 game.sokoban_fields_still_needed > 0 ||
9705 game.sokoban_objects_still_needed > 0 ||
9706 game.lights_still_needed > 0)
9708 int element = Tile[x][y];
9709 int graphic = el2img(element);
9711 if (IS_ANIMATED(graphic))
9712 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9717 // do not re-open exit door closed after last player
9718 if (game.all_players_gone)
9721 Tile[x][y] = EL_EM_EXIT_OPENING;
9723 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9726 static void CheckExitSteel(int x, int y)
9728 if (game.gems_still_needed > 0 ||
9729 game.sokoban_fields_still_needed > 0 ||
9730 game.sokoban_objects_still_needed > 0 ||
9731 game.lights_still_needed > 0)
9733 int element = Tile[x][y];
9734 int graphic = el2img(element);
9736 if (IS_ANIMATED(graphic))
9737 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9742 // do not re-open exit door closed after last player
9743 if (game.all_players_gone)
9746 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9748 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9751 static void CheckExitSteelEM(int x, int y)
9753 if (game.gems_still_needed > 0 ||
9754 game.sokoban_fields_still_needed > 0 ||
9755 game.sokoban_objects_still_needed > 0 ||
9756 game.lights_still_needed > 0)
9758 int element = Tile[x][y];
9759 int graphic = el2img(element);
9761 if (IS_ANIMATED(graphic))
9762 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9767 // do not re-open exit door closed after last player
9768 if (game.all_players_gone)
9771 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9773 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9776 static void CheckExitSP(int x, int y)
9778 if (game.gems_still_needed > 0)
9780 int element = Tile[x][y];
9781 int graphic = el2img(element);
9783 if (IS_ANIMATED(graphic))
9784 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9789 // do not re-open exit door closed after last player
9790 if (game.all_players_gone)
9793 Tile[x][y] = EL_SP_EXIT_OPENING;
9795 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9798 static void CloseAllOpenTimegates(void)
9802 SCAN_PLAYFIELD(x, y)
9804 int element = Tile[x][y];
9806 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9808 Tile[x][y] = EL_TIMEGATE_CLOSING;
9810 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9815 static void DrawTwinkleOnField(int x, int y)
9817 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9820 if (Tile[x][y] == EL_BD_DIAMOND)
9823 if (MovDelay[x][y] == 0) // next animation frame
9824 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9826 if (MovDelay[x][y] != 0) // wait some time before next frame
9830 DrawLevelElementAnimation(x, y, Tile[x][y]);
9832 if (MovDelay[x][y] != 0)
9834 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9835 10 - MovDelay[x][y]);
9837 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9842 static void WallGrowing(int x, int y)
9846 if (!MovDelay[x][y]) // next animation frame
9847 MovDelay[x][y] = 3 * delay;
9849 if (MovDelay[x][y]) // wait some time before next frame
9853 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9855 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9856 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9858 DrawLevelGraphic(x, y, graphic, frame);
9861 if (!MovDelay[x][y])
9863 if (MovDir[x][y] == MV_LEFT)
9865 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9866 TEST_DrawLevelField(x - 1, y);
9868 else if (MovDir[x][y] == MV_RIGHT)
9870 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9871 TEST_DrawLevelField(x + 1, y);
9873 else if (MovDir[x][y] == MV_UP)
9875 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9876 TEST_DrawLevelField(x, y - 1);
9880 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9881 TEST_DrawLevelField(x, y + 1);
9884 Tile[x][y] = Store[x][y];
9886 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9887 TEST_DrawLevelField(x, y);
9892 static void CheckWallGrowing(int ax, int ay)
9894 int element = Tile[ax][ay];
9895 int graphic = el2img(element);
9896 boolean free_top = FALSE;
9897 boolean free_bottom = FALSE;
9898 boolean free_left = FALSE;
9899 boolean free_right = FALSE;
9900 boolean stop_top = FALSE;
9901 boolean stop_bottom = FALSE;
9902 boolean stop_left = FALSE;
9903 boolean stop_right = FALSE;
9904 boolean new_wall = FALSE;
9906 boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9907 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9908 element == EL_EXPANDABLE_STEELWALL_ANY);
9910 boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9911 element == EL_EXPANDABLE_WALL_ANY ||
9912 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9913 element == EL_EXPANDABLE_STEELWALL_ANY);
9915 boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9916 element == EL_EXPANDABLE_WALL_ANY ||
9917 element == EL_EXPANDABLE_WALL ||
9918 element == EL_BD_EXPANDABLE_WALL ||
9919 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9920 element == EL_EXPANDABLE_STEELWALL_ANY);
9922 boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9923 element == EL_EXPANDABLE_STEELWALL_VERTICAL);
9925 boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9926 element == EL_EXPANDABLE_WALL ||
9927 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL);
9929 int wall_growing = (is_steelwall ?
9930 EL_EXPANDABLE_STEELWALL_GROWING :
9931 EL_EXPANDABLE_WALL_GROWING);
9933 int gfx_wall_growing_up = (is_steelwall ?
9934 IMG_EXPANDABLE_STEELWALL_GROWING_UP :
9935 IMG_EXPANDABLE_WALL_GROWING_UP);
9936 int gfx_wall_growing_down = (is_steelwall ?
9937 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN :
9938 IMG_EXPANDABLE_WALL_GROWING_DOWN);
9939 int gfx_wall_growing_left = (is_steelwall ?
9940 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT :
9941 IMG_EXPANDABLE_WALL_GROWING_LEFT);
9942 int gfx_wall_growing_right = (is_steelwall ?
9943 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT :
9944 IMG_EXPANDABLE_WALL_GROWING_RIGHT);
9946 if (IS_ANIMATED(graphic))
9947 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9949 if (!MovDelay[ax][ay]) // start building new wall
9950 MovDelay[ax][ay] = 6;
9952 if (MovDelay[ax][ay]) // wait some time before building new wall
9955 if (MovDelay[ax][ay])
9959 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9961 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9963 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9965 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9972 Tile[ax][ay - 1] = wall_growing;
9973 Store[ax][ay - 1] = element;
9974 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9976 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9977 DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0);
9984 Tile[ax][ay + 1] = wall_growing;
9985 Store[ax][ay + 1] = element;
9986 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9988 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9989 DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0);
9995 if (grow_horizontal)
9999 Tile[ax - 1][ay] = wall_growing;
10000 Store[ax - 1][ay] = element;
10001 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
10003 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
10004 DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0);
10011 Tile[ax + 1][ay] = wall_growing;
10012 Store[ax + 1][ay] = element;
10013 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
10015 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
10016 DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0);
10022 if (element == EL_EXPANDABLE_WALL && (free_left || free_right))
10023 TEST_DrawLevelField(ax, ay);
10025 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
10027 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
10028 stop_bottom = TRUE;
10029 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
10031 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
10034 if (((stop_top && stop_bottom) || stop_horizontal) &&
10035 ((stop_left && stop_right) || stop_vertical))
10036 Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL);
10039 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
10042 static void CheckForDragon(int x, int y)
10045 boolean dragon_found = FALSE;
10046 struct XY *xy = xy_topdown;
10048 for (i = 0; i < NUM_DIRECTIONS; i++)
10050 for (j = 0; j < 4; j++)
10052 int xx = x + j * xy[i].x;
10053 int yy = y + j * xy[i].y;
10055 if (IN_LEV_FIELD(xx, yy) &&
10056 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
10058 if (Tile[xx][yy] == EL_DRAGON)
10059 dragon_found = TRUE;
10068 for (i = 0; i < NUM_DIRECTIONS; i++)
10070 for (j = 0; j < 3; j++)
10072 int xx = x + j * xy[i].x;
10073 int yy = y + j * xy[i].y;
10075 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
10077 Tile[xx][yy] = EL_EMPTY;
10078 TEST_DrawLevelField(xx, yy);
10087 static void InitBuggyBase(int x, int y)
10089 int element = Tile[x][y];
10090 int activating_delay = FRAMES_PER_SECOND / 4;
10092 ChangeDelay[x][y] =
10093 (element == EL_SP_BUGGY_BASE ?
10094 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10095 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10097 element == EL_SP_BUGGY_BASE_ACTIVE ?
10098 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10101 static void WarnBuggyBase(int x, int y)
10104 struct XY *xy = xy_topdown;
10106 for (i = 0; i < NUM_DIRECTIONS; i++)
10108 int xx = x + xy[i].x;
10109 int yy = y + xy[i].y;
10111 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10113 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10120 static void InitTrap(int x, int y)
10122 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10125 static void ActivateTrap(int x, int y)
10127 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10130 static void ChangeActiveTrap(int x, int y)
10132 int graphic = IMG_TRAP_ACTIVE;
10134 // if new animation frame was drawn, correct crumbled sand border
10135 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10136 TEST_DrawLevelFieldCrumbled(x, y);
10139 static int getSpecialActionElement(int element, int number, int base_element)
10141 return (element != EL_EMPTY ? element :
10142 number != -1 ? base_element + number - 1 :
10146 static int getModifiedActionNumber(int value_old, int operator, int operand,
10147 int value_min, int value_max)
10149 int value_new = (operator == CA_MODE_SET ? operand :
10150 operator == CA_MODE_ADD ? value_old + operand :
10151 operator == CA_MODE_SUBTRACT ? value_old - operand :
10152 operator == CA_MODE_MULTIPLY ? value_old * operand :
10153 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10154 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10157 return (value_new < value_min ? value_min :
10158 value_new > value_max ? value_max :
10162 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10164 struct ElementInfo *ei = &element_info[element];
10165 struct ElementChangeInfo *change = &ei->change_page[page];
10166 int target_element = change->target_element;
10167 int action_type = change->action_type;
10168 int action_mode = change->action_mode;
10169 int action_arg = change->action_arg;
10170 int action_element = change->action_element;
10173 if (!change->has_action)
10176 // ---------- determine action paramater values -----------------------------
10178 int level_time_value =
10179 (level.time > 0 ? TimeLeft :
10182 int action_arg_element_raw =
10183 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10184 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10185 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10186 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10187 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10188 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10189 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10191 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10193 int action_arg_direction =
10194 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10195 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10196 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10197 change->actual_trigger_side :
10198 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10199 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10202 int action_arg_number_min =
10203 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10206 int action_arg_number_max =
10207 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10208 action_type == CA_SET_LEVEL_GEMS ? 999 :
10209 action_type == CA_SET_LEVEL_TIME ? 9999 :
10210 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10211 action_type == CA_SET_CE_VALUE ? 9999 :
10212 action_type == CA_SET_CE_SCORE ? 9999 :
10215 int action_arg_number_reset =
10216 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10217 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10218 action_type == CA_SET_LEVEL_TIME ? level.time :
10219 action_type == CA_SET_LEVEL_SCORE ? 0 :
10220 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10221 action_type == CA_SET_CE_SCORE ? 0 :
10224 int action_arg_number =
10225 (action_arg <= CA_ARG_MAX ? action_arg :
10226 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10227 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10228 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10229 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10230 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10231 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10232 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10233 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10234 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10235 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10236 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10237 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10238 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10239 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10240 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10241 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10242 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10243 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10244 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10245 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10246 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10249 int action_arg_number_old =
10250 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10251 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10252 action_type == CA_SET_LEVEL_SCORE ? game.score :
10253 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10254 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10257 int action_arg_number_new =
10258 getModifiedActionNumber(action_arg_number_old,
10259 action_mode, action_arg_number,
10260 action_arg_number_min, action_arg_number_max);
10262 int trigger_player_bits =
10263 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10264 change->actual_trigger_player_bits : change->trigger_player);
10266 int action_arg_player_bits =
10267 (action_arg >= CA_ARG_PLAYER_1 &&
10268 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10269 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10270 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10273 // ---------- execute action -----------------------------------------------
10275 switch (action_type)
10282 // ---------- level actions ----------------------------------------------
10284 case CA_RESTART_LEVEL:
10286 game.restart_level = TRUE;
10291 case CA_SHOW_ENVELOPE:
10293 int element = getSpecialActionElement(action_arg_element,
10294 action_arg_number, EL_ENVELOPE_1);
10296 if (IS_ENVELOPE(element))
10297 local_player->show_envelope = element;
10302 case CA_SET_LEVEL_TIME:
10304 if (level.time > 0) // only modify limited time value
10306 TimeLeft = action_arg_number_new;
10308 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10310 DisplayGameControlValues();
10312 if (!TimeLeft && game.time_limit)
10313 for (i = 0; i < MAX_PLAYERS; i++)
10314 KillPlayer(&stored_player[i]);
10320 case CA_SET_LEVEL_SCORE:
10322 game.score = action_arg_number_new;
10324 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10326 DisplayGameControlValues();
10331 case CA_SET_LEVEL_GEMS:
10333 game.gems_still_needed = action_arg_number_new;
10335 game.snapshot.collected_item = TRUE;
10337 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10339 DisplayGameControlValues();
10344 case CA_SET_LEVEL_WIND:
10346 game.wind_direction = action_arg_direction;
10351 case CA_SET_LEVEL_RANDOM_SEED:
10353 // ensure that setting a new random seed while playing is predictable
10354 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10359 // ---------- player actions ---------------------------------------------
10361 case CA_MOVE_PLAYER:
10362 case CA_MOVE_PLAYER_NEW:
10364 // automatically move to the next field in specified direction
10365 for (i = 0; i < MAX_PLAYERS; i++)
10366 if (trigger_player_bits & (1 << i))
10367 if (action_type == CA_MOVE_PLAYER ||
10368 stored_player[i].MovPos == 0)
10369 stored_player[i].programmed_action = action_arg_direction;
10374 case CA_EXIT_PLAYER:
10376 for (i = 0; i < MAX_PLAYERS; i++)
10377 if (action_arg_player_bits & (1 << i))
10378 ExitPlayer(&stored_player[i]);
10380 if (game.players_still_needed == 0)
10386 case CA_KILL_PLAYER:
10388 for (i = 0; i < MAX_PLAYERS; i++)
10389 if (action_arg_player_bits & (1 << i))
10390 KillPlayer(&stored_player[i]);
10395 case CA_SET_PLAYER_KEYS:
10397 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10398 int element = getSpecialActionElement(action_arg_element,
10399 action_arg_number, EL_KEY_1);
10401 if (IS_KEY(element))
10403 for (i = 0; i < MAX_PLAYERS; i++)
10405 if (trigger_player_bits & (1 << i))
10407 stored_player[i].key[KEY_NR(element)] = key_state;
10409 DrawGameDoorValues();
10417 case CA_SET_PLAYER_SPEED:
10419 for (i = 0; i < MAX_PLAYERS; i++)
10421 if (trigger_player_bits & (1 << i))
10423 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10425 if (action_arg == CA_ARG_SPEED_FASTER &&
10426 stored_player[i].cannot_move)
10428 action_arg_number = STEPSIZE_VERY_SLOW;
10430 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10431 action_arg == CA_ARG_SPEED_FASTER)
10433 action_arg_number = 2;
10434 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10437 else if (action_arg == CA_ARG_NUMBER_RESET)
10439 action_arg_number = level.initial_player_stepsize[i];
10443 getModifiedActionNumber(move_stepsize,
10446 action_arg_number_min,
10447 action_arg_number_max);
10449 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10456 case CA_SET_PLAYER_SHIELD:
10458 for (i = 0; i < MAX_PLAYERS; i++)
10460 if (trigger_player_bits & (1 << i))
10462 if (action_arg == CA_ARG_SHIELD_OFF)
10464 stored_player[i].shield_normal_time_left = 0;
10465 stored_player[i].shield_deadly_time_left = 0;
10467 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10469 stored_player[i].shield_normal_time_left = 999999;
10471 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10473 stored_player[i].shield_normal_time_left = 999999;
10474 stored_player[i].shield_deadly_time_left = 999999;
10482 case CA_SET_PLAYER_GRAVITY:
10484 for (i = 0; i < MAX_PLAYERS; i++)
10486 if (trigger_player_bits & (1 << i))
10488 stored_player[i].gravity =
10489 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10490 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10491 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10492 stored_player[i].gravity);
10499 case CA_SET_PLAYER_ARTWORK:
10501 for (i = 0; i < MAX_PLAYERS; i++)
10503 if (trigger_player_bits & (1 << i))
10505 int artwork_element = action_arg_element;
10507 if (action_arg == CA_ARG_ELEMENT_RESET)
10509 (level.use_artwork_element[i] ? level.artwork_element[i] :
10510 stored_player[i].element_nr);
10512 if (stored_player[i].artwork_element != artwork_element)
10513 stored_player[i].Frame = 0;
10515 stored_player[i].artwork_element = artwork_element;
10517 SetPlayerWaiting(&stored_player[i], FALSE);
10519 // set number of special actions for bored and sleeping animation
10520 stored_player[i].num_special_action_bored =
10521 get_num_special_action(artwork_element,
10522 ACTION_BORING_1, ACTION_BORING_LAST);
10523 stored_player[i].num_special_action_sleeping =
10524 get_num_special_action(artwork_element,
10525 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10532 case CA_SET_PLAYER_INVENTORY:
10534 for (i = 0; i < MAX_PLAYERS; i++)
10536 struct PlayerInfo *player = &stored_player[i];
10539 if (trigger_player_bits & (1 << i))
10541 int inventory_element = action_arg_element;
10543 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10544 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10545 action_arg == CA_ARG_ELEMENT_ACTION)
10547 int element = inventory_element;
10548 int collect_count = element_info[element].collect_count_initial;
10550 if (!IS_CUSTOM_ELEMENT(element))
10553 if (collect_count == 0)
10554 player->inventory_infinite_element = element;
10556 for (k = 0; k < collect_count; k++)
10557 if (player->inventory_size < MAX_INVENTORY_SIZE)
10558 player->inventory_element[player->inventory_size++] =
10561 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10562 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10563 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10565 if (player->inventory_infinite_element != EL_UNDEFINED &&
10566 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10567 action_arg_element_raw))
10568 player->inventory_infinite_element = EL_UNDEFINED;
10570 for (k = 0, j = 0; j < player->inventory_size; j++)
10572 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10573 action_arg_element_raw))
10574 player->inventory_element[k++] = player->inventory_element[j];
10577 player->inventory_size = k;
10579 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10581 if (player->inventory_size > 0)
10583 for (j = 0; j < player->inventory_size - 1; j++)
10584 player->inventory_element[j] = player->inventory_element[j + 1];
10586 player->inventory_size--;
10589 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10591 if (player->inventory_size > 0)
10592 player->inventory_size--;
10594 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10596 player->inventory_infinite_element = EL_UNDEFINED;
10597 player->inventory_size = 0;
10599 else if (action_arg == CA_ARG_INVENTORY_RESET)
10601 player->inventory_infinite_element = EL_UNDEFINED;
10602 player->inventory_size = 0;
10604 if (level.use_initial_inventory[i])
10606 for (j = 0; j < level.initial_inventory_size[i]; j++)
10608 int element = level.initial_inventory_content[i][j];
10609 int collect_count = element_info[element].collect_count_initial;
10611 if (!IS_CUSTOM_ELEMENT(element))
10614 if (collect_count == 0)
10615 player->inventory_infinite_element = element;
10617 for (k = 0; k < collect_count; k++)
10618 if (player->inventory_size < MAX_INVENTORY_SIZE)
10619 player->inventory_element[player->inventory_size++] =
10630 // ---------- CE actions -------------------------------------------------
10632 case CA_SET_CE_VALUE:
10634 int last_ce_value = CustomValue[x][y];
10636 CustomValue[x][y] = action_arg_number_new;
10638 if (CustomValue[x][y] != last_ce_value)
10640 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10641 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10643 if (CustomValue[x][y] == 0)
10645 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10646 ChangeCount[x][y] = 0; // allow at least one more change
10648 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10649 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10656 case CA_SET_CE_SCORE:
10658 int last_ce_score = ei->collect_score;
10660 ei->collect_score = action_arg_number_new;
10662 if (ei->collect_score != last_ce_score)
10664 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10665 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10667 if (ei->collect_score == 0)
10671 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10672 ChangeCount[x][y] = 0; // allow at least one more change
10674 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10675 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10678 This is a very special case that seems to be a mixture between
10679 CheckElementChange() and CheckTriggeredElementChange(): while
10680 the first one only affects single elements that are triggered
10681 directly, the second one affects multiple elements in the playfield
10682 that are triggered indirectly by another element. This is a third
10683 case: Changing the CE score always affects multiple identical CEs,
10684 so every affected CE must be checked, not only the single CE for
10685 which the CE score was changed in the first place (as every instance
10686 of that CE shares the same CE score, and therefore also can change)!
10688 SCAN_PLAYFIELD(xx, yy)
10690 if (Tile[xx][yy] == element)
10691 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10692 CE_SCORE_GETS_ZERO);
10700 case CA_SET_CE_ARTWORK:
10702 int artwork_element = action_arg_element;
10703 boolean reset_frame = FALSE;
10706 if (action_arg == CA_ARG_ELEMENT_RESET)
10707 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10710 if (ei->gfx_element != artwork_element)
10711 reset_frame = TRUE;
10713 ei->gfx_element = artwork_element;
10715 SCAN_PLAYFIELD(xx, yy)
10717 if (Tile[xx][yy] == element)
10721 ResetGfxAnimation(xx, yy);
10722 ResetRandomAnimationValue(xx, yy);
10725 TEST_DrawLevelField(xx, yy);
10732 // ---------- engine actions ---------------------------------------------
10734 case CA_SET_ENGINE_SCAN_MODE:
10736 InitPlayfieldScanMode(action_arg);
10746 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10748 int old_element = Tile[x][y];
10749 int new_element = GetElementFromGroupElement(element);
10750 int previous_move_direction = MovDir[x][y];
10751 int last_ce_value = CustomValue[x][y];
10752 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10753 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10754 boolean add_player_onto_element = (new_element_is_player &&
10755 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10756 IS_WALKABLE(old_element));
10758 if (!add_player_onto_element)
10760 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10761 RemoveMovingField(x, y);
10765 Tile[x][y] = new_element;
10767 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10768 MovDir[x][y] = previous_move_direction;
10770 if (element_info[new_element].use_last_ce_value)
10771 CustomValue[x][y] = last_ce_value;
10773 InitField_WithBug1(x, y, FALSE);
10775 new_element = Tile[x][y]; // element may have changed
10777 ResetGfxAnimation(x, y);
10778 ResetRandomAnimationValue(x, y);
10780 TEST_DrawLevelField(x, y);
10782 if (GFX_CRUMBLED(new_element))
10783 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10785 if (old_element == EL_EXPLOSION)
10787 Store[x][y] = Store2[x][y] = 0;
10789 // check if new element replaces an exploding player, requiring cleanup
10790 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
10791 StorePlayer[x][y] = 0;
10794 // check if element under the player changes from accessible to unaccessible
10795 // (needed for special case of dropping element which then changes)
10796 // (must be checked after creating new element for walkable group elements)
10797 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10798 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10800 KillPlayer(PLAYERINFO(x, y));
10806 // "ChangeCount" not set yet to allow "entered by player" change one time
10807 if (new_element_is_player)
10808 RelocatePlayer(x, y, new_element);
10811 ChangeCount[x][y]++; // count number of changes in the same frame
10813 TestIfBadThingTouchesPlayer(x, y);
10814 TestIfPlayerTouchesCustomElement(x, y);
10815 TestIfElementTouchesCustomElement(x, y);
10818 static void CreateField(int x, int y, int element)
10820 CreateFieldExt(x, y, element, FALSE);
10823 static void CreateElementFromChange(int x, int y, int element)
10825 element = GET_VALID_RUNTIME_ELEMENT(element);
10827 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10829 int old_element = Tile[x][y];
10831 // prevent changed element from moving in same engine frame
10832 // unless both old and new element can either fall or move
10833 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10834 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10838 CreateFieldExt(x, y, element, TRUE);
10841 static boolean ChangeElement(int x, int y, int element, int page)
10843 struct ElementInfo *ei = &element_info[element];
10844 struct ElementChangeInfo *change = &ei->change_page[page];
10845 int ce_value = CustomValue[x][y];
10846 int ce_score = ei->collect_score;
10847 int target_element;
10848 int old_element = Tile[x][y];
10850 // always use default change event to prevent running into a loop
10851 if (ChangeEvent[x][y] == -1)
10852 ChangeEvent[x][y] = CE_DELAY;
10854 if (ChangeEvent[x][y] == CE_DELAY)
10856 // reset actual trigger element, trigger player and action element
10857 change->actual_trigger_element = EL_EMPTY;
10858 change->actual_trigger_player = EL_EMPTY;
10859 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10860 change->actual_trigger_side = CH_SIDE_NONE;
10861 change->actual_trigger_ce_value = 0;
10862 change->actual_trigger_ce_score = 0;
10863 change->actual_trigger_x = -1;
10864 change->actual_trigger_y = -1;
10867 // do not change elements more than a specified maximum number of changes
10868 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10871 ChangeCount[x][y]++; // count number of changes in the same frame
10873 if (ei->has_anim_event)
10874 HandleGlobalAnimEventByElementChange(element, page, x, y,
10875 change->actual_trigger_x,
10876 change->actual_trigger_y);
10878 if (change->explode)
10885 if (change->use_target_content)
10887 boolean complete_replace = TRUE;
10888 boolean can_replace[3][3];
10891 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10894 boolean is_walkable;
10895 boolean is_diggable;
10896 boolean is_collectible;
10897 boolean is_removable;
10898 boolean is_destructible;
10899 int ex = x + xx - 1;
10900 int ey = y + yy - 1;
10901 int content_element = change->target_content.e[xx][yy];
10904 can_replace[xx][yy] = TRUE;
10906 if (ex == x && ey == y) // do not check changing element itself
10909 if (content_element == EL_EMPTY_SPACE)
10911 can_replace[xx][yy] = FALSE; // do not replace border with space
10916 if (!IN_LEV_FIELD(ex, ey))
10918 can_replace[xx][yy] = FALSE;
10919 complete_replace = FALSE;
10926 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10927 e = MovingOrBlocked2Element(ex, ey);
10929 is_empty = (IS_FREE(ex, ey) ||
10930 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10932 is_walkable = (is_empty || IS_WALKABLE(e));
10933 is_diggable = (is_empty || IS_DIGGABLE(e));
10934 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10935 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10936 is_removable = (is_diggable || is_collectible);
10938 can_replace[xx][yy] =
10939 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10940 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10941 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10942 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10943 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10944 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10945 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10947 if (!can_replace[xx][yy])
10948 complete_replace = FALSE;
10951 if (!change->only_if_complete || complete_replace)
10953 boolean something_has_changed = FALSE;
10955 if (change->only_if_complete && change->use_random_replace &&
10956 RND(100) < change->random_percentage)
10959 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10961 int ex = x + xx - 1;
10962 int ey = y + yy - 1;
10963 int content_element;
10965 if (can_replace[xx][yy] && (!change->use_random_replace ||
10966 RND(100) < change->random_percentage))
10968 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10969 RemoveMovingField(ex, ey);
10971 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10973 content_element = change->target_content.e[xx][yy];
10974 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10975 ce_value, ce_score);
10977 CreateElementFromChange(ex, ey, target_element);
10979 something_has_changed = TRUE;
10981 // for symmetry reasons, freeze newly created border elements
10982 if (ex != x || ey != y)
10983 Stop[ex][ey] = TRUE; // no more moving in this frame
10987 if (something_has_changed)
10989 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10990 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10996 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10997 ce_value, ce_score);
10999 if (element == EL_DIAGONAL_GROWING ||
11000 element == EL_DIAGONAL_SHRINKING)
11002 target_element = Store[x][y];
11004 Store[x][y] = EL_EMPTY;
11007 // special case: element changes to player (and may be kept if walkable)
11008 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
11009 CreateElementFromChange(x, y, EL_EMPTY);
11011 CreateElementFromChange(x, y, target_element);
11013 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
11014 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
11017 // this uses direct change before indirect change
11018 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
11023 static void HandleElementChange(int x, int y, int page)
11025 int element = MovingOrBlocked2Element(x, y);
11026 struct ElementInfo *ei = &element_info[element];
11027 struct ElementChangeInfo *change = &ei->change_page[page];
11028 boolean handle_action_before_change = FALSE;
11031 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
11032 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
11034 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
11035 x, y, element, element_info[element].token_name);
11036 Debug("game:playing:HandleElementChange", "This should never happen!");
11040 // this can happen with classic bombs on walkable, changing elements
11041 if (!CAN_CHANGE_OR_HAS_ACTION(element))
11046 if (ChangeDelay[x][y] == 0) // initialize element change
11048 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
11050 if (change->can_change)
11052 // !!! not clear why graphic animation should be reset at all here !!!
11053 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
11054 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
11057 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
11059 When using an animation frame delay of 1 (this only happens with
11060 "sp_zonk.moving.left/right" in the classic graphics), the default
11061 (non-moving) animation shows wrong animation frames (while the
11062 moving animation, like "sp_zonk.moving.left/right", is correct,
11063 so this graphical bug never shows up with the classic graphics).
11064 For an animation with 4 frames, this causes wrong frames 0,0,1,2
11065 be drawn instead of the correct frames 0,1,2,3. This is caused by
11066 "GfxFrame[][]" being reset *twice* (in two successive frames) after
11067 an element change: First when the change delay ("ChangeDelay[][]")
11068 counter has reached zero after decrementing, then a second time in
11069 the next frame (after "GfxFrame[][]" was already incremented) when
11070 "ChangeDelay[][]" is reset to the initial delay value again.
11072 This causes frame 0 to be drawn twice, while the last frame won't
11073 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
11075 As some animations may already be cleverly designed around this bug
11076 (at least the "Snake Bite" snake tail animation does this), it cannot
11077 simply be fixed here without breaking such existing animations.
11078 Unfortunately, it cannot easily be detected if a graphics set was
11079 designed "before" or "after" the bug was fixed. As a workaround,
11080 a new graphics set option "game.graphics_engine_version" was added
11081 to be able to specify the game's major release version for which the
11082 graphics set was designed, which can then be used to decide if the
11083 bugfix should be used (version 4 and above) or not (version 3 or
11084 below, or if no version was specified at all, as with old sets).
11086 (The wrong/fixed animation frames can be tested with the test level set
11087 "test_gfxframe" and level "000", which contains a specially prepared
11088 custom element at level position (x/y) == (11/9) which uses the zonk
11089 animation mentioned above. Using "game.graphics_engine_version: 4"
11090 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
11091 This can also be seen from the debug output for this test element.)
11094 // when a custom element is about to change (for example by change delay),
11095 // do not reset graphic animation when the custom element is moving
11096 if (game.graphics_engine_version < 4 &&
11099 ResetGfxAnimation(x, y);
11100 ResetRandomAnimationValue(x, y);
11103 if (change->pre_change_function)
11104 change->pre_change_function(x, y);
11108 ChangeDelay[x][y]--;
11110 if (ChangeDelay[x][y] != 0) // continue element change
11112 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11114 // also needed if CE can not change, but has CE delay with CE action
11115 if (IS_ANIMATED(graphic))
11116 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11118 if (change->can_change)
11120 if (change->change_function)
11121 change->change_function(x, y);
11124 else // finish element change
11126 if (ChangePage[x][y] != -1) // remember page from delayed change
11128 page = ChangePage[x][y];
11129 ChangePage[x][y] = -1;
11131 change = &ei->change_page[page];
11134 if (IS_MOVING(x, y)) // never change a running system ;-)
11136 ChangeDelay[x][y] = 1; // try change after next move step
11137 ChangePage[x][y] = page; // remember page to use for change
11142 // special case: set new level random seed before changing element
11143 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11144 handle_action_before_change = TRUE;
11146 if (change->has_action && handle_action_before_change)
11147 ExecuteCustomElementAction(x, y, element, page);
11149 if (change->can_change)
11151 if (ChangeElement(x, y, element, page))
11153 if (change->post_change_function)
11154 change->post_change_function(x, y);
11158 if (change->has_action && !handle_action_before_change)
11159 ExecuteCustomElementAction(x, y, element, page);
11163 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11164 int trigger_element,
11166 int trigger_player,
11170 boolean change_done_any = FALSE;
11171 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11174 if (!(trigger_events[trigger_element][trigger_event]))
11177 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11179 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11181 int element = EL_CUSTOM_START + i;
11182 boolean change_done = FALSE;
11185 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11186 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11189 for (p = 0; p < element_info[element].num_change_pages; p++)
11191 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11193 if (change->can_change_or_has_action &&
11194 change->has_event[trigger_event] &&
11195 change->trigger_side & trigger_side &&
11196 change->trigger_player & trigger_player &&
11197 change->trigger_page & trigger_page_bits &&
11198 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11200 change->actual_trigger_element = trigger_element;
11201 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11202 change->actual_trigger_player_bits = trigger_player;
11203 change->actual_trigger_side = trigger_side;
11204 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11205 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11206 change->actual_trigger_x = trigger_x;
11207 change->actual_trigger_y = trigger_y;
11209 if ((change->can_change && !change_done) || change->has_action)
11213 SCAN_PLAYFIELD(x, y)
11215 if (Tile[x][y] == element)
11217 if (change->can_change && !change_done)
11219 // if element already changed in this frame, not only prevent
11220 // another element change (checked in ChangeElement()), but
11221 // also prevent additional element actions for this element
11223 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11224 !level.use_action_after_change_bug)
11227 ChangeDelay[x][y] = 1;
11228 ChangeEvent[x][y] = trigger_event;
11230 HandleElementChange(x, y, p);
11232 else if (change->has_action)
11234 // if element already changed in this frame, not only prevent
11235 // another element change (checked in ChangeElement()), but
11236 // also prevent additional element actions for this element
11238 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11239 !level.use_action_after_change_bug)
11242 ExecuteCustomElementAction(x, y, element, p);
11243 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11248 if (change->can_change)
11250 change_done = TRUE;
11251 change_done_any = TRUE;
11258 RECURSION_LOOP_DETECTION_END();
11260 return change_done_any;
11263 static boolean CheckElementChangeExt(int x, int y,
11265 int trigger_element,
11267 int trigger_player,
11270 boolean change_done = FALSE;
11273 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11274 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11277 if (Tile[x][y] == EL_BLOCKED)
11279 Blocked2Moving(x, y, &x, &y);
11280 element = Tile[x][y];
11283 // check if element has already changed or is about to change after moving
11284 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11285 Tile[x][y] != element) ||
11287 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11288 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11289 ChangePage[x][y] != -1)))
11292 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11294 for (p = 0; p < element_info[element].num_change_pages; p++)
11296 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11298 /* check trigger element for all events where the element that is checked
11299 for changing interacts with a directly adjacent element -- this is
11300 different to element changes that affect other elements to change on the
11301 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11302 boolean check_trigger_element =
11303 (trigger_event == CE_NEXT_TO_X ||
11304 trigger_event == CE_TOUCHING_X ||
11305 trigger_event == CE_HITTING_X ||
11306 trigger_event == CE_HIT_BY_X ||
11307 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11309 if (change->can_change_or_has_action &&
11310 change->has_event[trigger_event] &&
11311 change->trigger_side & trigger_side &&
11312 change->trigger_player & trigger_player &&
11313 (!check_trigger_element ||
11314 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11316 change->actual_trigger_element = trigger_element;
11317 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11318 change->actual_trigger_player_bits = trigger_player;
11319 change->actual_trigger_side = trigger_side;
11320 change->actual_trigger_ce_value = CustomValue[x][y];
11321 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11322 change->actual_trigger_x = x;
11323 change->actual_trigger_y = y;
11325 // special case: trigger element not at (x,y) position for some events
11326 if (check_trigger_element)
11338 { 0, 0 }, { 0, 0 }, { 0, 0 },
11342 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11343 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11345 change->actual_trigger_ce_value = CustomValue[xx][yy];
11346 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11347 change->actual_trigger_x = xx;
11348 change->actual_trigger_y = yy;
11351 if (change->can_change && !change_done)
11353 ChangeDelay[x][y] = 1;
11354 ChangeEvent[x][y] = trigger_event;
11356 HandleElementChange(x, y, p);
11358 change_done = TRUE;
11360 else if (change->has_action)
11362 ExecuteCustomElementAction(x, y, element, p);
11363 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11368 RECURSION_LOOP_DETECTION_END();
11370 return change_done;
11373 static void PlayPlayerSound(struct PlayerInfo *player)
11375 int jx = player->jx, jy = player->jy;
11376 int sound_element = player->artwork_element;
11377 int last_action = player->last_action_waiting;
11378 int action = player->action_waiting;
11380 if (player->is_waiting)
11382 if (action != last_action)
11383 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11385 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11389 if (action != last_action)
11390 StopSound(element_info[sound_element].sound[last_action]);
11392 if (last_action == ACTION_SLEEPING)
11393 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11397 static void PlayAllPlayersSound(void)
11401 for (i = 0; i < MAX_PLAYERS; i++)
11402 if (stored_player[i].active)
11403 PlayPlayerSound(&stored_player[i]);
11406 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11408 boolean last_waiting = player->is_waiting;
11409 int move_dir = player->MovDir;
11411 player->dir_waiting = move_dir;
11412 player->last_action_waiting = player->action_waiting;
11416 if (!last_waiting) // not waiting -> waiting
11418 player->is_waiting = TRUE;
11420 player->frame_counter_bored =
11422 game.player_boring_delay_fixed +
11423 GetSimpleRandom(game.player_boring_delay_random);
11424 player->frame_counter_sleeping =
11426 game.player_sleeping_delay_fixed +
11427 GetSimpleRandom(game.player_sleeping_delay_random);
11429 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11432 if (game.player_sleeping_delay_fixed +
11433 game.player_sleeping_delay_random > 0 &&
11434 player->anim_delay_counter == 0 &&
11435 player->post_delay_counter == 0 &&
11436 FrameCounter >= player->frame_counter_sleeping)
11437 player->is_sleeping = TRUE;
11438 else if (game.player_boring_delay_fixed +
11439 game.player_boring_delay_random > 0 &&
11440 FrameCounter >= player->frame_counter_bored)
11441 player->is_bored = TRUE;
11443 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11444 player->is_bored ? ACTION_BORING :
11447 if (player->is_sleeping && player->use_murphy)
11449 // special case for sleeping Murphy when leaning against non-free tile
11451 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11452 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11453 !IS_MOVING(player->jx - 1, player->jy)))
11454 move_dir = MV_LEFT;
11455 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11456 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11457 !IS_MOVING(player->jx + 1, player->jy)))
11458 move_dir = MV_RIGHT;
11460 player->is_sleeping = FALSE;
11462 player->dir_waiting = move_dir;
11465 if (player->is_sleeping)
11467 if (player->num_special_action_sleeping > 0)
11469 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11471 int last_special_action = player->special_action_sleeping;
11472 int num_special_action = player->num_special_action_sleeping;
11473 int special_action =
11474 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11475 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11476 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11477 last_special_action + 1 : ACTION_SLEEPING);
11478 int special_graphic =
11479 el_act_dir2img(player->artwork_element, special_action, move_dir);
11481 player->anim_delay_counter =
11482 graphic_info[special_graphic].anim_delay_fixed +
11483 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11484 player->post_delay_counter =
11485 graphic_info[special_graphic].post_delay_fixed +
11486 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11488 player->special_action_sleeping = special_action;
11491 if (player->anim_delay_counter > 0)
11493 player->action_waiting = player->special_action_sleeping;
11494 player->anim_delay_counter--;
11496 else if (player->post_delay_counter > 0)
11498 player->post_delay_counter--;
11502 else if (player->is_bored)
11504 if (player->num_special_action_bored > 0)
11506 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11508 int special_action =
11509 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11510 int special_graphic =
11511 el_act_dir2img(player->artwork_element, special_action, move_dir);
11513 player->anim_delay_counter =
11514 graphic_info[special_graphic].anim_delay_fixed +
11515 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11516 player->post_delay_counter =
11517 graphic_info[special_graphic].post_delay_fixed +
11518 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11520 player->special_action_bored = special_action;
11523 if (player->anim_delay_counter > 0)
11525 player->action_waiting = player->special_action_bored;
11526 player->anim_delay_counter--;
11528 else if (player->post_delay_counter > 0)
11530 player->post_delay_counter--;
11535 else if (last_waiting) // waiting -> not waiting
11537 player->is_waiting = FALSE;
11538 player->is_bored = FALSE;
11539 player->is_sleeping = FALSE;
11541 player->frame_counter_bored = -1;
11542 player->frame_counter_sleeping = -1;
11544 player->anim_delay_counter = 0;
11545 player->post_delay_counter = 0;
11547 player->dir_waiting = player->MovDir;
11548 player->action_waiting = ACTION_DEFAULT;
11550 player->special_action_bored = ACTION_DEFAULT;
11551 player->special_action_sleeping = ACTION_DEFAULT;
11555 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11557 if ((!player->is_moving && player->was_moving) ||
11558 (player->MovPos == 0 && player->was_moving) ||
11559 (player->is_snapping && !player->was_snapping) ||
11560 (player->is_dropping && !player->was_dropping))
11562 if (!CheckSaveEngineSnapshotToList())
11565 player->was_moving = FALSE;
11566 player->was_snapping = TRUE;
11567 player->was_dropping = TRUE;
11571 if (player->is_moving)
11572 player->was_moving = TRUE;
11574 if (!player->is_snapping)
11575 player->was_snapping = FALSE;
11577 if (!player->is_dropping)
11578 player->was_dropping = FALSE;
11581 static struct MouseActionInfo mouse_action_last = { 0 };
11582 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11583 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11586 CheckSaveEngineSnapshotToList();
11588 mouse_action_last = mouse_action;
11591 static void CheckSingleStepMode(struct PlayerInfo *player)
11593 if (tape.single_step && tape.recording && !tape.pausing)
11595 // as it is called "single step mode", just return to pause mode when the
11596 // player stopped moving after one tile (or never starts moving at all)
11597 // (reverse logic needed here in case single step mode used in team mode)
11598 if (player->is_moving ||
11599 player->is_pushing ||
11600 player->is_dropping_pressed ||
11601 player->effective_mouse_action.button)
11602 game.enter_single_step_mode = FALSE;
11605 CheckSaveEngineSnapshot(player);
11608 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11610 int left = player_action & JOY_LEFT;
11611 int right = player_action & JOY_RIGHT;
11612 int up = player_action & JOY_UP;
11613 int down = player_action & JOY_DOWN;
11614 int button1 = player_action & JOY_BUTTON_1;
11615 int button2 = player_action & JOY_BUTTON_2;
11616 int dx = (left ? -1 : right ? 1 : 0);
11617 int dy = (up ? -1 : down ? 1 : 0);
11619 if (!player->active || tape.pausing)
11625 SnapField(player, dx, dy);
11629 DropElement(player);
11631 MovePlayer(player, dx, dy);
11634 CheckSingleStepMode(player);
11636 SetPlayerWaiting(player, FALSE);
11638 return player_action;
11642 // no actions for this player (no input at player's configured device)
11644 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11645 SnapField(player, 0, 0);
11646 CheckGravityMovementWhenNotMoving(player);
11648 if (player->MovPos == 0)
11649 SetPlayerWaiting(player, TRUE);
11651 if (player->MovPos == 0) // needed for tape.playing
11652 player->is_moving = FALSE;
11654 player->is_dropping = FALSE;
11655 player->is_dropping_pressed = FALSE;
11656 player->drop_pressed_delay = 0;
11658 CheckSingleStepMode(player);
11664 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11667 if (!tape.use_mouse_actions)
11670 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11671 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11672 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11675 static void SetTapeActionFromMouseAction(byte *tape_action,
11676 struct MouseActionInfo *mouse_action)
11678 if (!tape.use_mouse_actions)
11681 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11682 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11683 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11686 static void CheckLevelSolved(void)
11688 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11690 if (game_bd.level_solved &&
11691 !game_bd.game_over) // game won
11695 game_bd.game_over = TRUE;
11697 game.all_players_gone = TRUE;
11700 if (game_bd.game_over) // game lost
11701 game.all_players_gone = TRUE;
11703 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11705 if (game_em.level_solved &&
11706 !game_em.game_over) // game won
11710 game_em.game_over = TRUE;
11712 game.all_players_gone = TRUE;
11715 if (game_em.game_over) // game lost
11716 game.all_players_gone = TRUE;
11718 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11720 if (game_sp.level_solved &&
11721 !game_sp.game_over) // game won
11725 game_sp.game_over = TRUE;
11727 game.all_players_gone = TRUE;
11730 if (game_sp.game_over) // game lost
11731 game.all_players_gone = TRUE;
11733 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11735 if (game_mm.level_solved &&
11736 !game_mm.game_over) // game won
11740 game_mm.game_over = TRUE;
11742 game.all_players_gone = TRUE;
11745 if (game_mm.game_over) // game lost
11746 game.all_players_gone = TRUE;
11750 static void PlayTimeoutSound(int seconds_left)
11752 // will be played directly by BD engine (for classic bonus time sounds)
11753 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && checkBonusTime_BD())
11756 // try to use individual "running out of time" sound for each second left
11757 int sound = SND_GAME_RUNNING_OUT_OF_TIME_0 - seconds_left;
11759 // if special sound per second not defined, use default sound
11760 if (getSoundInfoEntryFilename(sound) == NULL)
11761 sound = SND_GAME_RUNNING_OUT_OF_TIME;
11763 // if out of time, but player still alive, play special "timeout" sound, if defined
11764 if (seconds_left == 0 && !checkGameFailed())
11765 if (getSoundInfoEntryFilename(SND_GAME_TIMEOUT) != NULL)
11766 sound = SND_GAME_TIMEOUT;
11771 static void CheckLevelTime_StepCounter(void)
11781 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11782 PlayTimeoutSound(TimeLeft);
11784 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11786 DisplayGameControlValues();
11788 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11789 for (i = 0; i < MAX_PLAYERS; i++)
11790 KillPlayer(&stored_player[i]);
11792 else if (game.no_level_time_limit && !game.all_players_gone)
11794 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11796 DisplayGameControlValues();
11800 static void CheckLevelTime(void)
11802 int frames_per_second = FRAMES_PER_SECOND;
11805 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11807 // level time may be running slower in native BD engine
11808 frames_per_second = getFramesPerSecond_BD();
11810 // if native engine time changed, force main engine time change
11811 if (getTimeLeft_BD() < TimeLeft)
11812 TimeFrames = frames_per_second;
11814 // if last second running, wait for native engine time to exactly reach zero
11815 if (getTimeLeft_BD() == 1 && TimeLeft == 1)
11816 TimeFrames = frames_per_second - 1;
11819 if (TimeFrames >= frames_per_second)
11823 for (i = 0; i < MAX_PLAYERS; i++)
11825 struct PlayerInfo *player = &stored_player[i];
11827 if (SHIELD_ON(player))
11829 player->shield_normal_time_left--;
11831 if (player->shield_deadly_time_left > 0)
11832 player->shield_deadly_time_left--;
11836 if (!game.LevelSolved && !level.use_step_counter)
11844 if (TimeLeft <= 10 && game.time_limit)
11845 PlayTimeoutSound(TimeLeft);
11847 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11848 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11850 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11852 if (!TimeLeft && game.time_limit)
11854 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11856 if (game_bd.game->cave->player_state == GD_PL_LIVING)
11857 game_bd.game->cave->player_state = GD_PL_TIMEOUT;
11859 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11861 game_em.lev->killed_out_of_time = TRUE;
11865 for (i = 0; i < MAX_PLAYERS; i++)
11866 KillPlayer(&stored_player[i]);
11870 else if (game.no_level_time_limit && !game.all_players_gone)
11872 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11875 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11879 if (TapeTimeFrames >= FRAMES_PER_SECOND)
11881 TapeTimeFrames = 0;
11884 if (tape.recording || tape.playing)
11885 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11888 if (tape.recording || tape.playing)
11889 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11891 UpdateAndDisplayGameControlValues();
11894 void AdvanceFrameAndPlayerCounters(int player_nr)
11898 // handle game and tape time differently for native BD game engine
11900 // tape time is running in native BD engine even if player is not hatched yet
11901 if (!checkGameRunning())
11904 // advance frame counters (global frame counter and tape time frame counter)
11908 // level time is running in native BD engine after player is being hatched
11909 if (!checkGamePlaying())
11912 // advance time frame counter (used to control available time to solve level)
11915 // advance player counters (counters for move delay, move animation etc.)
11916 for (i = 0; i < MAX_PLAYERS; i++)
11918 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11919 int move_delay_value = stored_player[i].move_delay_value;
11920 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11922 if (!advance_player_counters) // not all players may be affected
11925 if (move_frames == 0) // less than one move per game frame
11927 int stepsize = TILEX / move_delay_value;
11928 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11929 int count = (stored_player[i].is_moving ?
11930 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11932 if (count % delay == 0)
11936 stored_player[i].Frame += move_frames;
11938 if (stored_player[i].MovPos != 0)
11939 stored_player[i].StepFrame += move_frames;
11941 if (stored_player[i].move_delay > 0)
11942 stored_player[i].move_delay--;
11944 // due to bugs in previous versions, counter must count up, not down
11945 if (stored_player[i].push_delay != -1)
11946 stored_player[i].push_delay++;
11948 if (stored_player[i].drop_delay > 0)
11949 stored_player[i].drop_delay--;
11951 if (stored_player[i].is_dropping_pressed)
11952 stored_player[i].drop_pressed_delay++;
11956 void AdvanceFrameCounter(void)
11961 void AdvanceGfxFrame(void)
11965 SCAN_PLAYFIELD(x, y)
11971 static void HandleMouseAction(struct MouseActionInfo *mouse_action,
11972 struct MouseActionInfo *mouse_action_last)
11974 if (mouse_action->button)
11976 int new_button = (mouse_action->button && mouse_action_last->button == 0);
11977 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button);
11978 int x = mouse_action->lx;
11979 int y = mouse_action->ly;
11980 int element = Tile[x][y];
11984 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
11985 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
11989 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
11990 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
11993 if (level.use_step_counter)
11995 boolean counted_click = FALSE;
11997 // element clicked that can change when clicked/pressed
11998 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
11999 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
12000 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
12001 counted_click = TRUE;
12003 // element clicked that can trigger change when clicked/pressed
12004 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
12005 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
12006 counted_click = TRUE;
12008 if (new_button && counted_click)
12009 CheckLevelTime_StepCounter();
12014 void StartGameActions(boolean init_network_game, boolean record_tape,
12017 unsigned int new_random_seed = InitRND(random_seed);
12020 TapeStartRecording(new_random_seed);
12022 if (setup.auto_pause_on_start && !tape.pausing)
12023 TapeTogglePause(TAPE_TOGGLE_MANUAL);
12025 if (init_network_game)
12027 SendToServer_LevelFile();
12028 SendToServer_StartPlaying();
12036 static void GameActionsExt(void)
12039 static unsigned int game_frame_delay = 0;
12041 unsigned int game_frame_delay_value;
12042 byte *recorded_player_action;
12043 byte summarized_player_action = 0;
12044 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
12047 // detect endless loops, caused by custom element programming
12048 if (recursion_loop_detected && recursion_loop_depth == 0)
12050 char *message = getStringCat3("Internal Error! Element ",
12051 EL_NAME(recursion_loop_element),
12052 " caused endless loop! Quit the game?");
12054 Warn("element '%s' caused endless loop in game engine",
12055 EL_NAME(recursion_loop_element));
12057 RequestQuitGameExt(program.headless, level_editor_test_game, message);
12059 recursion_loop_detected = FALSE; // if game should be continued
12066 if (game.restart_level)
12067 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
12069 CheckLevelSolved();
12071 if (game.LevelSolved && !game.LevelSolved_GameEnd)
12074 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
12077 if (game_status != GAME_MODE_PLAYING) // status might have changed
12080 game_frame_delay_value =
12081 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
12083 if (tape.playing && tape.warp_forward && !tape.pausing)
12084 game_frame_delay_value = 0;
12086 SetVideoFrameDelay(game_frame_delay_value);
12088 // (de)activate virtual buttons depending on current game status
12089 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
12091 if (game.all_players_gone) // if no players there to be controlled anymore
12092 SetOverlayActive(FALSE);
12093 else if (!tape.playing) // if game continues after tape stopped playing
12094 SetOverlayActive(TRUE);
12099 // ---------- main game synchronization point ----------
12101 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12103 Debug("game:playing:skip", "skip == %d", skip);
12106 // ---------- main game synchronization point ----------
12108 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12112 if (network_playing && !network_player_action_received)
12114 // try to get network player actions in time
12116 // last chance to get network player actions without main loop delay
12117 HandleNetworking();
12119 // game was quit by network peer
12120 if (game_status != GAME_MODE_PLAYING)
12123 // check if network player actions still missing and game still running
12124 if (!network_player_action_received && !checkGameEnded())
12125 return; // failed to get network player actions in time
12127 // do not yet reset "network_player_action_received" (for tape.pausing)
12133 // at this point we know that we really continue executing the game
12135 network_player_action_received = FALSE;
12137 // when playing tape, read previously recorded player input from tape data
12138 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
12140 local_player->effective_mouse_action = local_player->mouse_action;
12142 if (recorded_player_action != NULL)
12143 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
12144 recorded_player_action);
12146 // TapePlayAction() may return NULL when toggling to "pause before death"
12150 if (tape.set_centered_player)
12152 game.centered_player_nr_next = tape.centered_player_nr_next;
12153 game.set_centered_player = TRUE;
12156 for (i = 0; i < MAX_PLAYERS; i++)
12158 summarized_player_action |= stored_player[i].action;
12160 if (!network_playing && (game.team_mode || tape.playing))
12161 stored_player[i].effective_action = stored_player[i].action;
12164 if (network_playing && !checkGameEnded())
12165 SendToServer_MovePlayer(summarized_player_action);
12167 // summarize all actions at local players mapped input device position
12168 // (this allows using different input devices in single player mode)
12169 if (!network.enabled && !game.team_mode)
12170 stored_player[map_player_action[local_player->index_nr]].effective_action =
12171 summarized_player_action;
12173 // summarize all actions at centered player in local team mode
12174 if (tape.recording &&
12175 setup.team_mode && !network.enabled &&
12176 setup.input_on_focus &&
12177 game.centered_player_nr != -1)
12179 for (i = 0; i < MAX_PLAYERS; i++)
12180 stored_player[map_player_action[i]].effective_action =
12181 (i == game.centered_player_nr ? summarized_player_action : 0);
12184 if (recorded_player_action != NULL)
12185 for (i = 0; i < MAX_PLAYERS; i++)
12186 stored_player[i].effective_action = recorded_player_action[i];
12188 for (i = 0; i < MAX_PLAYERS; i++)
12190 tape_action[i] = stored_player[i].effective_action;
12192 /* (this may happen in the RND game engine if a player was not present on
12193 the playfield on level start, but appeared later from a custom element */
12194 if (setup.team_mode &&
12197 !tape.player_participates[i])
12198 tape.player_participates[i] = TRUE;
12201 SetTapeActionFromMouseAction(tape_action,
12202 &local_player->effective_mouse_action);
12204 // only record actions from input devices, but not programmed actions
12205 if (tape.recording)
12206 TapeRecordAction(tape_action);
12208 // remember if game was played (especially after tape stopped playing)
12209 if (!tape.playing && summarized_player_action && !checkGameFailed())
12210 game.GamePlayed = TRUE;
12212 #if USE_NEW_PLAYER_ASSIGNMENTS
12213 // !!! also map player actions in single player mode !!!
12214 // if (game.team_mode)
12217 byte mapped_action[MAX_PLAYERS];
12219 #if DEBUG_PLAYER_ACTIONS
12220 for (i = 0; i < MAX_PLAYERS; i++)
12221 DebugContinued("", "%d, ", stored_player[i].effective_action);
12224 for (i = 0; i < MAX_PLAYERS; i++)
12225 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
12227 for (i = 0; i < MAX_PLAYERS; i++)
12228 stored_player[i].effective_action = mapped_action[i];
12230 #if DEBUG_PLAYER_ACTIONS
12231 DebugContinued("", "=> ");
12232 for (i = 0; i < MAX_PLAYERS; i++)
12233 DebugContinued("", "%d, ", stored_player[i].effective_action);
12234 DebugContinued("game:playing:player", "\n");
12237 #if DEBUG_PLAYER_ACTIONS
12240 for (i = 0; i < MAX_PLAYERS; i++)
12241 DebugContinued("", "%d, ", stored_player[i].effective_action);
12242 DebugContinued("game:playing:player", "\n");
12247 for (i = 0; i < MAX_PLAYERS; i++)
12249 // allow engine snapshot in case of changed movement attempt
12250 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12251 (stored_player[i].effective_action & KEY_MOTION))
12252 game.snapshot.changed_action = TRUE;
12254 // allow engine snapshot in case of snapping/dropping attempt
12255 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12256 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12257 game.snapshot.changed_action = TRUE;
12259 game.snapshot.last_action[i] = stored_player[i].effective_action;
12262 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
12264 GameActions_BD_Main();
12266 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12268 GameActions_EM_Main();
12270 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12272 GameActions_SP_Main();
12274 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12276 GameActions_MM_Main();
12280 GameActions_RND_Main();
12283 BlitScreenToBitmap(backbuffer);
12285 CheckLevelSolved();
12288 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12290 if (global.show_frames_per_second)
12292 static unsigned int fps_counter = 0;
12293 static int fps_frames = 0;
12294 unsigned int fps_delay_ms = Counter() - fps_counter;
12298 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12300 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12303 fps_counter = Counter();
12305 // always draw FPS to screen after FPS value was updated
12306 redraw_mask |= REDRAW_FPS;
12309 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12310 if (GetDrawDeactivationMask() == REDRAW_NONE)
12311 redraw_mask |= REDRAW_FPS;
12315 static void GameActions_CheckSaveEngineSnapshot(void)
12317 if (!game.snapshot.save_snapshot)
12320 // clear flag for saving snapshot _before_ saving snapshot
12321 game.snapshot.save_snapshot = FALSE;
12323 SaveEngineSnapshotToList();
12326 void GameActions(void)
12330 GameActions_CheckSaveEngineSnapshot();
12333 void GameActions_BD_Main(void)
12335 byte effective_action[MAX_PLAYERS];
12338 for (i = 0; i < MAX_PLAYERS; i++)
12339 effective_action[i] = stored_player[i].effective_action;
12341 GameActions_BD(effective_action);
12344 void GameActions_EM_Main(void)
12346 byte effective_action[MAX_PLAYERS];
12349 for (i = 0; i < MAX_PLAYERS; i++)
12350 effective_action[i] = stored_player[i].effective_action;
12352 GameActions_EM(effective_action);
12355 void GameActions_SP_Main(void)
12357 byte effective_action[MAX_PLAYERS];
12360 for (i = 0; i < MAX_PLAYERS; i++)
12361 effective_action[i] = stored_player[i].effective_action;
12363 GameActions_SP(effective_action);
12365 for (i = 0; i < MAX_PLAYERS; i++)
12367 if (stored_player[i].force_dropping)
12368 stored_player[i].action |= KEY_BUTTON_DROP;
12370 stored_player[i].force_dropping = FALSE;
12374 void GameActions_MM_Main(void)
12378 GameActions_MM(local_player->effective_mouse_action);
12381 void GameActions_RND_Main(void)
12386 void GameActions_RND(void)
12388 static struct MouseActionInfo mouse_action_last = { 0 };
12389 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12390 int magic_wall_x = 0, magic_wall_y = 0;
12391 int i, x, y, element, graphic, last_gfx_frame;
12393 InitPlayfieldScanModeVars();
12395 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12397 SCAN_PLAYFIELD(x, y)
12399 ChangeCount[x][y] = 0;
12400 ChangeEvent[x][y] = -1;
12404 if (game.set_centered_player)
12406 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12408 // switching to "all players" only possible if all players fit to screen
12409 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12411 game.centered_player_nr_next = game.centered_player_nr;
12412 game.set_centered_player = FALSE;
12415 // do not switch focus to non-existing (or non-active) player
12416 if (game.centered_player_nr_next >= 0 &&
12417 !stored_player[game.centered_player_nr_next].active)
12419 game.centered_player_nr_next = game.centered_player_nr;
12420 game.set_centered_player = FALSE;
12424 if (game.set_centered_player &&
12425 ScreenMovPos == 0) // screen currently aligned at tile position
12429 if (game.centered_player_nr_next == -1)
12431 setScreenCenteredToAllPlayers(&sx, &sy);
12435 sx = stored_player[game.centered_player_nr_next].jx;
12436 sy = stored_player[game.centered_player_nr_next].jy;
12439 game.centered_player_nr = game.centered_player_nr_next;
12440 game.set_centered_player = FALSE;
12442 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12443 DrawGameDoorValues();
12446 // check single step mode (set flag and clear again if any player is active)
12447 game.enter_single_step_mode =
12448 (tape.single_step && tape.recording && !tape.pausing);
12450 for (i = 0; i < MAX_PLAYERS; i++)
12452 int actual_player_action = stored_player[i].effective_action;
12455 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12456 - rnd_equinox_tetrachloride 048
12457 - rnd_equinox_tetrachloride_ii 096
12458 - rnd_emanuel_schmieg 002
12459 - doctor_sloan_ww 001, 020
12461 if (stored_player[i].MovPos == 0)
12462 CheckGravityMovement(&stored_player[i]);
12465 // overwrite programmed action with tape action
12466 if (stored_player[i].programmed_action)
12467 actual_player_action = stored_player[i].programmed_action;
12469 PlayerActions(&stored_player[i], actual_player_action);
12471 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12474 // single step pause mode may already have been toggled by "ScrollPlayer()"
12475 if (game.enter_single_step_mode && !tape.pausing)
12476 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12478 ScrollScreen(NULL, SCROLL_GO_ON);
12480 /* for backwards compatibility, the following code emulates a fixed bug that
12481 occured when pushing elements (causing elements that just made their last
12482 pushing step to already (if possible) make their first falling step in the
12483 same game frame, which is bad); this code is also needed to use the famous
12484 "spring push bug" which is used in older levels and might be wanted to be
12485 used also in newer levels, but in this case the buggy pushing code is only
12486 affecting the "spring" element and no other elements */
12488 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12490 for (i = 0; i < MAX_PLAYERS; i++)
12492 struct PlayerInfo *player = &stored_player[i];
12493 int x = player->jx;
12494 int y = player->jy;
12496 if (player->active && player->is_pushing && player->is_moving &&
12498 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12499 Tile[x][y] == EL_SPRING))
12501 ContinueMoving(x, y);
12503 // continue moving after pushing (this is actually a bug)
12504 if (!IS_MOVING(x, y))
12505 Stop[x][y] = FALSE;
12510 SCAN_PLAYFIELD(x, y)
12512 Last[x][y] = Tile[x][y];
12514 ChangeCount[x][y] = 0;
12515 ChangeEvent[x][y] = -1;
12517 // this must be handled before main playfield loop
12518 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12521 if (MovDelay[x][y] <= 0)
12525 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12528 if (MovDelay[x][y] <= 0)
12530 int element = Store[x][y];
12531 int move_direction = MovDir[x][y];
12532 int player_index_bit = Store2[x][y];
12538 TEST_DrawLevelField(x, y);
12540 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12542 if (IS_ENVELOPE(element))
12543 local_player->show_envelope = element;
12548 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12550 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12552 Debug("game:playing:GameActions_RND", "This should never happen!");
12554 ChangePage[x][y] = -1;
12558 Stop[x][y] = FALSE;
12559 if (WasJustMoving[x][y] > 0)
12560 WasJustMoving[x][y]--;
12561 if (WasJustFalling[x][y] > 0)
12562 WasJustFalling[x][y]--;
12563 if (CheckCollision[x][y] > 0)
12564 CheckCollision[x][y]--;
12565 if (CheckImpact[x][y] > 0)
12566 CheckImpact[x][y]--;
12570 /* reset finished pushing action (not done in ContinueMoving() to allow
12571 continuous pushing animation for elements with zero push delay) */
12572 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12574 ResetGfxAnimation(x, y);
12575 TEST_DrawLevelField(x, y);
12579 if (IS_BLOCKED(x, y))
12583 Blocked2Moving(x, y, &oldx, &oldy);
12584 if (!IS_MOVING(oldx, oldy))
12586 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12587 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12588 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12589 Debug("game:playing:GameActions_RND", "This should never happen!");
12595 HandleMouseAction(&mouse_action, &mouse_action_last);
12597 SCAN_PLAYFIELD(x, y)
12599 element = Tile[x][y];
12600 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12601 last_gfx_frame = GfxFrame[x][y];
12603 if (element == EL_EMPTY)
12604 graphic = el2img(GfxElementEmpty[x][y]);
12606 ResetGfxFrame(x, y);
12608 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12609 DrawLevelGraphicAnimation(x, y, graphic);
12611 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12612 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12613 ResetRandomAnimationValue(x, y);
12615 SetRandomAnimationValue(x, y);
12617 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12619 if (IS_INACTIVE(element))
12621 if (IS_ANIMATED(graphic))
12622 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12627 // this may take place after moving, so 'element' may have changed
12628 if (IS_CHANGING(x, y) &&
12629 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12631 int page = element_info[element].event_page_nr[CE_DELAY];
12633 HandleElementChange(x, y, page);
12635 element = Tile[x][y];
12636 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12639 CheckNextToConditions(x, y);
12641 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12645 element = Tile[x][y];
12646 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12648 if (IS_ANIMATED(graphic) &&
12649 !IS_MOVING(x, y) &&
12651 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12653 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12654 TEST_DrawTwinkleOnField(x, y);
12656 else if (element == EL_ACID)
12659 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12661 else if ((element == EL_EXIT_OPEN ||
12662 element == EL_EM_EXIT_OPEN ||
12663 element == EL_SP_EXIT_OPEN ||
12664 element == EL_STEEL_EXIT_OPEN ||
12665 element == EL_EM_STEEL_EXIT_OPEN ||
12666 element == EL_SP_TERMINAL ||
12667 element == EL_SP_TERMINAL_ACTIVE ||
12668 element == EL_EXTRA_TIME ||
12669 element == EL_SHIELD_NORMAL ||
12670 element == EL_SHIELD_DEADLY) &&
12671 IS_ANIMATED(graphic))
12672 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12673 else if (IS_MOVING(x, y))
12674 ContinueMoving(x, y);
12675 else if (IS_ACTIVE_BOMB(element))
12676 CheckDynamite(x, y);
12677 else if (element == EL_AMOEBA_GROWING)
12678 AmoebaGrowing(x, y);
12679 else if (element == EL_AMOEBA_SHRINKING)
12680 AmoebaShrinking(x, y);
12682 #if !USE_NEW_AMOEBA_CODE
12683 else if (IS_AMOEBALIVE(element))
12684 AmoebaReproduce(x, y);
12687 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12689 else if (element == EL_EXIT_CLOSED)
12691 else if (element == EL_EM_EXIT_CLOSED)
12693 else if (element == EL_STEEL_EXIT_CLOSED)
12694 CheckExitSteel(x, y);
12695 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12696 CheckExitSteelEM(x, y);
12697 else if (element == EL_SP_EXIT_CLOSED)
12699 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12700 element == EL_EXPANDABLE_STEELWALL_GROWING)
12702 else if (element == EL_EXPANDABLE_WALL ||
12703 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12704 element == EL_EXPANDABLE_WALL_VERTICAL ||
12705 element == EL_EXPANDABLE_WALL_ANY ||
12706 element == EL_BD_EXPANDABLE_WALL ||
12707 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12708 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12709 element == EL_EXPANDABLE_STEELWALL_ANY)
12710 CheckWallGrowing(x, y);
12711 else if (element == EL_FLAMES)
12712 CheckForDragon(x, y);
12713 else if (element == EL_EXPLOSION)
12714 ; // drawing of correct explosion animation is handled separately
12715 else if (element == EL_ELEMENT_SNAPPING ||
12716 element == EL_DIAGONAL_SHRINKING ||
12717 element == EL_DIAGONAL_GROWING)
12719 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]);
12721 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12723 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12724 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12726 if (IS_BELT_ACTIVE(element))
12727 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12729 if (game.magic_wall_active)
12731 int jx = local_player->jx, jy = local_player->jy;
12733 // play the element sound at the position nearest to the player
12734 if ((element == EL_MAGIC_WALL_FULL ||
12735 element == EL_MAGIC_WALL_ACTIVE ||
12736 element == EL_MAGIC_WALL_EMPTYING ||
12737 element == EL_BD_MAGIC_WALL_FULL ||
12738 element == EL_BD_MAGIC_WALL_ACTIVE ||
12739 element == EL_BD_MAGIC_WALL_EMPTYING ||
12740 element == EL_DC_MAGIC_WALL_FULL ||
12741 element == EL_DC_MAGIC_WALL_ACTIVE ||
12742 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12743 ABS(x - jx) + ABS(y - jy) <
12744 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12752 #if USE_NEW_AMOEBA_CODE
12753 // new experimental amoeba growth stuff
12754 if (!(FrameCounter % 8))
12756 static unsigned int random = 1684108901;
12758 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12760 x = RND(lev_fieldx);
12761 y = RND(lev_fieldy);
12762 element = Tile[x][y];
12764 if (!IS_PLAYER(x, y) &&
12765 (element == EL_EMPTY ||
12766 CAN_GROW_INTO(element) ||
12767 element == EL_QUICKSAND_EMPTY ||
12768 element == EL_QUICKSAND_FAST_EMPTY ||
12769 element == EL_ACID_SPLASH_LEFT ||
12770 element == EL_ACID_SPLASH_RIGHT))
12772 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12773 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12774 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12775 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12776 Tile[x][y] = EL_AMOEBA_DROP;
12779 random = random * 129 + 1;
12784 game.explosions_delayed = FALSE;
12786 SCAN_PLAYFIELD(x, y)
12788 element = Tile[x][y];
12790 if (ExplodeField[x][y])
12791 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12792 else if (element == EL_EXPLOSION)
12793 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12795 ExplodeField[x][y] = EX_TYPE_NONE;
12798 game.explosions_delayed = TRUE;
12800 if (game.magic_wall_active)
12802 if (!(game.magic_wall_time_left % 4))
12804 int element = Tile[magic_wall_x][magic_wall_y];
12806 if (element == EL_BD_MAGIC_WALL_FULL ||
12807 element == EL_BD_MAGIC_WALL_ACTIVE ||
12808 element == EL_BD_MAGIC_WALL_EMPTYING)
12809 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12810 else if (element == EL_DC_MAGIC_WALL_FULL ||
12811 element == EL_DC_MAGIC_WALL_ACTIVE ||
12812 element == EL_DC_MAGIC_WALL_EMPTYING)
12813 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12815 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12818 if (game.magic_wall_time_left > 0)
12820 game.magic_wall_time_left--;
12822 if (!game.magic_wall_time_left)
12824 SCAN_PLAYFIELD(x, y)
12826 element = Tile[x][y];
12828 if (element == EL_MAGIC_WALL_ACTIVE ||
12829 element == EL_MAGIC_WALL_FULL)
12831 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12832 TEST_DrawLevelField(x, y);
12834 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12835 element == EL_BD_MAGIC_WALL_FULL)
12837 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12838 TEST_DrawLevelField(x, y);
12840 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12841 element == EL_DC_MAGIC_WALL_FULL)
12843 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12844 TEST_DrawLevelField(x, y);
12848 game.magic_wall_active = FALSE;
12853 if (game.light_time_left > 0)
12855 game.light_time_left--;
12857 if (game.light_time_left == 0)
12858 RedrawAllLightSwitchesAndInvisibleElements();
12861 if (game.timegate_time_left > 0)
12863 game.timegate_time_left--;
12865 if (game.timegate_time_left == 0)
12866 CloseAllOpenTimegates();
12869 if (game.lenses_time_left > 0)
12871 game.lenses_time_left--;
12873 if (game.lenses_time_left == 0)
12874 RedrawAllInvisibleElementsForLenses();
12877 if (game.magnify_time_left > 0)
12879 game.magnify_time_left--;
12881 if (game.magnify_time_left == 0)
12882 RedrawAllInvisibleElementsForMagnifier();
12885 for (i = 0; i < MAX_PLAYERS; i++)
12887 struct PlayerInfo *player = &stored_player[i];
12889 if (SHIELD_ON(player))
12891 if (player->shield_deadly_time_left)
12892 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12893 else if (player->shield_normal_time_left)
12894 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12898 #if USE_DELAYED_GFX_REDRAW
12899 SCAN_PLAYFIELD(x, y)
12901 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12903 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12904 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12906 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12907 DrawLevelField(x, y);
12909 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12910 DrawLevelFieldCrumbled(x, y);
12912 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12913 DrawLevelFieldCrumbledNeighbours(x, y);
12915 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12916 DrawTwinkleOnField(x, y);
12919 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12924 PlayAllPlayersSound();
12926 for (i = 0; i < MAX_PLAYERS; i++)
12928 struct PlayerInfo *player = &stored_player[i];
12930 if (player->show_envelope != 0 && (!player->active ||
12931 player->MovPos == 0))
12933 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12935 player->show_envelope = 0;
12939 // use random number generator in every frame to make it less predictable
12940 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12943 mouse_action_last = mouse_action;
12946 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12948 int min_x = x, min_y = y, max_x = x, max_y = y;
12949 int scr_fieldx = getScreenFieldSizeX();
12950 int scr_fieldy = getScreenFieldSizeY();
12953 for (i = 0; i < MAX_PLAYERS; i++)
12955 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12957 if (!stored_player[i].active || &stored_player[i] == player)
12960 min_x = MIN(min_x, jx);
12961 min_y = MIN(min_y, jy);
12962 max_x = MAX(max_x, jx);
12963 max_y = MAX(max_y, jy);
12966 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12969 static boolean AllPlayersInVisibleScreen(void)
12973 for (i = 0; i < MAX_PLAYERS; i++)
12975 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12977 if (!stored_player[i].active)
12980 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12987 void ScrollLevel(int dx, int dy)
12989 int scroll_offset = 2 * TILEX_VAR;
12992 BlitBitmap(drawto_field, drawto_field,
12993 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12994 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12995 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12996 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12997 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12998 FY + TILEY_VAR * (dy == 1) - scroll_offset);
13002 x = (dx == 1 ? BX1 : BX2);
13003 for (y = BY1; y <= BY2; y++)
13004 DrawScreenField(x, y);
13009 y = (dy == 1 ? BY1 : BY2);
13010 for (x = BX1; x <= BX2; x++)
13011 DrawScreenField(x, y);
13014 redraw_mask |= REDRAW_FIELD;
13017 static boolean canFallDown(struct PlayerInfo *player)
13019 int jx = player->jx, jy = player->jy;
13021 return (IN_LEV_FIELD(jx, jy + 1) &&
13022 (IS_FREE(jx, jy + 1) ||
13023 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
13024 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
13025 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
13028 static boolean canPassField(int x, int y, int move_dir)
13030 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13031 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13032 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13033 int nextx = x + dx;
13034 int nexty = y + dy;
13035 int element = Tile[x][y];
13037 return (IS_PASSABLE_FROM(element, opposite_dir) &&
13038 !CAN_MOVE(element) &&
13039 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
13040 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
13041 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
13044 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
13046 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13047 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13048 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13052 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
13053 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
13054 (IS_DIGGABLE(Tile[newx][newy]) ||
13055 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
13056 canPassField(newx, newy, move_dir)));
13059 static void CheckGravityMovement(struct PlayerInfo *player)
13061 if (player->gravity && !player->programmed_action)
13063 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
13064 int move_dir_vertical = player->effective_action & MV_VERTICAL;
13065 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
13066 int jx = player->jx, jy = player->jy;
13067 boolean player_is_moving_to_valid_field =
13068 (!player_is_snapping &&
13069 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
13070 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
13071 boolean player_can_fall_down = canFallDown(player);
13073 if (player_can_fall_down &&
13074 !player_is_moving_to_valid_field)
13075 player->programmed_action = MV_DOWN;
13079 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
13081 return CheckGravityMovement(player);
13083 if (player->gravity && !player->programmed_action)
13085 int jx = player->jx, jy = player->jy;
13086 boolean field_under_player_is_free =
13087 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
13088 boolean player_is_standing_on_valid_field =
13089 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
13090 (IS_WALKABLE(Tile[jx][jy]) &&
13091 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
13093 if (field_under_player_is_free && !player_is_standing_on_valid_field)
13094 player->programmed_action = MV_DOWN;
13099 MovePlayerOneStep()
13100 -----------------------------------------------------------------------------
13101 dx, dy: direction (non-diagonal) to try to move the player to
13102 real_dx, real_dy: direction as read from input device (can be diagonal)
13105 boolean MovePlayerOneStep(struct PlayerInfo *player,
13106 int dx, int dy, int real_dx, int real_dy)
13108 int jx = player->jx, jy = player->jy;
13109 int new_jx = jx + dx, new_jy = jy + dy;
13111 boolean player_can_move = !player->cannot_move;
13113 if (!player->active || (!dx && !dy))
13114 return MP_NO_ACTION;
13116 player->MovDir = (dx < 0 ? MV_LEFT :
13117 dx > 0 ? MV_RIGHT :
13119 dy > 0 ? MV_DOWN : MV_NONE);
13121 if (!IN_LEV_FIELD(new_jx, new_jy))
13122 return MP_NO_ACTION;
13124 if (!player_can_move)
13126 if (player->MovPos == 0)
13128 player->is_moving = FALSE;
13129 player->is_digging = FALSE;
13130 player->is_collecting = FALSE;
13131 player->is_snapping = FALSE;
13132 player->is_pushing = FALSE;
13136 if (!network.enabled && game.centered_player_nr == -1 &&
13137 !AllPlayersInSight(player, new_jx, new_jy))
13138 return MP_NO_ACTION;
13140 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG);
13141 if (can_move != MP_MOVING)
13144 // check if DigField() has caused relocation of the player
13145 if (player->jx != jx || player->jy != jy)
13146 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
13148 StorePlayer[jx][jy] = 0;
13149 player->last_jx = jx;
13150 player->last_jy = jy;
13151 player->jx = new_jx;
13152 player->jy = new_jy;
13153 StorePlayer[new_jx][new_jy] = player->element_nr;
13155 if (player->move_delay_value_next != -1)
13157 player->move_delay_value = player->move_delay_value_next;
13158 player->move_delay_value_next = -1;
13162 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
13164 player->step_counter++;
13166 PlayerVisit[jx][jy] = FrameCounter;
13168 player->is_moving = TRUE;
13171 // should better be called in MovePlayer(), but this breaks some tapes
13172 ScrollPlayer(player, SCROLL_INIT);
13178 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
13180 int jx = player->jx, jy = player->jy;
13181 int old_jx = jx, old_jy = jy;
13182 int moved = MP_NO_ACTION;
13184 if (!player->active)
13189 if (player->MovPos == 0)
13191 player->is_moving = FALSE;
13192 player->is_digging = FALSE;
13193 player->is_collecting = FALSE;
13194 player->is_snapping = FALSE;
13195 player->is_pushing = FALSE;
13201 if (player->move_delay > 0)
13204 player->move_delay = -1; // set to "uninitialized" value
13206 // store if player is automatically moved to next field
13207 player->is_auto_moving = (player->programmed_action != MV_NONE);
13209 // remove the last programmed player action
13210 player->programmed_action = 0;
13212 if (player->MovPos)
13214 // should only happen if pre-1.2 tape recordings are played
13215 // this is only for backward compatibility
13217 int original_move_delay_value = player->move_delay_value;
13220 Debug("game:playing:MovePlayer",
13221 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13225 // scroll remaining steps with finest movement resolution
13226 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13228 while (player->MovPos)
13230 ScrollPlayer(player, SCROLL_GO_ON);
13231 ScrollScreen(NULL, SCROLL_GO_ON);
13233 AdvanceFrameAndPlayerCounters(player->index_nr);
13236 BackToFront_WithFrameDelay(0);
13239 player->move_delay_value = original_move_delay_value;
13242 player->is_active = FALSE;
13244 if (player->last_move_dir & MV_HORIZONTAL)
13246 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13247 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13251 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13252 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13255 if (!moved && !player->is_active)
13257 player->is_moving = FALSE;
13258 player->is_digging = FALSE;
13259 player->is_collecting = FALSE;
13260 player->is_snapping = FALSE;
13261 player->is_pushing = FALSE;
13267 if (moved & MP_MOVING && !ScreenMovPos &&
13268 (player->index_nr == game.centered_player_nr ||
13269 game.centered_player_nr == -1))
13271 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13273 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13275 // actual player has left the screen -- scroll in that direction
13276 if (jx != old_jx) // player has moved horizontally
13277 scroll_x += (jx - old_jx);
13278 else // player has moved vertically
13279 scroll_y += (jy - old_jy);
13283 int offset_raw = game.scroll_delay_value;
13285 if (jx != old_jx) // player has moved horizontally
13287 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13288 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13289 int new_scroll_x = jx - MIDPOSX + offset_x;
13291 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13292 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13293 scroll_x = new_scroll_x;
13295 // don't scroll over playfield boundaries
13296 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13298 // don't scroll more than one field at a time
13299 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13301 // don't scroll against the player's moving direction
13302 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13303 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13304 scroll_x = old_scroll_x;
13306 else // player has moved vertically
13308 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13309 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13310 int new_scroll_y = jy - MIDPOSY + offset_y;
13312 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13313 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13314 scroll_y = new_scroll_y;
13316 // don't scroll over playfield boundaries
13317 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13319 // don't scroll more than one field at a time
13320 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13322 // don't scroll against the player's moving direction
13323 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13324 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13325 scroll_y = old_scroll_y;
13329 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13331 if (!network.enabled && game.centered_player_nr == -1 &&
13332 !AllPlayersInVisibleScreen())
13334 scroll_x = old_scroll_x;
13335 scroll_y = old_scroll_y;
13339 ScrollScreen(player, SCROLL_INIT);
13340 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13345 player->StepFrame = 0;
13347 if (moved & MP_MOVING)
13349 if (old_jx != jx && old_jy == jy)
13350 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13351 else if (old_jx == jx && old_jy != jy)
13352 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13354 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13356 player->last_move_dir = player->MovDir;
13357 player->is_moving = TRUE;
13358 player->is_snapping = FALSE;
13359 player->is_switching = FALSE;
13360 player->is_dropping = FALSE;
13361 player->is_dropping_pressed = FALSE;
13362 player->drop_pressed_delay = 0;
13365 // should better be called here than above, but this breaks some tapes
13366 ScrollPlayer(player, SCROLL_INIT);
13371 CheckGravityMovementWhenNotMoving(player);
13373 player->is_moving = FALSE;
13375 /* at this point, the player is allowed to move, but cannot move right now
13376 (e.g. because of something blocking the way) -- ensure that the player
13377 is also allowed to move in the next frame (in old versions before 3.1.1,
13378 the player was forced to wait again for eight frames before next try) */
13380 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13381 player->move_delay = 0; // allow direct movement in the next frame
13384 if (player->move_delay == -1) // not yet initialized by DigField()
13385 player->move_delay = player->move_delay_value;
13387 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13389 TestIfPlayerTouchesBadThing(jx, jy);
13390 TestIfPlayerTouchesCustomElement(jx, jy);
13393 if (!player->active)
13394 RemovePlayer(player);
13399 void ScrollPlayer(struct PlayerInfo *player, int mode)
13401 int jx = player->jx, jy = player->jy;
13402 int last_jx = player->last_jx, last_jy = player->last_jy;
13403 int move_stepsize = TILEX / player->move_delay_value;
13405 if (!player->active)
13408 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13411 if (mode == SCROLL_INIT)
13413 player->actual_frame_counter.count = FrameCounter;
13414 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13416 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13417 Tile[last_jx][last_jy] == EL_EMPTY)
13419 int last_field_block_delay = 0; // start with no blocking at all
13420 int block_delay_adjustment = player->block_delay_adjustment;
13422 // if player blocks last field, add delay for exactly one move
13423 if (player->block_last_field)
13425 last_field_block_delay += player->move_delay_value;
13427 // when blocking enabled, prevent moving up despite gravity
13428 if (player->gravity && player->MovDir == MV_UP)
13429 block_delay_adjustment = -1;
13432 // add block delay adjustment (also possible when not blocking)
13433 last_field_block_delay += block_delay_adjustment;
13435 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13436 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13439 if (player->MovPos != 0) // player has not yet reached destination
13442 else if (!FrameReached(&player->actual_frame_counter))
13445 if (player->MovPos != 0)
13447 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13448 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13450 // before DrawPlayer() to draw correct player graphic for this case
13451 if (player->MovPos == 0)
13452 CheckGravityMovement(player);
13455 if (player->MovPos == 0) // player reached destination field
13457 if (player->move_delay_reset_counter > 0)
13459 player->move_delay_reset_counter--;
13461 if (player->move_delay_reset_counter == 0)
13463 // continue with normal speed after quickly moving through gate
13464 HALVE_PLAYER_SPEED(player);
13466 // be able to make the next move without delay
13467 player->move_delay = 0;
13471 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13472 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13473 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13474 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13475 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13476 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13477 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13478 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13480 ExitPlayer(player);
13482 if (game.players_still_needed == 0 &&
13483 (game.friends_still_needed == 0 ||
13484 IS_SP_ELEMENT(Tile[jx][jy])))
13488 player->last_jx = jx;
13489 player->last_jy = jy;
13491 // this breaks one level: "machine", level 000
13493 int move_direction = player->MovDir;
13494 int enter_side = MV_DIR_OPPOSITE(move_direction);
13495 int leave_side = move_direction;
13496 int old_jx = last_jx;
13497 int old_jy = last_jy;
13498 int old_element = Tile[old_jx][old_jy];
13499 int new_element = Tile[jx][jy];
13501 if (IS_CUSTOM_ELEMENT(old_element))
13502 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13504 player->index_bit, leave_side);
13506 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13507 CE_PLAYER_LEAVES_X,
13508 player->index_bit, leave_side);
13510 // needed because pushed element has not yet reached its destination,
13511 // so it would trigger a change event at its previous field location
13512 if (!player->is_pushing)
13514 if (IS_CUSTOM_ELEMENT(new_element))
13515 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13516 player->index_bit, enter_side);
13518 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13519 CE_PLAYER_ENTERS_X,
13520 player->index_bit, enter_side);
13523 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13524 CE_MOVE_OF_X, move_direction);
13527 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13529 TestIfPlayerTouchesBadThing(jx, jy);
13530 TestIfPlayerTouchesCustomElement(jx, jy);
13532 // needed because pushed element has not yet reached its destination,
13533 // so it would trigger a change event at its previous field location
13534 if (!player->is_pushing)
13535 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13537 if (level.finish_dig_collect &&
13538 (player->is_digging || player->is_collecting))
13540 int last_element = player->last_removed_element;
13541 int move_direction = player->MovDir;
13542 int enter_side = MV_DIR_OPPOSITE(move_direction);
13543 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13544 CE_PLAYER_COLLECTS_X);
13546 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13547 player->index_bit, enter_side);
13549 player->last_removed_element = EL_UNDEFINED;
13552 if (!player->active)
13553 RemovePlayer(player);
13556 if (level.use_step_counter)
13557 CheckLevelTime_StepCounter();
13559 if (tape.single_step && tape.recording && !tape.pausing &&
13560 !player->programmed_action)
13561 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13563 if (!player->programmed_action)
13564 CheckSaveEngineSnapshot(player);
13568 void ScrollScreen(struct PlayerInfo *player, int mode)
13570 static DelayCounter screen_frame_counter = { 0 };
13572 if (mode == SCROLL_INIT)
13574 // set scrolling step size according to actual player's moving speed
13575 ScrollStepSize = TILEX / player->move_delay_value;
13577 screen_frame_counter.count = FrameCounter;
13578 screen_frame_counter.value = 1;
13580 ScreenMovDir = player->MovDir;
13581 ScreenMovPos = player->MovPos;
13582 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13585 else if (!FrameReached(&screen_frame_counter))
13590 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13591 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13592 redraw_mask |= REDRAW_FIELD;
13595 ScreenMovDir = MV_NONE;
13598 void CheckNextToConditions(int x, int y)
13600 int element = Tile[x][y];
13602 if (IS_PLAYER(x, y))
13603 TestIfPlayerNextToCustomElement(x, y);
13605 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13606 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13607 TestIfElementNextToCustomElement(x, y);
13610 void TestIfPlayerNextToCustomElement(int x, int y)
13612 struct XY *xy = xy_topdown;
13613 static int trigger_sides[4][2] =
13615 // center side border side
13616 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13617 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13618 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13619 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13623 if (!IS_PLAYER(x, y))
13626 struct PlayerInfo *player = PLAYERINFO(x, y);
13628 if (player->is_moving)
13631 for (i = 0; i < NUM_DIRECTIONS; i++)
13633 int xx = x + xy[i].x;
13634 int yy = y + xy[i].y;
13635 int border_side = trigger_sides[i][1];
13636 int border_element;
13638 if (!IN_LEV_FIELD(xx, yy))
13641 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13642 continue; // center and border element not connected
13644 border_element = Tile[xx][yy];
13646 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13647 player->index_bit, border_side);
13648 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13649 CE_PLAYER_NEXT_TO_X,
13650 player->index_bit, border_side);
13652 /* use player element that is initially defined in the level playfield,
13653 not the player element that corresponds to the runtime player number
13654 (example: a level that contains EL_PLAYER_3 as the only player would
13655 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13657 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13658 CE_NEXT_TO_X, border_side);
13662 void TestIfPlayerTouchesCustomElement(int x, int y)
13664 struct XY *xy = xy_topdown;
13665 static int trigger_sides[4][2] =
13667 // center side border side
13668 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13669 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13670 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13671 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13673 static int touch_dir[4] =
13675 MV_LEFT | MV_RIGHT,
13680 int center_element = Tile[x][y]; // should always be non-moving!
13683 for (i = 0; i < NUM_DIRECTIONS; i++)
13685 int xx = x + xy[i].x;
13686 int yy = y + xy[i].y;
13687 int center_side = trigger_sides[i][0];
13688 int border_side = trigger_sides[i][1];
13689 int border_element;
13691 if (!IN_LEV_FIELD(xx, yy))
13694 if (IS_PLAYER(x, y)) // player found at center element
13696 struct PlayerInfo *player = PLAYERINFO(x, y);
13698 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13699 border_element = Tile[xx][yy]; // may be moving!
13700 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13701 border_element = Tile[xx][yy];
13702 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13703 border_element = MovingOrBlocked2Element(xx, yy);
13705 continue; // center and border element do not touch
13707 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13708 player->index_bit, border_side);
13709 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13710 CE_PLAYER_TOUCHES_X,
13711 player->index_bit, border_side);
13714 /* use player element that is initially defined in the level playfield,
13715 not the player element that corresponds to the runtime player number
13716 (example: a level that contains EL_PLAYER_3 as the only player would
13717 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13718 int player_element = PLAYERINFO(x, y)->initial_element;
13720 // as element "X" is the player here, check opposite (center) side
13721 CheckElementChangeBySide(xx, yy, border_element, player_element,
13722 CE_TOUCHING_X, center_side);
13725 else if (IS_PLAYER(xx, yy)) // player found at border element
13727 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13729 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13731 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13732 continue; // center and border element do not touch
13735 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13736 player->index_bit, center_side);
13737 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13738 CE_PLAYER_TOUCHES_X,
13739 player->index_bit, center_side);
13742 /* use player element that is initially defined in the level playfield,
13743 not the player element that corresponds to the runtime player number
13744 (example: a level that contains EL_PLAYER_3 as the only player would
13745 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13746 int player_element = PLAYERINFO(xx, yy)->initial_element;
13748 // as element "X" is the player here, check opposite (border) side
13749 CheckElementChangeBySide(x, y, center_element, player_element,
13750 CE_TOUCHING_X, border_side);
13758 void TestIfElementNextToCustomElement(int x, int y)
13760 struct XY *xy = xy_topdown;
13761 static int trigger_sides[4][2] =
13763 // center side border side
13764 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13765 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13766 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13767 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13769 int center_element = Tile[x][y]; // should always be non-moving!
13772 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13775 for (i = 0; i < NUM_DIRECTIONS; i++)
13777 int xx = x + xy[i].x;
13778 int yy = y + xy[i].y;
13779 int border_side = trigger_sides[i][1];
13780 int border_element;
13782 if (!IN_LEV_FIELD(xx, yy))
13785 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13786 continue; // center and border element not connected
13788 border_element = Tile[xx][yy];
13790 // check for change of center element (but change it only once)
13791 if (CheckElementChangeBySide(x, y, center_element, border_element,
13792 CE_NEXT_TO_X, border_side))
13797 void TestIfElementTouchesCustomElement(int x, int y)
13799 struct XY *xy = xy_topdown;
13800 static int trigger_sides[4][2] =
13802 // center side border side
13803 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13804 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13805 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13806 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13808 static int touch_dir[4] =
13810 MV_LEFT | MV_RIGHT,
13815 boolean change_center_element = FALSE;
13816 int center_element = Tile[x][y]; // should always be non-moving!
13817 int border_element_old[NUM_DIRECTIONS];
13820 for (i = 0; i < NUM_DIRECTIONS; i++)
13822 int xx = x + xy[i].x;
13823 int yy = y + xy[i].y;
13824 int border_element;
13826 border_element_old[i] = -1;
13828 if (!IN_LEV_FIELD(xx, yy))
13831 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13832 border_element = Tile[xx][yy]; // may be moving!
13833 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13834 border_element = Tile[xx][yy];
13835 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13836 border_element = MovingOrBlocked2Element(xx, yy);
13838 continue; // center and border element do not touch
13840 border_element_old[i] = border_element;
13843 for (i = 0; i < NUM_DIRECTIONS; i++)
13845 int xx = x + xy[i].x;
13846 int yy = y + xy[i].y;
13847 int center_side = trigger_sides[i][0];
13848 int border_element = border_element_old[i];
13850 if (border_element == -1)
13853 // check for change of border element
13854 CheckElementChangeBySide(xx, yy, border_element, center_element,
13855 CE_TOUCHING_X, center_side);
13857 // (center element cannot be player, so we don't have to check this here)
13860 for (i = 0; i < NUM_DIRECTIONS; i++)
13862 int xx = x + xy[i].x;
13863 int yy = y + xy[i].y;
13864 int border_side = trigger_sides[i][1];
13865 int border_element = border_element_old[i];
13867 if (border_element == -1)
13870 // check for change of center element (but change it only once)
13871 if (!change_center_element)
13872 change_center_element =
13873 CheckElementChangeBySide(x, y, center_element, border_element,
13874 CE_TOUCHING_X, border_side);
13876 if (IS_PLAYER(xx, yy))
13878 /* use player element that is initially defined in the level playfield,
13879 not the player element that corresponds to the runtime player number
13880 (example: a level that contains EL_PLAYER_3 as the only player would
13881 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13882 int player_element = PLAYERINFO(xx, yy)->initial_element;
13884 // as element "X" is the player here, check opposite (border) side
13885 CheckElementChangeBySide(x, y, center_element, player_element,
13886 CE_TOUCHING_X, border_side);
13891 void TestIfElementHitsCustomElement(int x, int y, int direction)
13893 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13894 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13895 int hitx = x + dx, hity = y + dy;
13896 int hitting_element = Tile[x][y];
13897 int touched_element;
13899 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13902 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13903 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13905 if (IN_LEV_FIELD(hitx, hity))
13907 int opposite_direction = MV_DIR_OPPOSITE(direction);
13908 int hitting_side = direction;
13909 int touched_side = opposite_direction;
13910 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13911 MovDir[hitx][hity] != direction ||
13912 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13918 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13919 CE_HITTING_X, touched_side);
13921 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13922 CE_HIT_BY_X, hitting_side);
13924 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13925 CE_HIT_BY_SOMETHING, opposite_direction);
13927 if (IS_PLAYER(hitx, hity))
13929 /* use player element that is initially defined in the level playfield,
13930 not the player element that corresponds to the runtime player number
13931 (example: a level that contains EL_PLAYER_3 as the only player would
13932 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13933 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13935 CheckElementChangeBySide(x, y, hitting_element, player_element,
13936 CE_HITTING_X, touched_side);
13941 // "hitting something" is also true when hitting the playfield border
13942 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13943 CE_HITTING_SOMETHING, direction);
13946 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13948 int i, kill_x = -1, kill_y = -1;
13950 int bad_element = -1;
13951 struct XY *test_xy = xy_topdown;
13952 static int test_dir[4] =
13960 for (i = 0; i < NUM_DIRECTIONS; i++)
13962 int test_x, test_y, test_move_dir, test_element;
13964 test_x = good_x + test_xy[i].x;
13965 test_y = good_y + test_xy[i].y;
13967 if (!IN_LEV_FIELD(test_x, test_y))
13971 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13973 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13975 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13976 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13978 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13979 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13983 bad_element = test_element;
13989 if (kill_x != -1 || kill_y != -1)
13991 if (IS_PLAYER(good_x, good_y))
13993 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13995 if (player->shield_deadly_time_left > 0 &&
13996 !IS_INDESTRUCTIBLE(bad_element))
13997 Bang(kill_x, kill_y);
13998 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13999 KillPlayer(player);
14002 Bang(good_x, good_y);
14006 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
14008 int i, kill_x = -1, kill_y = -1;
14009 int bad_element = Tile[bad_x][bad_y];
14010 struct XY *test_xy = xy_topdown;
14011 static int touch_dir[4] =
14013 MV_LEFT | MV_RIGHT,
14018 static int test_dir[4] =
14026 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
14029 for (i = 0; i < NUM_DIRECTIONS; i++)
14031 int test_x, test_y, test_move_dir, test_element;
14033 test_x = bad_x + test_xy[i].x;
14034 test_y = bad_y + test_xy[i].y;
14036 if (!IN_LEV_FIELD(test_x, test_y))
14040 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14042 test_element = Tile[test_x][test_y];
14044 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
14045 2nd case: DONT_TOUCH style bad thing does not move away from good thing
14047 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
14048 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
14050 // good thing is player or penguin that does not move away
14051 if (IS_PLAYER(test_x, test_y))
14053 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14055 if (bad_element == EL_ROBOT && player->is_moving)
14056 continue; // robot does not kill player if he is moving
14058 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
14060 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
14061 continue; // center and border element do not touch
14069 else if (test_element == EL_PENGUIN)
14079 if (kill_x != -1 || kill_y != -1)
14081 if (IS_PLAYER(kill_x, kill_y))
14083 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14085 if (player->shield_deadly_time_left > 0 &&
14086 !IS_INDESTRUCTIBLE(bad_element))
14087 Bang(bad_x, bad_y);
14088 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14089 KillPlayer(player);
14092 Bang(kill_x, kill_y);
14096 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
14098 int bad_element = Tile[bad_x][bad_y];
14099 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
14100 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
14101 int test_x = bad_x + dx, test_y = bad_y + dy;
14102 int test_move_dir, test_element;
14103 int kill_x = -1, kill_y = -1;
14105 if (!IN_LEV_FIELD(test_x, test_y))
14109 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14111 test_element = Tile[test_x][test_y];
14113 if (test_move_dir != bad_move_dir)
14115 // good thing can be player or penguin that does not move away
14116 if (IS_PLAYER(test_x, test_y))
14118 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14120 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
14121 player as being hit when he is moving towards the bad thing, because
14122 the "get hit by" condition would be lost after the player stops) */
14123 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
14124 return; // player moves away from bad thing
14129 else if (test_element == EL_PENGUIN)
14136 if (kill_x != -1 || kill_y != -1)
14138 if (IS_PLAYER(kill_x, kill_y))
14140 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14142 if (player->shield_deadly_time_left > 0 &&
14143 !IS_INDESTRUCTIBLE(bad_element))
14144 Bang(bad_x, bad_y);
14145 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14146 KillPlayer(player);
14149 Bang(kill_x, kill_y);
14153 void TestIfPlayerTouchesBadThing(int x, int y)
14155 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14158 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
14160 TestIfGoodThingHitsBadThing(x, y, move_dir);
14163 void TestIfBadThingTouchesPlayer(int x, int y)
14165 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14168 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
14170 TestIfBadThingHitsGoodThing(x, y, move_dir);
14173 void TestIfFriendTouchesBadThing(int x, int y)
14175 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14178 void TestIfBadThingTouchesFriend(int x, int y)
14180 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14183 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
14185 int i, kill_x = bad_x, kill_y = bad_y;
14186 struct XY *xy = xy_topdown;
14188 for (i = 0; i < NUM_DIRECTIONS; i++)
14192 x = bad_x + xy[i].x;
14193 y = bad_y + xy[i].y;
14194 if (!IN_LEV_FIELD(x, y))
14197 element = Tile[x][y];
14198 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14199 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14207 if (kill_x != bad_x || kill_y != bad_y)
14208 Bang(bad_x, bad_y);
14211 void KillPlayer(struct PlayerInfo *player)
14213 int jx = player->jx, jy = player->jy;
14215 if (!player->active)
14219 Debug("game:playing:KillPlayer",
14220 "0: killed == %d, active == %d, reanimated == %d",
14221 player->killed, player->active, player->reanimated);
14224 /* the following code was introduced to prevent an infinite loop when calling
14226 -> CheckTriggeredElementChangeExt()
14227 -> ExecuteCustomElementAction()
14229 -> (infinitely repeating the above sequence of function calls)
14230 which occurs when killing the player while having a CE with the setting
14231 "kill player X when explosion of <player X>"; the solution using a new
14232 field "player->killed" was chosen for backwards compatibility, although
14233 clever use of the fields "player->active" etc. would probably also work */
14235 if (player->killed)
14239 player->killed = TRUE;
14241 // remove accessible field at the player's position
14242 RemoveField(jx, jy);
14244 // deactivate shield (else Bang()/Explode() would not work right)
14245 player->shield_normal_time_left = 0;
14246 player->shield_deadly_time_left = 0;
14249 Debug("game:playing:KillPlayer",
14250 "1: killed == %d, active == %d, reanimated == %d",
14251 player->killed, player->active, player->reanimated);
14257 Debug("game:playing:KillPlayer",
14258 "2: killed == %d, active == %d, reanimated == %d",
14259 player->killed, player->active, player->reanimated);
14262 if (player->reanimated) // killed player may have been reanimated
14263 player->killed = player->reanimated = FALSE;
14265 BuryPlayer(player);
14268 static void KillPlayerUnlessEnemyProtected(int x, int y)
14270 if (!PLAYER_ENEMY_PROTECTED(x, y))
14271 KillPlayer(PLAYERINFO(x, y));
14274 static void KillPlayerUnlessExplosionProtected(int x, int y)
14276 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14277 KillPlayer(PLAYERINFO(x, y));
14280 void BuryPlayer(struct PlayerInfo *player)
14282 int jx = player->jx, jy = player->jy;
14284 if (!player->active)
14287 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14289 RemovePlayer(player);
14291 player->buried = TRUE;
14293 if (game.all_players_gone)
14294 game.GameOver = TRUE;
14297 void RemovePlayer(struct PlayerInfo *player)
14299 int jx = player->jx, jy = player->jy;
14300 int i, found = FALSE;
14302 player->present = FALSE;
14303 player->active = FALSE;
14305 // required for some CE actions (even if the player is not active anymore)
14306 player->MovPos = 0;
14308 if (!ExplodeField[jx][jy])
14309 StorePlayer[jx][jy] = 0;
14311 if (player->is_moving)
14312 TEST_DrawLevelField(player->last_jx, player->last_jy);
14314 for (i = 0; i < MAX_PLAYERS; i++)
14315 if (stored_player[i].active)
14320 game.all_players_gone = TRUE;
14321 game.GameOver = TRUE;
14324 game.exit_x = game.robot_wheel_x = jx;
14325 game.exit_y = game.robot_wheel_y = jy;
14328 void ExitPlayer(struct PlayerInfo *player)
14330 DrawPlayer(player); // needed here only to cleanup last field
14331 RemovePlayer(player);
14333 if (game.players_still_needed > 0)
14334 game.players_still_needed--;
14337 static void SetFieldForSnapping(int x, int y, int element, int direction,
14338 int player_index_bit)
14340 struct ElementInfo *ei = &element_info[element];
14341 int direction_bit = MV_DIR_TO_BIT(direction);
14342 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14343 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14344 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14346 Tile[x][y] = EL_ELEMENT_SNAPPING;
14347 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14348 MovDir[x][y] = direction;
14349 Store[x][y] = element;
14350 Store2[x][y] = player_index_bit;
14352 ResetGfxAnimation(x, y);
14354 GfxElement[x][y] = element;
14355 GfxAction[x][y] = action;
14356 GfxDir[x][y] = direction;
14357 GfxFrame[x][y] = -1;
14360 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14361 int player_index_bit)
14363 TestIfElementTouchesCustomElement(x, y); // for empty space
14365 if (level.finish_dig_collect)
14367 int dig_side = MV_DIR_OPPOSITE(direction);
14368 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14369 CE_PLAYER_COLLECTS_X);
14371 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14372 player_index_bit, dig_side);
14373 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14374 player_index_bit, dig_side);
14379 =============================================================================
14380 checkDiagonalPushing()
14381 -----------------------------------------------------------------------------
14382 check if diagonal input device direction results in pushing of object
14383 (by checking if the alternative direction is walkable, diggable, ...)
14384 =============================================================================
14387 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14388 int x, int y, int real_dx, int real_dy)
14390 int jx, jy, dx, dy, xx, yy;
14392 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14395 // diagonal direction: check alternative direction
14400 xx = jx + (dx == 0 ? real_dx : 0);
14401 yy = jy + (dy == 0 ? real_dy : 0);
14403 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14407 =============================================================================
14409 -----------------------------------------------------------------------------
14410 x, y: field next to player (non-diagonal) to try to dig to
14411 real_dx, real_dy: direction as read from input device (can be diagonal)
14412 =============================================================================
14415 static int DigField(struct PlayerInfo *player,
14416 int oldx, int oldy, int x, int y,
14417 int real_dx, int real_dy, int mode)
14419 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14420 boolean player_was_pushing = player->is_pushing;
14421 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14422 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14423 int jx = oldx, jy = oldy;
14424 int dx = x - jx, dy = y - jy;
14425 int nextx = x + dx, nexty = y + dy;
14426 int move_direction = (dx == -1 ? MV_LEFT :
14427 dx == +1 ? MV_RIGHT :
14429 dy == +1 ? MV_DOWN : MV_NONE);
14430 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14431 int dig_side = MV_DIR_OPPOSITE(move_direction);
14432 int old_element = Tile[jx][jy];
14433 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14436 if (is_player) // function can also be called by EL_PENGUIN
14438 if (player->MovPos == 0)
14440 player->is_digging = FALSE;
14441 player->is_collecting = FALSE;
14444 if (player->MovPos == 0) // last pushing move finished
14445 player->is_pushing = FALSE;
14447 if (mode == DF_NO_PUSH) // player just stopped pushing
14449 player->is_switching = FALSE;
14450 player->push_delay = -1;
14452 return MP_NO_ACTION;
14455 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14456 old_element = Back[jx][jy];
14458 // in case of element dropped at player position, check background
14459 else if (Back[jx][jy] != EL_EMPTY &&
14460 game.engine_version >= VERSION_IDENT(2,2,0,0))
14461 old_element = Back[jx][jy];
14463 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14464 return MP_NO_ACTION; // field has no opening in this direction
14466 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction))
14467 return MP_NO_ACTION; // field has no opening in this direction
14469 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14473 Tile[jx][jy] = player->artwork_element;
14474 InitMovingField(jx, jy, MV_DOWN);
14475 Store[jx][jy] = EL_ACID;
14476 ContinueMoving(jx, jy);
14477 BuryPlayer(player);
14479 return MP_DONT_RUN_INTO;
14482 if (player_can_move && DONT_RUN_INTO(element))
14484 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14486 return MP_DONT_RUN_INTO;
14489 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14490 return MP_NO_ACTION;
14492 collect_count = element_info[element].collect_count_initial;
14494 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14495 return MP_NO_ACTION;
14497 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14498 player_can_move = player_can_move_or_snap;
14500 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14501 game.engine_version >= VERSION_IDENT(2,2,0,0))
14503 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14504 player->index_bit, dig_side);
14505 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14506 player->index_bit, dig_side);
14508 if (element == EL_DC_LANDMINE)
14511 if (Tile[x][y] != element) // field changed by snapping
14514 return MP_NO_ACTION;
14517 if (player->gravity && is_player && !player->is_auto_moving &&
14518 canFallDown(player) && move_direction != MV_DOWN &&
14519 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14520 return MP_NO_ACTION; // player cannot walk here due to gravity
14522 if (player_can_move &&
14523 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14525 int sound_element = SND_ELEMENT(element);
14526 int sound_action = ACTION_WALKING;
14528 if (IS_RND_GATE(element))
14530 if (!player->key[RND_GATE_NR(element)])
14531 return MP_NO_ACTION;
14533 else if (IS_RND_GATE_GRAY(element))
14535 if (!player->key[RND_GATE_GRAY_NR(element)])
14536 return MP_NO_ACTION;
14538 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14540 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14541 return MP_NO_ACTION;
14543 else if (element == EL_EXIT_OPEN ||
14544 element == EL_EM_EXIT_OPEN ||
14545 element == EL_EM_EXIT_OPENING ||
14546 element == EL_STEEL_EXIT_OPEN ||
14547 element == EL_EM_STEEL_EXIT_OPEN ||
14548 element == EL_EM_STEEL_EXIT_OPENING ||
14549 element == EL_SP_EXIT_OPEN ||
14550 element == EL_SP_EXIT_OPENING)
14552 sound_action = ACTION_PASSING; // player is passing exit
14554 else if (element == EL_EMPTY)
14556 sound_action = ACTION_MOVING; // nothing to walk on
14559 // play sound from background or player, whatever is available
14560 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14561 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14563 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14565 else if (player_can_move &&
14566 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14568 if (!ACCESS_FROM(element, opposite_direction))
14569 return MP_NO_ACTION; // field not accessible from this direction
14571 if (CAN_MOVE(element)) // only fixed elements can be passed!
14572 return MP_NO_ACTION;
14574 if (IS_EM_GATE(element))
14576 if (!player->key[EM_GATE_NR(element)])
14577 return MP_NO_ACTION;
14579 else if (IS_EM_GATE_GRAY(element))
14581 if (!player->key[EM_GATE_GRAY_NR(element)])
14582 return MP_NO_ACTION;
14584 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14586 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14587 return MP_NO_ACTION;
14589 else if (IS_EMC_GATE(element))
14591 if (!player->key[EMC_GATE_NR(element)])
14592 return MP_NO_ACTION;
14594 else if (IS_EMC_GATE_GRAY(element))
14596 if (!player->key[EMC_GATE_GRAY_NR(element)])
14597 return MP_NO_ACTION;
14599 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14601 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14602 return MP_NO_ACTION;
14604 else if (element == EL_DC_GATE_WHITE ||
14605 element == EL_DC_GATE_WHITE_GRAY ||
14606 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14608 if (player->num_white_keys == 0)
14609 return MP_NO_ACTION;
14611 player->num_white_keys--;
14613 else if (IS_SP_PORT(element))
14615 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14616 element == EL_SP_GRAVITY_PORT_RIGHT ||
14617 element == EL_SP_GRAVITY_PORT_UP ||
14618 element == EL_SP_GRAVITY_PORT_DOWN)
14619 player->gravity = !player->gravity;
14620 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14621 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14622 element == EL_SP_GRAVITY_ON_PORT_UP ||
14623 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14624 player->gravity = TRUE;
14625 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14626 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14627 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14628 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14629 player->gravity = FALSE;
14632 // automatically move to the next field with double speed
14633 player->programmed_action = move_direction;
14635 if (player->move_delay_reset_counter == 0)
14637 player->move_delay_reset_counter = 2; // two double speed steps
14639 DOUBLE_PLAYER_SPEED(player);
14642 PlayLevelSoundAction(x, y, ACTION_PASSING);
14644 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14648 if (mode != DF_SNAP)
14650 GfxElement[x][y] = GFX_ELEMENT(element);
14651 player->is_digging = TRUE;
14654 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14656 // use old behaviour for old levels (digging)
14657 if (!level.finish_dig_collect)
14659 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14660 player->index_bit, dig_side);
14662 // if digging triggered player relocation, finish digging tile
14663 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14664 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14667 if (mode == DF_SNAP)
14669 if (level.block_snap_field)
14670 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14672 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14674 // use old behaviour for old levels (snapping)
14675 if (!level.finish_dig_collect)
14676 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14677 player->index_bit, dig_side);
14680 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14684 if (is_player && mode != DF_SNAP)
14686 GfxElement[x][y] = element;
14687 player->is_collecting = TRUE;
14690 if (element == EL_SPEED_PILL)
14692 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14694 else if (element == EL_EXTRA_TIME && level.time > 0)
14696 TimeLeft += level.extra_time;
14698 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14700 DisplayGameControlValues();
14702 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14704 int shield_time = (element == EL_SHIELD_DEADLY ?
14705 level.shield_deadly_time :
14706 level.shield_normal_time);
14708 player->shield_normal_time_left += shield_time;
14709 if (element == EL_SHIELD_DEADLY)
14710 player->shield_deadly_time_left += shield_time;
14712 else if (element == EL_DYNAMITE ||
14713 element == EL_EM_DYNAMITE ||
14714 element == EL_SP_DISK_RED)
14716 if (player->inventory_size < MAX_INVENTORY_SIZE)
14717 player->inventory_element[player->inventory_size++] = element;
14719 DrawGameDoorValues();
14721 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14723 player->dynabomb_count++;
14724 player->dynabombs_left++;
14726 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14728 player->dynabomb_size++;
14730 else if (element == EL_DYNABOMB_INCREASE_POWER)
14732 player->dynabomb_xl = TRUE;
14734 else if (IS_KEY(element))
14736 player->key[KEY_NR(element)] = TRUE;
14738 DrawGameDoorValues();
14740 else if (element == EL_DC_KEY_WHITE)
14742 player->num_white_keys++;
14744 // display white keys?
14745 // DrawGameDoorValues();
14747 else if (IS_ENVELOPE(element))
14749 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14751 if (!wait_for_snapping)
14752 player->show_envelope = element;
14754 else if (element == EL_EMC_LENSES)
14756 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14758 RedrawAllInvisibleElementsForLenses();
14760 else if (element == EL_EMC_MAGNIFIER)
14762 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14764 RedrawAllInvisibleElementsForMagnifier();
14766 else if (IS_DROPPABLE(element) ||
14767 IS_THROWABLE(element)) // can be collected and dropped
14771 if (collect_count == 0)
14772 player->inventory_infinite_element = element;
14774 for (i = 0; i < collect_count; i++)
14775 if (player->inventory_size < MAX_INVENTORY_SIZE)
14776 player->inventory_element[player->inventory_size++] = element;
14778 DrawGameDoorValues();
14780 else if (collect_count > 0)
14782 game.gems_still_needed -= collect_count;
14783 if (game.gems_still_needed < 0)
14784 game.gems_still_needed = 0;
14786 game.snapshot.collected_item = TRUE;
14788 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14790 DisplayGameControlValues();
14793 RaiseScoreElement(element);
14794 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14796 // use old behaviour for old levels (collecting)
14797 if (!level.finish_dig_collect && is_player)
14799 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14800 player->index_bit, dig_side);
14802 // if collecting triggered player relocation, finish collecting tile
14803 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14804 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14807 if (mode == DF_SNAP)
14809 if (level.block_snap_field)
14810 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14812 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14814 // use old behaviour for old levels (snapping)
14815 if (!level.finish_dig_collect)
14816 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14817 player->index_bit, dig_side);
14820 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14822 if (mode == DF_SNAP && element != EL_BD_ROCK)
14823 return MP_NO_ACTION;
14825 if (CAN_FALL(element) && dy)
14826 return MP_NO_ACTION;
14828 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14829 !(element == EL_SPRING && level.use_spring_bug))
14830 return MP_NO_ACTION;
14832 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14833 ((move_direction & MV_VERTICAL &&
14834 ((element_info[element].move_pattern & MV_LEFT &&
14835 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14836 (element_info[element].move_pattern & MV_RIGHT &&
14837 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14838 (move_direction & MV_HORIZONTAL &&
14839 ((element_info[element].move_pattern & MV_UP &&
14840 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14841 (element_info[element].move_pattern & MV_DOWN &&
14842 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14843 return MP_NO_ACTION;
14845 // do not push elements already moving away faster than player
14846 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14847 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14848 return MP_NO_ACTION;
14850 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14852 if (player->push_delay_value == -1 || !player_was_pushing)
14853 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14855 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14857 if (player->push_delay_value == -1)
14858 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14860 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14862 if (!player->is_pushing)
14863 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14866 player->is_pushing = TRUE;
14867 player->is_active = TRUE;
14869 if (!(IN_LEV_FIELD(nextx, nexty) &&
14870 (IS_FREE(nextx, nexty) ||
14871 (IS_SB_ELEMENT(element) &&
14872 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14873 (IS_CUSTOM_ELEMENT(element) &&
14874 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14875 return MP_NO_ACTION;
14877 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14878 return MP_NO_ACTION;
14880 if (player->push_delay == -1) // new pushing; restart delay
14881 player->push_delay = 0;
14883 if (player->push_delay < player->push_delay_value &&
14884 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14885 element != EL_SPRING && element != EL_BALLOON)
14887 // make sure that there is no move delay before next try to push
14888 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14889 player->move_delay = 0;
14891 return MP_NO_ACTION;
14894 if (IS_CUSTOM_ELEMENT(element) &&
14895 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14897 if (!DigFieldByCE(nextx, nexty, element))
14898 return MP_NO_ACTION;
14901 if (IS_SB_ELEMENT(element))
14903 boolean sokoban_task_solved = FALSE;
14905 if (element == EL_SOKOBAN_FIELD_FULL)
14907 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14909 IncrementSokobanFieldsNeeded();
14910 IncrementSokobanObjectsNeeded();
14913 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14915 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14917 DecrementSokobanFieldsNeeded();
14918 DecrementSokobanObjectsNeeded();
14920 // sokoban object was pushed from empty field to sokoban field
14921 if (Back[x][y] == EL_EMPTY)
14922 sokoban_task_solved = TRUE;
14925 Tile[x][y] = EL_SOKOBAN_OBJECT;
14927 if (Back[x][y] == Back[nextx][nexty])
14928 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14929 else if (Back[x][y] != 0)
14930 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14933 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14936 if (sokoban_task_solved &&
14937 game.sokoban_fields_still_needed == 0 &&
14938 game.sokoban_objects_still_needed == 0 &&
14939 level.auto_exit_sokoban)
14941 game.players_still_needed = 0;
14945 PlaySound(SND_GAME_SOKOBAN_SOLVING);
14949 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14951 InitMovingField(x, y, move_direction);
14952 GfxAction[x][y] = ACTION_PUSHING;
14954 if (mode == DF_SNAP)
14955 ContinueMoving(x, y);
14957 MovPos[x][y] = (dx != 0 ? dx : dy);
14959 Pushed[x][y] = TRUE;
14960 Pushed[nextx][nexty] = TRUE;
14962 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14963 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14965 player->push_delay_value = -1; // get new value later
14967 // check for element change _after_ element has been pushed
14968 if (game.use_change_when_pushing_bug)
14970 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14971 player->index_bit, dig_side);
14972 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14973 player->index_bit, dig_side);
14976 else if (IS_SWITCHABLE(element))
14978 if (PLAYER_SWITCHING(player, x, y))
14980 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14981 player->index_bit, dig_side);
14986 player->is_switching = TRUE;
14987 player->switch_x = x;
14988 player->switch_y = y;
14990 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14992 if (element == EL_ROBOT_WHEEL)
14994 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14996 game.robot_wheel_x = x;
14997 game.robot_wheel_y = y;
14998 game.robot_wheel_active = TRUE;
15000 TEST_DrawLevelField(x, y);
15002 else if (element == EL_SP_TERMINAL)
15006 SCAN_PLAYFIELD(xx, yy)
15008 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
15012 else if (Tile[xx][yy] == EL_SP_TERMINAL)
15014 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
15016 ResetGfxAnimation(xx, yy);
15017 TEST_DrawLevelField(xx, yy);
15021 else if (IS_BELT_SWITCH(element))
15023 ToggleBeltSwitch(x, y);
15025 else if (element == EL_SWITCHGATE_SWITCH_UP ||
15026 element == EL_SWITCHGATE_SWITCH_DOWN ||
15027 element == EL_DC_SWITCHGATE_SWITCH_UP ||
15028 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
15030 ToggleSwitchgateSwitch();
15032 else if (element == EL_LIGHT_SWITCH ||
15033 element == EL_LIGHT_SWITCH_ACTIVE)
15035 ToggleLightSwitch(x, y);
15037 else if (element == EL_TIMEGATE_SWITCH ||
15038 element == EL_DC_TIMEGATE_SWITCH)
15040 ActivateTimegateSwitch(x, y);
15042 else if (element == EL_BALLOON_SWITCH_LEFT ||
15043 element == EL_BALLOON_SWITCH_RIGHT ||
15044 element == EL_BALLOON_SWITCH_UP ||
15045 element == EL_BALLOON_SWITCH_DOWN ||
15046 element == EL_BALLOON_SWITCH_NONE ||
15047 element == EL_BALLOON_SWITCH_ANY)
15049 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
15050 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
15051 element == EL_BALLOON_SWITCH_UP ? MV_UP :
15052 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
15053 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
15056 else if (element == EL_LAMP)
15058 Tile[x][y] = EL_LAMP_ACTIVE;
15059 game.lights_still_needed--;
15061 ResetGfxAnimation(x, y);
15062 TEST_DrawLevelField(x, y);
15064 else if (element == EL_TIME_ORB_FULL)
15066 Tile[x][y] = EL_TIME_ORB_EMPTY;
15068 if (level.time > 0 || level.use_time_orb_bug)
15070 TimeLeft += level.time_orb_time;
15071 game.no_level_time_limit = FALSE;
15073 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
15075 DisplayGameControlValues();
15078 ResetGfxAnimation(x, y);
15079 TEST_DrawLevelField(x, y);
15081 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
15082 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15086 game.ball_active = !game.ball_active;
15088 SCAN_PLAYFIELD(xx, yy)
15090 int e = Tile[xx][yy];
15092 if (game.ball_active)
15094 if (e == EL_EMC_MAGIC_BALL)
15095 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
15096 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
15097 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
15101 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
15102 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
15103 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15104 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
15109 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15110 player->index_bit, dig_side);
15112 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15113 player->index_bit, dig_side);
15115 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15116 player->index_bit, dig_side);
15122 if (!PLAYER_SWITCHING(player, x, y))
15124 player->is_switching = TRUE;
15125 player->switch_x = x;
15126 player->switch_y = y;
15128 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
15129 player->index_bit, dig_side);
15130 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15131 player->index_bit, dig_side);
15133 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
15134 player->index_bit, dig_side);
15135 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15136 player->index_bit, dig_side);
15139 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
15140 player->index_bit, dig_side);
15141 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15142 player->index_bit, dig_side);
15144 return MP_NO_ACTION;
15147 player->push_delay = -1;
15149 if (is_player) // function can also be called by EL_PENGUIN
15151 if (Tile[x][y] != element) // really digged/collected something
15153 player->is_collecting = !player->is_digging;
15154 player->is_active = TRUE;
15156 player->last_removed_element = element;
15163 static boolean DigFieldByCE(int x, int y, int digging_element)
15165 int element = Tile[x][y];
15167 if (!IS_FREE(x, y))
15169 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
15170 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
15173 // no element can dig solid indestructible elements
15174 if (IS_INDESTRUCTIBLE(element) &&
15175 !IS_DIGGABLE(element) &&
15176 !IS_COLLECTIBLE(element))
15179 if (AmoebaNr[x][y] &&
15180 (element == EL_AMOEBA_FULL ||
15181 element == EL_BD_AMOEBA ||
15182 element == EL_AMOEBA_GROWING))
15184 AmoebaCnt[AmoebaNr[x][y]]--;
15185 AmoebaCnt2[AmoebaNr[x][y]]--;
15188 if (IS_MOVING(x, y))
15189 RemoveMovingField(x, y);
15193 TEST_DrawLevelField(x, y);
15196 // if digged element was about to explode, prevent the explosion
15197 ExplodeField[x][y] = EX_TYPE_NONE;
15199 PlayLevelSoundAction(x, y, action);
15202 Store[x][y] = EL_EMPTY;
15204 // this makes it possible to leave the removed element again
15205 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15206 Store[x][y] = element;
15211 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15213 int jx = player->jx, jy = player->jy;
15214 int x = jx + dx, y = jy + dy;
15215 int snap_direction = (dx == -1 ? MV_LEFT :
15216 dx == +1 ? MV_RIGHT :
15218 dy == +1 ? MV_DOWN : MV_NONE);
15219 boolean can_continue_snapping = (level.continuous_snapping &&
15220 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15222 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15225 if (!player->active || !IN_LEV_FIELD(x, y))
15233 if (player->MovPos == 0)
15234 player->is_pushing = FALSE;
15236 player->is_snapping = FALSE;
15238 if (player->MovPos == 0)
15240 player->is_moving = FALSE;
15241 player->is_digging = FALSE;
15242 player->is_collecting = FALSE;
15248 // prevent snapping with already pressed snap key when not allowed
15249 if (player->is_snapping && !can_continue_snapping)
15252 player->MovDir = snap_direction;
15254 if (player->MovPos == 0)
15256 player->is_moving = FALSE;
15257 player->is_digging = FALSE;
15258 player->is_collecting = FALSE;
15261 player->is_dropping = FALSE;
15262 player->is_dropping_pressed = FALSE;
15263 player->drop_pressed_delay = 0;
15265 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15268 player->is_snapping = TRUE;
15269 player->is_active = TRUE;
15271 if (player->MovPos == 0)
15273 player->is_moving = FALSE;
15274 player->is_digging = FALSE;
15275 player->is_collecting = FALSE;
15278 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15279 TEST_DrawLevelField(player->last_jx, player->last_jy);
15281 TEST_DrawLevelField(x, y);
15286 static boolean DropElement(struct PlayerInfo *player)
15288 int old_element, new_element;
15289 int dropx = player->jx, dropy = player->jy;
15290 int drop_direction = player->MovDir;
15291 int drop_side = drop_direction;
15292 int drop_element = get_next_dropped_element(player);
15294 /* do not drop an element on top of another element; when holding drop key
15295 pressed without moving, dropped element must move away before the next
15296 element can be dropped (this is especially important if the next element
15297 is dynamite, which can be placed on background for historical reasons) */
15298 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15301 if (IS_THROWABLE(drop_element))
15303 dropx += GET_DX_FROM_DIR(drop_direction);
15304 dropy += GET_DY_FROM_DIR(drop_direction);
15306 if (!IN_LEV_FIELD(dropx, dropy))
15310 old_element = Tile[dropx][dropy]; // old element at dropping position
15311 new_element = drop_element; // default: no change when dropping
15313 // check if player is active, not moving and ready to drop
15314 if (!player->active || player->MovPos || player->drop_delay > 0)
15317 // check if player has anything that can be dropped
15318 if (new_element == EL_UNDEFINED)
15321 // only set if player has anything that can be dropped
15322 player->is_dropping_pressed = TRUE;
15324 // check if drop key was pressed long enough for EM style dynamite
15325 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15328 // check if anything can be dropped at the current position
15329 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15332 // collected custom elements can only be dropped on empty fields
15333 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15336 if (old_element != EL_EMPTY)
15337 Back[dropx][dropy] = old_element; // store old element on this field
15339 ResetGfxAnimation(dropx, dropy);
15340 ResetRandomAnimationValue(dropx, dropy);
15342 if (player->inventory_size > 0 ||
15343 player->inventory_infinite_element != EL_UNDEFINED)
15345 if (player->inventory_size > 0)
15347 player->inventory_size--;
15349 DrawGameDoorValues();
15351 if (new_element == EL_DYNAMITE)
15352 new_element = EL_DYNAMITE_ACTIVE;
15353 else if (new_element == EL_EM_DYNAMITE)
15354 new_element = EL_EM_DYNAMITE_ACTIVE;
15355 else if (new_element == EL_SP_DISK_RED)
15356 new_element = EL_SP_DISK_RED_ACTIVE;
15359 Tile[dropx][dropy] = new_element;
15361 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15362 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15363 el2img(Tile[dropx][dropy]), 0);
15365 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15367 // needed if previous element just changed to "empty" in the last frame
15368 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15370 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15371 player->index_bit, drop_side);
15372 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15374 player->index_bit, drop_side);
15376 TestIfElementTouchesCustomElement(dropx, dropy);
15378 else // player is dropping a dyna bomb
15380 player->dynabombs_left--;
15382 Tile[dropx][dropy] = new_element;
15384 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15385 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15386 el2img(Tile[dropx][dropy]), 0);
15388 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15391 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15392 InitField_WithBug1(dropx, dropy, FALSE);
15394 new_element = Tile[dropx][dropy]; // element might have changed
15396 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15397 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15399 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15400 MovDir[dropx][dropy] = drop_direction;
15402 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15404 // do not cause impact style collision by dropping elements that can fall
15405 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15408 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15409 player->is_dropping = TRUE;
15411 player->drop_pressed_delay = 0;
15412 player->is_dropping_pressed = FALSE;
15414 player->drop_x = dropx;
15415 player->drop_y = dropy;
15420 // ----------------------------------------------------------------------------
15421 // game sound playing functions
15422 // ----------------------------------------------------------------------------
15424 static int *loop_sound_frame = NULL;
15425 static int *loop_sound_volume = NULL;
15427 void InitPlayLevelSound(void)
15429 int num_sounds = getSoundListSize();
15431 checked_free(loop_sound_frame);
15432 checked_free(loop_sound_volume);
15434 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15435 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15438 static void PlayLevelSoundExt(int x, int y, int nr, boolean is_loop_sound)
15440 int sx = SCREENX(x), sy = SCREENY(y);
15441 int volume, stereo_position;
15442 int max_distance = 8;
15443 int type = (is_loop_sound ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15445 if ((!setup.sound_simple && !is_loop_sound) ||
15446 (!setup.sound_loops && is_loop_sound))
15449 if (!IN_LEV_FIELD(x, y) ||
15450 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15451 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15454 volume = SOUND_MAX_VOLUME;
15456 if (!IN_SCR_FIELD(sx, sy))
15458 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15459 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15461 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15464 stereo_position = (SOUND_MAX_LEFT +
15465 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15466 (SCR_FIELDX + 2 * max_distance));
15470 /* This assures that quieter loop sounds do not overwrite louder ones,
15471 while restarting sound volume comparison with each new game frame. */
15473 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15476 loop_sound_volume[nr] = volume;
15477 loop_sound_frame[nr] = FrameCounter;
15480 PlaySoundExt(nr, volume, stereo_position, type);
15483 static void PlayLevelSound(int x, int y, int nr)
15485 PlayLevelSoundExt(x, y, nr, IS_LOOP_SOUND(nr));
15488 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15490 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15491 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15492 y < LEVELY(BY1) ? LEVELY(BY1) :
15493 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15497 static void PlayLevelSoundAction(int x, int y, int action)
15499 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15502 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15504 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15506 if (sound_effect != SND_UNDEFINED)
15507 PlayLevelSound(x, y, sound_effect);
15510 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15513 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15515 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15516 PlayLevelSound(x, y, sound_effect);
15519 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15521 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15523 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15524 PlayLevelSound(x, y, sound_effect);
15527 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15529 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15531 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15532 StopSound(sound_effect);
15535 static int getLevelMusicNr(void)
15537 int level_pos = level_nr - leveldir_current->first_level;
15539 if (levelset.music[level_nr] != MUS_UNDEFINED)
15540 return levelset.music[level_nr]; // from config file
15542 return MAP_NOCONF_MUSIC(level_pos); // from music dir
15545 static void FadeLevelSounds(void)
15550 static void FadeLevelMusic(void)
15552 int music_nr = getLevelMusicNr();
15553 char *curr_music = getCurrentlyPlayingMusicFilename();
15554 char *next_music = getMusicInfoEntryFilename(music_nr);
15556 if (!strEqual(curr_music, next_music))
15560 void FadeLevelSoundsAndMusic(void)
15566 static void PlayLevelMusic(void)
15568 int music_nr = getLevelMusicNr();
15569 char *curr_music = getCurrentlyPlayingMusicFilename();
15570 char *next_music = getMusicInfoEntryFilename(music_nr);
15572 if (!strEqual(curr_music, next_music))
15573 PlayMusicLoop(music_nr);
15576 static int getSoundAction_BD(int sample)
15580 case GD_S_STONE_PUSHING:
15581 case GD_S_MEGA_STONE_PUSHING:
15582 case GD_S_FLYING_STONE_PUSHING:
15583 case GD_S_WAITING_STONE_PUSHING:
15584 case GD_S_CHASING_STONE_PUSHING:
15585 case GD_S_NUT_PUSHING:
15586 case GD_S_NITRO_PACK_PUSHING:
15587 case GD_S_BLADDER_PUSHING:
15588 case GD_S_BOX_PUSHING:
15589 return ACTION_PUSHING;
15591 case GD_S_STONE_FALLING:
15592 case GD_S_MEGA_STONE_FALLING:
15593 case GD_S_FLYING_STONE_FALLING:
15594 case GD_S_NUT_FALLING:
15595 case GD_S_DIRT_BALL_FALLING:
15596 case GD_S_DIRT_LOOSE_FALLING:
15597 case GD_S_NITRO_PACK_FALLING:
15598 case GD_S_FALLING_WALL_FALLING:
15599 return ACTION_FALLING;
15601 case GD_S_STONE_IMPACT:
15602 case GD_S_MEGA_STONE_IMPACT:
15603 case GD_S_FLYING_STONE_IMPACT:
15604 case GD_S_NUT_IMPACT:
15605 case GD_S_DIRT_BALL_IMPACT:
15606 case GD_S_DIRT_LOOSE_IMPACT:
15607 case GD_S_NITRO_PACK_IMPACT:
15608 case GD_S_FALLING_WALL_IMPACT:
15609 return ACTION_IMPACT;
15611 case GD_S_NUT_CRACKING:
15612 return ACTION_BREAKING;
15614 case GD_S_EXPANDING_WALL:
15615 case GD_S_WALL_REAPPEARING:
15618 case GD_S_ACID_SPREADING:
15619 return ACTION_GROWING;
15621 case GD_S_DIAMOND_COLLECTING:
15622 case GD_S_FLYING_DIAMOND_COLLECTING:
15623 case GD_S_SKELETON_COLLECTING:
15624 case GD_S_PNEUMATIC_COLLECTING:
15625 case GD_S_BOMB_COLLECTING:
15626 case GD_S_CLOCK_COLLECTING:
15627 case GD_S_SWEET_COLLECTING:
15628 case GD_S_KEY_COLLECTING:
15629 case GD_S_DIAMOND_KEY_COLLECTING:
15630 return ACTION_COLLECTING;
15632 case GD_S_BOMB_PLACING:
15633 case GD_S_REPLICATOR:
15634 return ACTION_DROPPING;
15636 case GD_S_BLADDER_MOVING:
15637 return ACTION_MOVING;
15639 case GD_S_BLADDER_SPENDER:
15640 case GD_S_BLADDER_CONVERTING:
15641 case GD_S_GRAVITY_CHANGING:
15642 return ACTION_CHANGING;
15644 case GD_S_BITER_EATING:
15645 return ACTION_EATING;
15647 case GD_S_DOOR_OPENING:
15648 case GD_S_CRACKING:
15649 return ACTION_OPENING;
15651 case GD_S_DIRT_WALKING:
15652 return ACTION_DIGGING;
15654 case GD_S_EMPTY_WALKING:
15655 return ACTION_WALKING;
15657 case GD_S_SWITCH_BITER:
15658 case GD_S_SWITCH_CREATURES:
15659 case GD_S_SWITCH_GRAVITY:
15660 case GD_S_SWITCH_EXPANDING:
15661 case GD_S_SWITCH_CONVEYOR:
15662 case GD_S_SWITCH_REPLICATOR:
15663 case GD_S_STIRRING:
15664 return ACTION_ACTIVATING;
15666 case GD_S_TELEPORTER:
15667 return ACTION_PASSING;
15669 case GD_S_EXPLODING:
15670 case GD_S_BOMB_EXPLODING:
15671 case GD_S_GHOST_EXPLODING:
15672 case GD_S_VOODOO_EXPLODING:
15673 case GD_S_NITRO_PACK_EXPLODING:
15674 return ACTION_EXPLODING;
15676 case GD_S_COVERING:
15678 case GD_S_MAGIC_WALL:
15679 case GD_S_PNEUMATIC_HAMMER:
15681 return ACTION_ACTIVE;
15683 case GD_S_DIAMOND_FALLING_RANDOM:
15684 case GD_S_DIAMOND_FALLING_1:
15685 case GD_S_DIAMOND_FALLING_2:
15686 case GD_S_DIAMOND_FALLING_3:
15687 case GD_S_DIAMOND_FALLING_4:
15688 case GD_S_DIAMOND_FALLING_5:
15689 case GD_S_DIAMOND_FALLING_6:
15690 case GD_S_DIAMOND_FALLING_7:
15691 case GD_S_DIAMOND_FALLING_8:
15692 case GD_S_DIAMOND_IMPACT_RANDOM:
15693 case GD_S_DIAMOND_IMPACT_1:
15694 case GD_S_DIAMOND_IMPACT_2:
15695 case GD_S_DIAMOND_IMPACT_3:
15696 case GD_S_DIAMOND_IMPACT_4:
15697 case GD_S_DIAMOND_IMPACT_5:
15698 case GD_S_DIAMOND_IMPACT_6:
15699 case GD_S_DIAMOND_IMPACT_7:
15700 case GD_S_DIAMOND_IMPACT_8:
15701 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15702 case GD_S_FLYING_DIAMOND_FALLING_1:
15703 case GD_S_FLYING_DIAMOND_FALLING_2:
15704 case GD_S_FLYING_DIAMOND_FALLING_3:
15705 case GD_S_FLYING_DIAMOND_FALLING_4:
15706 case GD_S_FLYING_DIAMOND_FALLING_5:
15707 case GD_S_FLYING_DIAMOND_FALLING_6:
15708 case GD_S_FLYING_DIAMOND_FALLING_7:
15709 case GD_S_FLYING_DIAMOND_FALLING_8:
15710 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15711 case GD_S_FLYING_DIAMOND_IMPACT_1:
15712 case GD_S_FLYING_DIAMOND_IMPACT_2:
15713 case GD_S_FLYING_DIAMOND_IMPACT_3:
15714 case GD_S_FLYING_DIAMOND_IMPACT_4:
15715 case GD_S_FLYING_DIAMOND_IMPACT_5:
15716 case GD_S_FLYING_DIAMOND_IMPACT_6:
15717 case GD_S_FLYING_DIAMOND_IMPACT_7:
15718 case GD_S_FLYING_DIAMOND_IMPACT_8:
15719 case GD_S_TIMEOUT_0:
15720 case GD_S_TIMEOUT_1:
15721 case GD_S_TIMEOUT_2:
15722 case GD_S_TIMEOUT_3:
15723 case GD_S_TIMEOUT_4:
15724 case GD_S_TIMEOUT_5:
15725 case GD_S_TIMEOUT_6:
15726 case GD_S_TIMEOUT_7:
15727 case GD_S_TIMEOUT_8:
15728 case GD_S_TIMEOUT_9:
15729 case GD_S_TIMEOUT_10:
15730 case GD_S_BONUS_LIFE:
15731 // trigger special post-processing (and force sound to be non-looping)
15732 return ACTION_OTHER;
15734 case GD_S_AMOEBA_MAGIC:
15735 case GD_S_FINISHED:
15736 // trigger special post-processing (and force sound to be looping)
15737 return ACTION_DEFAULT;
15740 return ACTION_DEFAULT;
15744 static int getSoundEffect_BD(int element_bd, int sample)
15746 int sound_action = getSoundAction_BD(sample);
15747 int sound_effect = element_info[SND_ELEMENT(element_bd)].sound[sound_action];
15751 if (sound_action != ACTION_OTHER &&
15752 sound_action != ACTION_DEFAULT)
15753 return sound_effect;
15755 // special post-processing for some sounds
15758 case GD_S_DIAMOND_FALLING_RANDOM:
15759 case GD_S_DIAMOND_FALLING_1:
15760 case GD_S_DIAMOND_FALLING_2:
15761 case GD_S_DIAMOND_FALLING_3:
15762 case GD_S_DIAMOND_FALLING_4:
15763 case GD_S_DIAMOND_FALLING_5:
15764 case GD_S_DIAMOND_FALLING_6:
15765 case GD_S_DIAMOND_FALLING_7:
15766 case GD_S_DIAMOND_FALLING_8:
15767 nr = (sample == GD_S_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15768 sample - GD_S_DIAMOND_FALLING_1);
15769 sound_effect = SND_BD_DIAMOND_FALLING_RANDOM_1 + nr;
15771 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15772 sound_effect = SND_BD_DIAMOND_FALLING;
15775 case GD_S_DIAMOND_IMPACT_RANDOM:
15776 case GD_S_DIAMOND_IMPACT_1:
15777 case GD_S_DIAMOND_IMPACT_2:
15778 case GD_S_DIAMOND_IMPACT_3:
15779 case GD_S_DIAMOND_IMPACT_4:
15780 case GD_S_DIAMOND_IMPACT_5:
15781 case GD_S_DIAMOND_IMPACT_6:
15782 case GD_S_DIAMOND_IMPACT_7:
15783 case GD_S_DIAMOND_IMPACT_8:
15784 nr = (sample == GD_S_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15785 sample - GD_S_DIAMOND_IMPACT_1);
15786 sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr;
15788 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15789 sound_effect = SND_BD_DIAMOND_IMPACT;
15792 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15793 case GD_S_FLYING_DIAMOND_FALLING_1:
15794 case GD_S_FLYING_DIAMOND_FALLING_2:
15795 case GD_S_FLYING_DIAMOND_FALLING_3:
15796 case GD_S_FLYING_DIAMOND_FALLING_4:
15797 case GD_S_FLYING_DIAMOND_FALLING_5:
15798 case GD_S_FLYING_DIAMOND_FALLING_6:
15799 case GD_S_FLYING_DIAMOND_FALLING_7:
15800 case GD_S_FLYING_DIAMOND_FALLING_8:
15801 nr = (sample == GD_S_FLYING_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15802 sample - GD_S_FLYING_DIAMOND_FALLING_1);
15803 sound_effect = SND_BD_FLYING_DIAMOND_FALLING_RANDOM_1 + nr;
15805 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15806 sound_effect = SND_BD_FLYING_DIAMOND_FALLING;
15809 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15810 case GD_S_FLYING_DIAMOND_IMPACT_1:
15811 case GD_S_FLYING_DIAMOND_IMPACT_2:
15812 case GD_S_FLYING_DIAMOND_IMPACT_3:
15813 case GD_S_FLYING_DIAMOND_IMPACT_4:
15814 case GD_S_FLYING_DIAMOND_IMPACT_5:
15815 case GD_S_FLYING_DIAMOND_IMPACT_6:
15816 case GD_S_FLYING_DIAMOND_IMPACT_7:
15817 case GD_S_FLYING_DIAMOND_IMPACT_8:
15818 nr = (sample == GD_S_FLYING_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15819 sample - GD_S_FLYING_DIAMOND_IMPACT_1);
15820 sound_effect = SND_BD_FLYING_DIAMOND_IMPACT_RANDOM_1 + nr;
15822 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15823 sound_effect = SND_BD_FLYING_DIAMOND_IMPACT;
15826 case GD_S_TIMEOUT_0:
15827 case GD_S_TIMEOUT_1:
15828 case GD_S_TIMEOUT_2:
15829 case GD_S_TIMEOUT_3:
15830 case GD_S_TIMEOUT_4:
15831 case GD_S_TIMEOUT_5:
15832 case GD_S_TIMEOUT_6:
15833 case GD_S_TIMEOUT_7:
15834 case GD_S_TIMEOUT_8:
15835 case GD_S_TIMEOUT_9:
15836 case GD_S_TIMEOUT_10:
15837 nr = sample - GD_S_TIMEOUT_0;
15838 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME_0 + nr;
15840 if (getSoundInfoEntryFilename(sound_effect) == NULL && sample != GD_S_TIMEOUT_0)
15841 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME;
15844 case GD_S_BONUS_LIFE:
15845 sound_effect = SND_GAME_HEALTH_BONUS;
15848 case GD_S_AMOEBA_MAGIC:
15849 sound_effect = SND_BD_AMOEBA_OTHER;
15852 case GD_S_FINISHED:
15853 sound_effect = SND_GAME_LEVELTIME_BONUS;
15857 sound_effect = SND_UNDEFINED;
15861 return sound_effect;
15864 void PlayLevelSound_BD(int xx, int yy, int element_bd, int sample)
15866 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15867 int sound_effect = getSoundEffect_BD(element, sample);
15868 int sound_action = getSoundAction_BD(sample);
15869 boolean is_loop_sound = IS_LOOP_SOUND(sound_effect);
15871 int x = xx - offset;
15872 int y = yy - offset;
15874 // some sound actions are always looping in native BD game engine
15875 if (sound_action == ACTION_DEFAULT)
15876 is_loop_sound = TRUE;
15878 // some sound actions are always non-looping in native BD game engine
15879 if (sound_action == ACTION_FALLING ||
15880 sound_action == ACTION_MOVING ||
15881 sound_action == ACTION_OTHER)
15882 is_loop_sound = FALSE;
15884 if (sound_effect != SND_UNDEFINED)
15885 PlayLevelSoundExt(x, y, sound_effect, is_loop_sound);
15888 void StopSound_BD(int element_bd, int sample)
15890 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15891 int sound_effect = getSoundEffect_BD(element, sample);
15893 if (sound_effect != SND_UNDEFINED)
15894 StopSound(sound_effect);
15897 boolean isSoundPlaying_BD(int element_bd, int sample)
15899 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15900 int sound_effect = getSoundEffect_BD(element, sample);
15902 if (sound_effect != SND_UNDEFINED)
15903 return isSoundPlaying(sound_effect);
15908 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15910 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15912 int x = xx - offset;
15913 int y = yy - offset;
15918 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15922 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15926 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15930 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15934 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15938 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15942 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15945 case SOUND_android_clone:
15946 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15949 case SOUND_android_move:
15950 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15954 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15958 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15962 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15965 case SOUND_eater_eat:
15966 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15970 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15973 case SOUND_collect:
15974 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15977 case SOUND_diamond:
15978 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15982 // !!! CHECK THIS !!!
15984 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15986 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15990 case SOUND_wonderfall:
15991 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15995 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15999 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
16003 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
16007 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
16011 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16015 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
16019 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16023 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16026 case SOUND_exit_open:
16027 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
16030 case SOUND_exit_leave:
16031 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16034 case SOUND_dynamite:
16035 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16039 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16043 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
16047 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16051 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
16055 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
16059 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
16063 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
16068 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
16070 int element = map_element_SP_to_RND(element_sp);
16071 int action = map_action_SP_to_RND(action_sp);
16072 int offset = (setup.sp_show_border_elements ? 0 : 1);
16073 int x = xx - offset;
16074 int y = yy - offset;
16076 PlayLevelSoundElementAction(x, y, element, action);
16079 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
16081 int element = map_element_MM_to_RND(element_mm);
16082 int action = map_action_MM_to_RND(action_mm);
16084 int x = xx - offset;
16085 int y = yy - offset;
16087 if (!IS_MM_ELEMENT(element))
16088 element = EL_MM_DEFAULT;
16090 PlayLevelSoundElementAction(x, y, element, action);
16093 void PlaySound_MM(int sound_mm)
16095 int sound = map_sound_MM_to_RND(sound_mm);
16097 if (sound == SND_UNDEFINED)
16103 void PlaySoundLoop_MM(int sound_mm)
16105 int sound = map_sound_MM_to_RND(sound_mm);
16107 if (sound == SND_UNDEFINED)
16110 PlaySoundLoop(sound);
16113 void StopSound_MM(int sound_mm)
16115 int sound = map_sound_MM_to_RND(sound_mm);
16117 if (sound == SND_UNDEFINED)
16123 void RaiseScore(int value)
16125 game.score += value;
16127 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
16129 DisplayGameControlValues();
16132 void RaiseScoreElement(int element)
16137 case EL_BD_DIAMOND:
16138 case EL_EMERALD_YELLOW:
16139 case EL_EMERALD_RED:
16140 case EL_EMERALD_PURPLE:
16141 case EL_SP_INFOTRON:
16142 RaiseScore(level.score[SC_EMERALD]);
16145 RaiseScore(level.score[SC_DIAMOND]);
16148 RaiseScore(level.score[SC_CRYSTAL]);
16151 RaiseScore(level.score[SC_PEARL]);
16154 case EL_BD_BUTTERFLY:
16155 case EL_SP_ELECTRON:
16156 RaiseScore(level.score[SC_BUG]);
16159 case EL_BD_FIREFLY:
16160 case EL_SP_SNIKSNAK:
16161 RaiseScore(level.score[SC_SPACESHIP]);
16164 case EL_DARK_YAMYAM:
16165 RaiseScore(level.score[SC_YAMYAM]);
16168 RaiseScore(level.score[SC_ROBOT]);
16171 RaiseScore(level.score[SC_PACMAN]);
16174 RaiseScore(level.score[SC_NUT]);
16177 case EL_EM_DYNAMITE:
16178 case EL_SP_DISK_RED:
16179 case EL_DYNABOMB_INCREASE_NUMBER:
16180 case EL_DYNABOMB_INCREASE_SIZE:
16181 case EL_DYNABOMB_INCREASE_POWER:
16182 RaiseScore(level.score[SC_DYNAMITE]);
16184 case EL_SHIELD_NORMAL:
16185 case EL_SHIELD_DEADLY:
16186 RaiseScore(level.score[SC_SHIELD]);
16188 case EL_EXTRA_TIME:
16189 RaiseScore(level.extra_time_score);
16203 case EL_DC_KEY_WHITE:
16204 RaiseScore(level.score[SC_KEY]);
16207 RaiseScore(element_info[element].collect_score);
16212 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
16214 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
16218 // prevent short reactivation of overlay buttons while closing door
16219 SetOverlayActive(FALSE);
16220 UnmapGameButtons();
16222 // door may still be open due to skipped or envelope style request
16223 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
16226 if (network.enabled)
16228 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
16232 // when using BD game engine, cover screen before fading out
16233 if (!quick_quit && level.game_engine_type == GAME_ENGINE_TYPE_BD)
16234 game_bd.cover_screen = TRUE;
16237 FadeSkipNextFadeIn();
16239 SetGameStatus(GAME_MODE_MAIN);
16244 else // continue playing the game
16246 if (tape.playing && tape.deactivate_display)
16247 TapeDeactivateDisplayOff(TRUE);
16249 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
16251 if (tape.playing && tape.deactivate_display)
16252 TapeDeactivateDisplayOn();
16256 void RequestQuitGame(boolean escape_key_pressed)
16258 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
16259 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
16260 level_editor_test_game);
16261 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
16262 quick_quit || score_info_tape_play);
16264 RequestQuitGameExt(skip_request, quick_quit,
16265 "Do you really want to quit the game?");
16268 static char *getRestartGameMessage(void)
16270 boolean play_again = hasStartedNetworkGame();
16271 static char message[MAX_OUTPUT_LINESIZE];
16272 char *game_over_text = "Game over!";
16273 char *play_again_text = " Play it again?";
16275 if (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
16276 game_mm.game_over_message != NULL)
16277 game_over_text = game_mm.game_over_message;
16279 snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text,
16280 (play_again ? play_again_text : ""));
16285 static void RequestRestartGame(void)
16287 char *message = getRestartGameMessage();
16288 boolean has_started_game = hasStartedNetworkGame();
16289 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
16290 int door_state = DOOR_CLOSE_1;
16292 boolean restart_wanted = (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game);
16294 // if no restart wanted, continue with next level for BD style intermission levels
16295 if (!restart_wanted && !level_editor_test_game && level.bd_intermission)
16297 boolean success = AdvanceToNextLevel();
16299 restart_wanted = (success && setup.auto_play_next_level);
16302 if (restart_wanted)
16304 CloseDoor(door_state);
16306 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16310 // if game was invoked from level editor, also close tape recorder door
16311 if (level_editor_test_game)
16312 door_state = DOOR_CLOSE_ALL;
16314 CloseDoor(door_state);
16316 SetGameStatus(GAME_MODE_MAIN);
16322 boolean CheckRestartGame(void)
16324 static int game_over_delay = 0;
16325 int game_over_delay_value = 50;
16326 boolean game_over = checkGameFailed();
16330 game_over_delay = game_over_delay_value;
16335 if (game_over_delay > 0)
16337 if (game_over_delay == game_over_delay_value / 2)
16338 PlaySound(SND_GAME_LOSING);
16345 // do not ask to play again if request dialog is already active
16346 if (game.request_active)
16349 // do not ask to play again if request dialog already handled
16350 if (game.RestartGameRequested)
16353 // do not ask to play again if game was never actually played
16354 if (!game.GamePlayed)
16357 // do not ask to play again if this was disabled in setup menu
16358 if (!setup.ask_on_game_over)
16361 game.RestartGameRequested = TRUE;
16363 RequestRestartGame();
16368 boolean checkGameRunning(void)
16370 if (game_status != GAME_MODE_PLAYING)
16373 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGameRunning_BD())
16379 boolean checkGamePlaying(void)
16381 if (game_status != GAME_MODE_PLAYING)
16384 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGamePlaying_BD())
16390 boolean checkGameSolved(void)
16392 // set for all game engines if level was solved
16393 return game.LevelSolved_GameEnd;
16396 boolean checkGameFailed(void)
16398 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
16399 return (game_bd.game_over && !game_bd.level_solved);
16400 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16401 return (game_em.game_over && !game_em.level_solved);
16402 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16403 return (game_sp.game_over && !game_sp.level_solved);
16404 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16405 return (game_mm.game_over && !game_mm.level_solved);
16406 else // GAME_ENGINE_TYPE_RND
16407 return (game.GameOver && !game.LevelSolved);
16410 boolean checkGameEnded(void)
16412 return (checkGameSolved() || checkGameFailed());
16416 // ----------------------------------------------------------------------------
16417 // random generator functions
16418 // ----------------------------------------------------------------------------
16420 unsigned int InitEngineRandom_RND(int seed)
16422 game.num_random_calls = 0;
16424 return InitEngineRandom(seed);
16427 unsigned int RND(int max)
16431 game.num_random_calls++;
16433 return GetEngineRandom(max);
16440 // ----------------------------------------------------------------------------
16441 // game engine snapshot handling functions
16442 // ----------------------------------------------------------------------------
16444 struct EngineSnapshotInfo
16446 // runtime values for custom element collect score
16447 int collect_score[NUM_CUSTOM_ELEMENTS];
16449 // runtime values for group element choice position
16450 int choice_pos[NUM_GROUP_ELEMENTS];
16452 // runtime values for belt position animations
16453 int belt_graphic[4][NUM_BELT_PARTS];
16454 int belt_anim_mode[4][NUM_BELT_PARTS];
16457 static struct EngineSnapshotInfo engine_snapshot_rnd;
16458 static char *snapshot_level_identifier = NULL;
16459 static int snapshot_level_nr = -1;
16461 static void SaveEngineSnapshotValues_RND(void)
16463 static int belt_base_active_element[4] =
16465 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
16466 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
16467 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
16468 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
16472 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16474 int element = EL_CUSTOM_START + i;
16476 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
16479 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16481 int element = EL_GROUP_START + i;
16483 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
16486 for (i = 0; i < 4; i++)
16488 for (j = 0; j < NUM_BELT_PARTS; j++)
16490 int element = belt_base_active_element[i] + j;
16491 int graphic = el2img(element);
16492 int anim_mode = graphic_info[graphic].anim_mode;
16494 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
16495 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
16500 static void LoadEngineSnapshotValues_RND(void)
16502 unsigned int num_random_calls = game.num_random_calls;
16505 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16507 int element = EL_CUSTOM_START + i;
16509 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
16512 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16514 int element = EL_GROUP_START + i;
16516 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
16519 for (i = 0; i < 4; i++)
16521 for (j = 0; j < NUM_BELT_PARTS; j++)
16523 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
16524 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
16526 graphic_info[graphic].anim_mode = anim_mode;
16530 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16532 InitRND(tape.random_seed);
16533 for (i = 0; i < num_random_calls; i++)
16537 if (game.num_random_calls != num_random_calls)
16539 Error("number of random calls out of sync");
16540 Error("number of random calls should be %d", num_random_calls);
16541 Error("number of random calls is %d", game.num_random_calls);
16543 Fail("this should not happen -- please debug");
16547 void FreeEngineSnapshotSingle(void)
16549 FreeSnapshotSingle();
16551 setString(&snapshot_level_identifier, NULL);
16552 snapshot_level_nr = -1;
16555 void FreeEngineSnapshotList(void)
16557 FreeSnapshotList();
16560 static ListNode *SaveEngineSnapshotBuffers(void)
16562 ListNode *buffers = NULL;
16564 // copy some special values to a structure better suited for the snapshot
16566 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16567 SaveEngineSnapshotValues_RND();
16568 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16569 SaveEngineSnapshotValues_EM();
16570 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16571 SaveEngineSnapshotValues_SP(&buffers);
16572 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16573 SaveEngineSnapshotValues_MM();
16575 // save values stored in special snapshot structure
16577 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16578 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
16579 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16580 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
16581 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16582 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
16583 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16584 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
16586 // save further RND engine values
16588 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
16589 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
16590 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
16592 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
16593 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
16594 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
16595 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16596 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTimeFrames));
16597 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16599 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16600 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16601 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16603 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16605 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16606 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16608 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16609 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16610 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16611 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16612 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16613 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16614 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16615 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16616 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16617 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16618 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16619 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16620 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16621 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16622 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16623 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16624 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16625 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16627 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16628 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16630 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16631 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16632 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16634 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16635 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16637 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16638 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16639 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16640 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16641 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16642 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16644 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16645 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16648 ListNode *node = engine_snapshot_list_rnd;
16651 while (node != NULL)
16653 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16658 Debug("game:playing:SaveEngineSnapshotBuffers",
16659 "size of engine snapshot: %d bytes", num_bytes);
16665 void SaveEngineSnapshotSingle(void)
16667 ListNode *buffers = SaveEngineSnapshotBuffers();
16669 // finally save all snapshot buffers to single snapshot
16670 SaveSnapshotSingle(buffers);
16672 // save level identification information
16673 setString(&snapshot_level_identifier, leveldir_current->identifier);
16674 snapshot_level_nr = level_nr;
16677 boolean CheckSaveEngineSnapshotToList(void)
16679 boolean save_snapshot =
16680 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16681 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16682 game.snapshot.changed_action) ||
16683 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16684 game.snapshot.collected_item));
16686 game.snapshot.changed_action = FALSE;
16687 game.snapshot.collected_item = FALSE;
16688 game.snapshot.save_snapshot = save_snapshot;
16690 return save_snapshot;
16693 void SaveEngineSnapshotToList(void)
16695 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16699 ListNode *buffers = SaveEngineSnapshotBuffers();
16701 // finally save all snapshot buffers to snapshot list
16702 SaveSnapshotToList(buffers);
16705 void SaveEngineSnapshotToListInitial(void)
16707 FreeEngineSnapshotList();
16709 SaveEngineSnapshotToList();
16712 static void LoadEngineSnapshotValues(void)
16714 // restore special values from snapshot structure
16716 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16717 LoadEngineSnapshotValues_RND();
16718 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16719 LoadEngineSnapshotValues_EM();
16720 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16721 LoadEngineSnapshotValues_SP();
16722 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16723 LoadEngineSnapshotValues_MM();
16726 void LoadEngineSnapshotSingle(void)
16728 LoadSnapshotSingle();
16730 LoadEngineSnapshotValues();
16733 static void LoadEngineSnapshot_Undo(int steps)
16735 LoadSnapshotFromList_Older(steps);
16737 LoadEngineSnapshotValues();
16740 static void LoadEngineSnapshot_Redo(int steps)
16742 LoadSnapshotFromList_Newer(steps);
16744 LoadEngineSnapshotValues();
16747 boolean CheckEngineSnapshotSingle(void)
16749 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16750 snapshot_level_nr == level_nr);
16753 boolean CheckEngineSnapshotList(void)
16755 return CheckSnapshotList();
16759 // ---------- new game button stuff -------------------------------------------
16766 boolean *setup_value;
16767 boolean allowed_on_tape;
16768 boolean is_touch_button;
16770 } gamebutton_info[NUM_GAME_BUTTONS] =
16773 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16774 GAME_CTRL_ID_STOP, NULL,
16775 TRUE, FALSE, "stop game"
16778 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16779 GAME_CTRL_ID_PAUSE, NULL,
16780 TRUE, FALSE, "pause game"
16783 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16784 GAME_CTRL_ID_PLAY, NULL,
16785 TRUE, FALSE, "play game"
16788 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16789 GAME_CTRL_ID_UNDO, NULL,
16790 TRUE, FALSE, "undo step"
16793 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16794 GAME_CTRL_ID_REDO, NULL,
16795 TRUE, FALSE, "redo step"
16798 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16799 GAME_CTRL_ID_SAVE, NULL,
16800 TRUE, FALSE, "save game"
16803 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16804 GAME_CTRL_ID_PAUSE2, NULL,
16805 TRUE, FALSE, "pause game"
16808 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16809 GAME_CTRL_ID_LOAD, NULL,
16810 TRUE, FALSE, "load game"
16813 IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart,
16814 GAME_CTRL_ID_RESTART, NULL,
16815 TRUE, FALSE, "restart game"
16818 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16819 GAME_CTRL_ID_PANEL_STOP, NULL,
16820 FALSE, FALSE, "stop game"
16823 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16824 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16825 FALSE, FALSE, "pause game"
16828 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16829 GAME_CTRL_ID_PANEL_PLAY, NULL,
16830 FALSE, FALSE, "play game"
16833 IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart,
16834 GAME_CTRL_ID_PANEL_RESTART, NULL,
16835 FALSE, FALSE, "restart game"
16838 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16839 GAME_CTRL_ID_TOUCH_STOP, NULL,
16840 FALSE, TRUE, "stop game"
16843 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16844 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16845 FALSE, TRUE, "pause game"
16848 IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart,
16849 GAME_CTRL_ID_TOUCH_RESTART, NULL,
16850 FALSE, TRUE, "restart game"
16853 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16854 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16855 TRUE, FALSE, "background music on/off"
16858 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16859 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16860 TRUE, FALSE, "sound loops on/off"
16863 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16864 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16865 TRUE, FALSE, "normal sounds on/off"
16868 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16869 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16870 FALSE, FALSE, "background music on/off"
16873 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16874 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16875 FALSE, FALSE, "sound loops on/off"
16878 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16879 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16880 FALSE, FALSE, "normal sounds on/off"
16884 void CreateGameButtons(void)
16888 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16890 int graphic = gamebutton_info[i].graphic;
16891 struct GraphicInfo *gfx = &graphic_info[graphic];
16892 struct XY *pos = gamebutton_info[i].pos;
16893 struct GadgetInfo *gi;
16896 unsigned int event_mask;
16897 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16898 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16899 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16900 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16901 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16902 int gd_x = gfx->src_x;
16903 int gd_y = gfx->src_y;
16904 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16905 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16906 int gd_xa = gfx->src_x + gfx->active_xoffset;
16907 int gd_ya = gfx->src_y + gfx->active_yoffset;
16908 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16909 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16910 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16911 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16914 // do not use touch buttons if overlay touch buttons are disabled
16915 if (is_touch_button && !setup.touch.overlay_buttons)
16918 if (gfx->bitmap == NULL)
16920 game_gadget[id] = NULL;
16925 if (id == GAME_CTRL_ID_STOP ||
16926 id == GAME_CTRL_ID_PANEL_STOP ||
16927 id == GAME_CTRL_ID_TOUCH_STOP ||
16928 id == GAME_CTRL_ID_PLAY ||
16929 id == GAME_CTRL_ID_PANEL_PLAY ||
16930 id == GAME_CTRL_ID_SAVE ||
16931 id == GAME_CTRL_ID_LOAD ||
16932 id == GAME_CTRL_ID_RESTART ||
16933 id == GAME_CTRL_ID_PANEL_RESTART ||
16934 id == GAME_CTRL_ID_TOUCH_RESTART)
16936 button_type = GD_TYPE_NORMAL_BUTTON;
16938 event_mask = GD_EVENT_RELEASED;
16940 else if (id == GAME_CTRL_ID_UNDO ||
16941 id == GAME_CTRL_ID_REDO)
16943 button_type = GD_TYPE_NORMAL_BUTTON;
16945 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16949 button_type = GD_TYPE_CHECK_BUTTON;
16950 checked = (gamebutton_info[i].setup_value != NULL ?
16951 *gamebutton_info[i].setup_value : FALSE);
16952 event_mask = GD_EVENT_PRESSED;
16955 gi = CreateGadget(GDI_CUSTOM_ID, id,
16956 GDI_IMAGE_ID, graphic,
16957 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16960 GDI_WIDTH, gfx->width,
16961 GDI_HEIGHT, gfx->height,
16962 GDI_TYPE, button_type,
16963 GDI_STATE, GD_BUTTON_UNPRESSED,
16964 GDI_CHECKED, checked,
16965 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16966 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16967 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16968 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16969 GDI_DIRECT_DRAW, FALSE,
16970 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16971 GDI_EVENT_MASK, event_mask,
16972 GDI_CALLBACK_ACTION, HandleGameButtons,
16976 Fail("cannot create gadget");
16978 game_gadget[id] = gi;
16982 void FreeGameButtons(void)
16986 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16987 FreeGadget(game_gadget[i]);
16990 static void UnmapGameButtonsAtSamePosition(int id)
16994 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16996 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16997 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16998 UnmapGadget(game_gadget[i]);
17001 static void UnmapGameButtonsAtSamePosition_All(void)
17003 if (setup.show_load_save_buttons)
17005 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
17006 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
17007 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
17009 else if (setup.show_undo_redo_buttons)
17011 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
17012 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
17013 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
17017 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
17018 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
17019 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
17021 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
17022 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
17023 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
17027 void MapLoadSaveButtons(void)
17029 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
17030 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
17032 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
17033 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
17036 void MapUndoRedoButtons(void)
17038 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
17039 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
17041 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
17042 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
17045 void ModifyPauseButtons(void)
17049 GAME_CTRL_ID_PAUSE,
17050 GAME_CTRL_ID_PAUSE2,
17051 GAME_CTRL_ID_PANEL_PAUSE,
17052 GAME_CTRL_ID_TOUCH_PAUSE,
17057 // do not redraw pause button on closed door (may happen when restarting game)
17058 if (!(GetDoorState() & DOOR_OPEN_1))
17061 for (i = 0; ids[i] > -1; i++)
17062 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
17065 static void MapGameButtonsExt(boolean on_tape)
17069 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17071 if ((i == GAME_CTRL_ID_UNDO ||
17072 i == GAME_CTRL_ID_REDO) &&
17073 game_status != GAME_MODE_PLAYING)
17076 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17077 MapGadget(game_gadget[i]);
17080 UnmapGameButtonsAtSamePosition_All();
17082 RedrawGameButtons();
17085 static void UnmapGameButtonsExt(boolean on_tape)
17089 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17090 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17091 UnmapGadget(game_gadget[i]);
17094 static void RedrawGameButtonsExt(boolean on_tape)
17098 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17099 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17100 RedrawGadget(game_gadget[i]);
17103 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
17108 gi->checked = state;
17111 static void RedrawSoundButtonGadget(int id)
17113 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
17114 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
17115 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
17116 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
17117 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
17118 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
17121 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
17122 RedrawGadget(game_gadget[id2]);
17125 void MapGameButtons(void)
17127 MapGameButtonsExt(FALSE);
17130 void UnmapGameButtons(void)
17132 UnmapGameButtonsExt(FALSE);
17135 void RedrawGameButtons(void)
17137 RedrawGameButtonsExt(FALSE);
17140 void MapGameButtonsOnTape(void)
17142 MapGameButtonsExt(TRUE);
17145 void UnmapGameButtonsOnTape(void)
17147 UnmapGameButtonsExt(TRUE);
17150 void RedrawGameButtonsOnTape(void)
17152 RedrawGameButtonsExt(TRUE);
17155 static void GameUndoRedoExt(void)
17157 ClearPlayerAction();
17159 tape.pausing = TRUE;
17162 UpdateAndDisplayGameControlValues();
17164 DrawCompleteVideoDisplay();
17165 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
17166 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
17167 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
17169 ModifyPauseButtons();
17174 static void GameUndo(int steps)
17176 if (!CheckEngineSnapshotList())
17179 int tape_property_bits = tape.property_bits;
17181 LoadEngineSnapshot_Undo(steps);
17183 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17188 static void GameRedo(int steps)
17190 if (!CheckEngineSnapshotList())
17193 int tape_property_bits = tape.property_bits;
17195 LoadEngineSnapshot_Redo(steps);
17197 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17202 static void HandleGameButtonsExt(int id, int button)
17204 static boolean game_undo_executed = FALSE;
17205 int steps = BUTTON_STEPSIZE(button);
17206 boolean handle_game_buttons =
17207 (game_status == GAME_MODE_PLAYING ||
17208 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
17210 if (!handle_game_buttons)
17215 case GAME_CTRL_ID_STOP:
17216 case GAME_CTRL_ID_PANEL_STOP:
17217 case GAME_CTRL_ID_TOUCH_STOP:
17222 case GAME_CTRL_ID_PAUSE:
17223 case GAME_CTRL_ID_PAUSE2:
17224 case GAME_CTRL_ID_PANEL_PAUSE:
17225 case GAME_CTRL_ID_TOUCH_PAUSE:
17226 if (network.enabled && game_status == GAME_MODE_PLAYING)
17229 SendToServer_ContinuePlaying();
17231 SendToServer_PausePlaying();
17234 TapeTogglePause(TAPE_TOGGLE_MANUAL);
17236 game_undo_executed = FALSE;
17240 case GAME_CTRL_ID_PLAY:
17241 case GAME_CTRL_ID_PANEL_PLAY:
17242 if (game_status == GAME_MODE_MAIN)
17244 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
17246 else if (tape.pausing)
17248 if (network.enabled)
17249 SendToServer_ContinuePlaying();
17251 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
17255 case GAME_CTRL_ID_UNDO:
17256 // Important: When using "save snapshot when collecting an item" mode,
17257 // load last (current) snapshot for first "undo" after pressing "pause"
17258 // (else the last-but-one snapshot would be loaded, because the snapshot
17259 // pointer already points to the last snapshot when pressing "pause",
17260 // which is fine for "every step/move" mode, but not for "every collect")
17261 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
17262 !game_undo_executed)
17265 game_undo_executed = TRUE;
17270 case GAME_CTRL_ID_REDO:
17274 case GAME_CTRL_ID_SAVE:
17278 case GAME_CTRL_ID_LOAD:
17282 case GAME_CTRL_ID_RESTART:
17283 case GAME_CTRL_ID_PANEL_RESTART:
17284 case GAME_CTRL_ID_TOUCH_RESTART:
17289 case SOUND_CTRL_ID_MUSIC:
17290 case SOUND_CTRL_ID_PANEL_MUSIC:
17291 if (setup.sound_music)
17293 setup.sound_music = FALSE;
17297 else if (audio.music_available)
17299 setup.sound = setup.sound_music = TRUE;
17301 SetAudioMode(setup.sound);
17303 if (game_status == GAME_MODE_PLAYING)
17307 RedrawSoundButtonGadget(id);
17311 case SOUND_CTRL_ID_LOOPS:
17312 case SOUND_CTRL_ID_PANEL_LOOPS:
17313 if (setup.sound_loops)
17314 setup.sound_loops = FALSE;
17315 else if (audio.loops_available)
17317 setup.sound = setup.sound_loops = TRUE;
17319 SetAudioMode(setup.sound);
17322 RedrawSoundButtonGadget(id);
17326 case SOUND_CTRL_ID_SIMPLE:
17327 case SOUND_CTRL_ID_PANEL_SIMPLE:
17328 if (setup.sound_simple)
17329 setup.sound_simple = FALSE;
17330 else if (audio.sound_available)
17332 setup.sound = setup.sound_simple = TRUE;
17334 SetAudioMode(setup.sound);
17337 RedrawSoundButtonGadget(id);
17346 static void HandleGameButtons(struct GadgetInfo *gi)
17348 HandleGameButtonsExt(gi->custom_id, gi->event.button);
17351 void HandleSoundButtonKeys(Key key)
17353 if (key == setup.shortcut.sound_simple)
17354 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
17355 else if (key == setup.shortcut.sound_loops)
17356 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
17357 else if (key == setup.shortcut.sound_music)
17358 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);