1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_INIT_PLAYER 1
27 #define DEBUG_PLAYER_ACTIONS 0
30 #define USE_NEW_AMOEBA_CODE FALSE
33 #define USE_QUICKSAND_BD_ROCK_BUGFIX 0
34 #define USE_QUICKSAND_IMPACT_BUGFIX 0
35 #define USE_DELAYED_GFX_REDRAW 0
36 #define USE_NEW_PLAYER_ASSIGNMENTS 1
38 #if USE_DELAYED_GFX_REDRAW
39 #define TEST_DrawLevelField(x, y) \
40 GfxRedraw[x][y] |= GFX_REDRAW_TILE
41 #define TEST_DrawLevelFieldCrumbled(x, y) \
42 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED
43 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
44 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS
45 #define TEST_DrawTwinkleOnField(x, y) \
46 GfxRedraw[x][y] |= GFX_REDRAW_TILE_TWINKLED
48 #define TEST_DrawLevelField(x, y) \
50 #define TEST_DrawLevelFieldCrumbled(x, y) \
51 DrawLevelFieldCrumbled(x, y)
52 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
53 DrawLevelFieldCrumbledNeighbours(x, y)
54 #define TEST_DrawTwinkleOnField(x, y) \
55 DrawTwinkleOnField(x, y)
65 #define MP_NO_ACTION 0
68 #define MP_DONT_RUN_INTO (MP_MOVING | MP_ACTION)
72 #define SCROLL_GO_ON 1
74 // for Bang()/Explode()
75 #define EX_PHASE_START 0
76 #define EX_TYPE_NONE 0
77 #define EX_TYPE_NORMAL (1 << 0)
78 #define EX_TYPE_CENTER (1 << 1)
79 #define EX_TYPE_BORDER (1 << 2)
80 #define EX_TYPE_CROSS (1 << 3)
81 #define EX_TYPE_DYNA (1 << 4)
82 #define EX_TYPE_SINGLE_TILE (EX_TYPE_CENTER | EX_TYPE_BORDER)
84 #define PANEL_OFF() (game.panel.active == FALSE)
85 #define PANEL_DEACTIVATED(p) ((p)->x < 0 || (p)->y < 0 || PANEL_OFF())
86 #define PANEL_XPOS(p) (DX + ALIGNED_TEXT_XPOS(p))
87 #define PANEL_YPOS(p) (DY + ALIGNED_TEXT_YPOS(p))
89 // game panel display and control definitions
90 #define GAME_PANEL_LEVEL_NUMBER 0
91 #define GAME_PANEL_GEMS 1
92 #define GAME_PANEL_GEMS_NEEDED 2
93 #define GAME_PANEL_GEMS_COLLECTED 3
94 #define GAME_PANEL_GEMS_SCORE 4
95 #define GAME_PANEL_INVENTORY_COUNT 5
96 #define GAME_PANEL_INVENTORY_FIRST_1 6
97 #define GAME_PANEL_INVENTORY_FIRST_2 7
98 #define GAME_PANEL_INVENTORY_FIRST_3 8
99 #define GAME_PANEL_INVENTORY_FIRST_4 9
100 #define GAME_PANEL_INVENTORY_FIRST_5 10
101 #define GAME_PANEL_INVENTORY_FIRST_6 11
102 #define GAME_PANEL_INVENTORY_FIRST_7 12
103 #define GAME_PANEL_INVENTORY_FIRST_8 13
104 #define GAME_PANEL_INVENTORY_LAST_1 14
105 #define GAME_PANEL_INVENTORY_LAST_2 15
106 #define GAME_PANEL_INVENTORY_LAST_3 16
107 #define GAME_PANEL_INVENTORY_LAST_4 17
108 #define GAME_PANEL_INVENTORY_LAST_5 18
109 #define GAME_PANEL_INVENTORY_LAST_6 19
110 #define GAME_PANEL_INVENTORY_LAST_7 20
111 #define GAME_PANEL_INVENTORY_LAST_8 21
112 #define GAME_PANEL_KEY_1 22
113 #define GAME_PANEL_KEY_2 23
114 #define GAME_PANEL_KEY_3 24
115 #define GAME_PANEL_KEY_4 25
116 #define GAME_PANEL_KEY_5 26
117 #define GAME_PANEL_KEY_6 27
118 #define GAME_PANEL_KEY_7 28
119 #define GAME_PANEL_KEY_8 29
120 #define GAME_PANEL_KEY_WHITE 30
121 #define GAME_PANEL_KEY_WHITE_COUNT 31
122 #define GAME_PANEL_SCORE 32
123 #define GAME_PANEL_HIGHSCORE 33
124 #define GAME_PANEL_TIME 34
125 #define GAME_PANEL_TIME_HH 35
126 #define GAME_PANEL_TIME_MM 36
127 #define GAME_PANEL_TIME_SS 37
128 #define GAME_PANEL_TIME_ANIM 38
129 #define GAME_PANEL_HEALTH 39
130 #define GAME_PANEL_HEALTH_ANIM 40
131 #define GAME_PANEL_FRAME 41
132 #define GAME_PANEL_SHIELD_NORMAL 42
133 #define GAME_PANEL_SHIELD_NORMAL_TIME 43
134 #define GAME_PANEL_SHIELD_DEADLY 44
135 #define GAME_PANEL_SHIELD_DEADLY_TIME 45
136 #define GAME_PANEL_EXIT 46
137 #define GAME_PANEL_EMC_MAGIC_BALL 47
138 #define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 48
139 #define GAME_PANEL_LIGHT_SWITCH 49
140 #define GAME_PANEL_LIGHT_SWITCH_TIME 50
141 #define GAME_PANEL_TIMEGATE_SWITCH 51
142 #define GAME_PANEL_TIMEGATE_SWITCH_TIME 52
143 #define GAME_PANEL_SWITCHGATE_SWITCH 53
144 #define GAME_PANEL_EMC_LENSES 54
145 #define GAME_PANEL_EMC_LENSES_TIME 55
146 #define GAME_PANEL_EMC_MAGNIFIER 56
147 #define GAME_PANEL_EMC_MAGNIFIER_TIME 57
148 #define GAME_PANEL_BALLOON_SWITCH 58
149 #define GAME_PANEL_DYNABOMB_NUMBER 59
150 #define GAME_PANEL_DYNABOMB_SIZE 60
151 #define GAME_PANEL_DYNABOMB_POWER 61
152 #define GAME_PANEL_PENGUINS 62
153 #define GAME_PANEL_SOKOBAN_OBJECTS 63
154 #define GAME_PANEL_SOKOBAN_FIELDS 64
155 #define GAME_PANEL_ROBOT_WHEEL 65
156 #define GAME_PANEL_CONVEYOR_BELT_1 66
157 #define GAME_PANEL_CONVEYOR_BELT_2 67
158 #define GAME_PANEL_CONVEYOR_BELT_3 68
159 #define GAME_PANEL_CONVEYOR_BELT_4 69
160 #define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 70
161 #define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 71
162 #define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 72
163 #define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 73
164 #define GAME_PANEL_MAGIC_WALL 74
165 #define GAME_PANEL_MAGIC_WALL_TIME 75
166 #define GAME_PANEL_GRAVITY_STATE 76
167 #define GAME_PANEL_GRAPHIC_1 77
168 #define GAME_PANEL_GRAPHIC_2 78
169 #define GAME_PANEL_GRAPHIC_3 79
170 #define GAME_PANEL_GRAPHIC_4 80
171 #define GAME_PANEL_GRAPHIC_5 81
172 #define GAME_PANEL_GRAPHIC_6 82
173 #define GAME_PANEL_GRAPHIC_7 83
174 #define GAME_PANEL_GRAPHIC_8 84
175 #define GAME_PANEL_ELEMENT_1 85
176 #define GAME_PANEL_ELEMENT_2 86
177 #define GAME_PANEL_ELEMENT_3 87
178 #define GAME_PANEL_ELEMENT_4 88
179 #define GAME_PANEL_ELEMENT_5 89
180 #define GAME_PANEL_ELEMENT_6 90
181 #define GAME_PANEL_ELEMENT_7 91
182 #define GAME_PANEL_ELEMENT_8 92
183 #define GAME_PANEL_ELEMENT_COUNT_1 93
184 #define GAME_PANEL_ELEMENT_COUNT_2 94
185 #define GAME_PANEL_ELEMENT_COUNT_3 95
186 #define GAME_PANEL_ELEMENT_COUNT_4 96
187 #define GAME_PANEL_ELEMENT_COUNT_5 97
188 #define GAME_PANEL_ELEMENT_COUNT_6 98
189 #define GAME_PANEL_ELEMENT_COUNT_7 99
190 #define GAME_PANEL_ELEMENT_COUNT_8 100
191 #define GAME_PANEL_CE_SCORE_1 101
192 #define GAME_PANEL_CE_SCORE_2 102
193 #define GAME_PANEL_CE_SCORE_3 103
194 #define GAME_PANEL_CE_SCORE_4 104
195 #define GAME_PANEL_CE_SCORE_5 105
196 #define GAME_PANEL_CE_SCORE_6 106
197 #define GAME_PANEL_CE_SCORE_7 107
198 #define GAME_PANEL_CE_SCORE_8 108
199 #define GAME_PANEL_CE_SCORE_1_ELEMENT 109
200 #define GAME_PANEL_CE_SCORE_2_ELEMENT 110
201 #define GAME_PANEL_CE_SCORE_3_ELEMENT 111
202 #define GAME_PANEL_CE_SCORE_4_ELEMENT 112
203 #define GAME_PANEL_CE_SCORE_5_ELEMENT 113
204 #define GAME_PANEL_CE_SCORE_6_ELEMENT 114
205 #define GAME_PANEL_CE_SCORE_7_ELEMENT 115
206 #define GAME_PANEL_CE_SCORE_8_ELEMENT 116
207 #define GAME_PANEL_PLAYER_NAME 117
208 #define GAME_PANEL_LEVEL_NAME 118
209 #define GAME_PANEL_LEVEL_AUTHOR 119
211 #define NUM_GAME_PANEL_CONTROLS 120
213 struct GamePanelOrderInfo
219 static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
221 struct GamePanelControlInfo
225 struct TextPosInfo *pos;
228 int graphic, graphic_active;
230 int value, last_value;
231 int frame, last_frame;
236 static struct GamePanelControlInfo game_panel_controls[] =
239 GAME_PANEL_LEVEL_NUMBER,
240 &game.panel.level_number,
249 GAME_PANEL_GEMS_NEEDED,
250 &game.panel.gems_needed,
254 GAME_PANEL_GEMS_COLLECTED,
255 &game.panel.gems_collected,
259 GAME_PANEL_GEMS_SCORE,
260 &game.panel.gems_score,
264 GAME_PANEL_INVENTORY_COUNT,
265 &game.panel.inventory_count,
269 GAME_PANEL_INVENTORY_FIRST_1,
270 &game.panel.inventory_first[0],
274 GAME_PANEL_INVENTORY_FIRST_2,
275 &game.panel.inventory_first[1],
279 GAME_PANEL_INVENTORY_FIRST_3,
280 &game.panel.inventory_first[2],
284 GAME_PANEL_INVENTORY_FIRST_4,
285 &game.panel.inventory_first[3],
289 GAME_PANEL_INVENTORY_FIRST_5,
290 &game.panel.inventory_first[4],
294 GAME_PANEL_INVENTORY_FIRST_6,
295 &game.panel.inventory_first[5],
299 GAME_PANEL_INVENTORY_FIRST_7,
300 &game.panel.inventory_first[6],
304 GAME_PANEL_INVENTORY_FIRST_8,
305 &game.panel.inventory_first[7],
309 GAME_PANEL_INVENTORY_LAST_1,
310 &game.panel.inventory_last[0],
314 GAME_PANEL_INVENTORY_LAST_2,
315 &game.panel.inventory_last[1],
319 GAME_PANEL_INVENTORY_LAST_3,
320 &game.panel.inventory_last[2],
324 GAME_PANEL_INVENTORY_LAST_4,
325 &game.panel.inventory_last[3],
329 GAME_PANEL_INVENTORY_LAST_5,
330 &game.panel.inventory_last[4],
334 GAME_PANEL_INVENTORY_LAST_6,
335 &game.panel.inventory_last[5],
339 GAME_PANEL_INVENTORY_LAST_7,
340 &game.panel.inventory_last[6],
344 GAME_PANEL_INVENTORY_LAST_8,
345 &game.panel.inventory_last[7],
389 GAME_PANEL_KEY_WHITE,
390 &game.panel.key_white,
394 GAME_PANEL_KEY_WHITE_COUNT,
395 &game.panel.key_white_count,
404 GAME_PANEL_HIGHSCORE,
405 &game.panel.highscore,
429 GAME_PANEL_TIME_ANIM,
430 &game.panel.time_anim,
433 IMG_GFX_GAME_PANEL_TIME_ANIM,
434 IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
442 GAME_PANEL_HEALTH_ANIM,
443 &game.panel.health_anim,
446 IMG_GFX_GAME_PANEL_HEALTH_ANIM,
447 IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
455 GAME_PANEL_SHIELD_NORMAL,
456 &game.panel.shield_normal,
460 GAME_PANEL_SHIELD_NORMAL_TIME,
461 &game.panel.shield_normal_time,
465 GAME_PANEL_SHIELD_DEADLY,
466 &game.panel.shield_deadly,
470 GAME_PANEL_SHIELD_DEADLY_TIME,
471 &game.panel.shield_deadly_time,
480 GAME_PANEL_EMC_MAGIC_BALL,
481 &game.panel.emc_magic_ball,
485 GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
486 &game.panel.emc_magic_ball_switch,
490 GAME_PANEL_LIGHT_SWITCH,
491 &game.panel.light_switch,
495 GAME_PANEL_LIGHT_SWITCH_TIME,
496 &game.panel.light_switch_time,
500 GAME_PANEL_TIMEGATE_SWITCH,
501 &game.panel.timegate_switch,
505 GAME_PANEL_TIMEGATE_SWITCH_TIME,
506 &game.panel.timegate_switch_time,
510 GAME_PANEL_SWITCHGATE_SWITCH,
511 &game.panel.switchgate_switch,
515 GAME_PANEL_EMC_LENSES,
516 &game.panel.emc_lenses,
520 GAME_PANEL_EMC_LENSES_TIME,
521 &game.panel.emc_lenses_time,
525 GAME_PANEL_EMC_MAGNIFIER,
526 &game.panel.emc_magnifier,
530 GAME_PANEL_EMC_MAGNIFIER_TIME,
531 &game.panel.emc_magnifier_time,
535 GAME_PANEL_BALLOON_SWITCH,
536 &game.panel.balloon_switch,
540 GAME_PANEL_DYNABOMB_NUMBER,
541 &game.panel.dynabomb_number,
545 GAME_PANEL_DYNABOMB_SIZE,
546 &game.panel.dynabomb_size,
550 GAME_PANEL_DYNABOMB_POWER,
551 &game.panel.dynabomb_power,
556 &game.panel.penguins,
560 GAME_PANEL_SOKOBAN_OBJECTS,
561 &game.panel.sokoban_objects,
565 GAME_PANEL_SOKOBAN_FIELDS,
566 &game.panel.sokoban_fields,
570 GAME_PANEL_ROBOT_WHEEL,
571 &game.panel.robot_wheel,
575 GAME_PANEL_CONVEYOR_BELT_1,
576 &game.panel.conveyor_belt[0],
580 GAME_PANEL_CONVEYOR_BELT_2,
581 &game.panel.conveyor_belt[1],
585 GAME_PANEL_CONVEYOR_BELT_3,
586 &game.panel.conveyor_belt[2],
590 GAME_PANEL_CONVEYOR_BELT_4,
591 &game.panel.conveyor_belt[3],
595 GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
596 &game.panel.conveyor_belt_switch[0],
600 GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
601 &game.panel.conveyor_belt_switch[1],
605 GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
606 &game.panel.conveyor_belt_switch[2],
610 GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
611 &game.panel.conveyor_belt_switch[3],
615 GAME_PANEL_MAGIC_WALL,
616 &game.panel.magic_wall,
620 GAME_PANEL_MAGIC_WALL_TIME,
621 &game.panel.magic_wall_time,
625 GAME_PANEL_GRAVITY_STATE,
626 &game.panel.gravity_state,
630 GAME_PANEL_GRAPHIC_1,
631 &game.panel.graphic[0],
635 GAME_PANEL_GRAPHIC_2,
636 &game.panel.graphic[1],
640 GAME_PANEL_GRAPHIC_3,
641 &game.panel.graphic[2],
645 GAME_PANEL_GRAPHIC_4,
646 &game.panel.graphic[3],
650 GAME_PANEL_GRAPHIC_5,
651 &game.panel.graphic[4],
655 GAME_PANEL_GRAPHIC_6,
656 &game.panel.graphic[5],
660 GAME_PANEL_GRAPHIC_7,
661 &game.panel.graphic[6],
665 GAME_PANEL_GRAPHIC_8,
666 &game.panel.graphic[7],
670 GAME_PANEL_ELEMENT_1,
671 &game.panel.element[0],
675 GAME_PANEL_ELEMENT_2,
676 &game.panel.element[1],
680 GAME_PANEL_ELEMENT_3,
681 &game.panel.element[2],
685 GAME_PANEL_ELEMENT_4,
686 &game.panel.element[3],
690 GAME_PANEL_ELEMENT_5,
691 &game.panel.element[4],
695 GAME_PANEL_ELEMENT_6,
696 &game.panel.element[5],
700 GAME_PANEL_ELEMENT_7,
701 &game.panel.element[6],
705 GAME_PANEL_ELEMENT_8,
706 &game.panel.element[7],
710 GAME_PANEL_ELEMENT_COUNT_1,
711 &game.panel.element_count[0],
715 GAME_PANEL_ELEMENT_COUNT_2,
716 &game.panel.element_count[1],
720 GAME_PANEL_ELEMENT_COUNT_3,
721 &game.panel.element_count[2],
725 GAME_PANEL_ELEMENT_COUNT_4,
726 &game.panel.element_count[3],
730 GAME_PANEL_ELEMENT_COUNT_5,
731 &game.panel.element_count[4],
735 GAME_PANEL_ELEMENT_COUNT_6,
736 &game.panel.element_count[5],
740 GAME_PANEL_ELEMENT_COUNT_7,
741 &game.panel.element_count[6],
745 GAME_PANEL_ELEMENT_COUNT_8,
746 &game.panel.element_count[7],
750 GAME_PANEL_CE_SCORE_1,
751 &game.panel.ce_score[0],
755 GAME_PANEL_CE_SCORE_2,
756 &game.panel.ce_score[1],
760 GAME_PANEL_CE_SCORE_3,
761 &game.panel.ce_score[2],
765 GAME_PANEL_CE_SCORE_4,
766 &game.panel.ce_score[3],
770 GAME_PANEL_CE_SCORE_5,
771 &game.panel.ce_score[4],
775 GAME_PANEL_CE_SCORE_6,
776 &game.panel.ce_score[5],
780 GAME_PANEL_CE_SCORE_7,
781 &game.panel.ce_score[6],
785 GAME_PANEL_CE_SCORE_8,
786 &game.panel.ce_score[7],
790 GAME_PANEL_CE_SCORE_1_ELEMENT,
791 &game.panel.ce_score_element[0],
795 GAME_PANEL_CE_SCORE_2_ELEMENT,
796 &game.panel.ce_score_element[1],
800 GAME_PANEL_CE_SCORE_3_ELEMENT,
801 &game.panel.ce_score_element[2],
805 GAME_PANEL_CE_SCORE_4_ELEMENT,
806 &game.panel.ce_score_element[3],
810 GAME_PANEL_CE_SCORE_5_ELEMENT,
811 &game.panel.ce_score_element[4],
815 GAME_PANEL_CE_SCORE_6_ELEMENT,
816 &game.panel.ce_score_element[5],
820 GAME_PANEL_CE_SCORE_7_ELEMENT,
821 &game.panel.ce_score_element[6],
825 GAME_PANEL_CE_SCORE_8_ELEMENT,
826 &game.panel.ce_score_element[7],
830 GAME_PANEL_PLAYER_NAME,
831 &game.panel.player_name,
835 GAME_PANEL_LEVEL_NAME,
836 &game.panel.level_name,
840 GAME_PANEL_LEVEL_AUTHOR,
841 &game.panel.level_author,
852 // values for delayed check of falling and moving elements and for collision
853 #define CHECK_DELAY_MOVING 3
854 #define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
855 #define CHECK_DELAY_COLLISION 2
856 #define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
858 // values for initial player move delay (initial delay counter value)
859 #define INITIAL_MOVE_DELAY_OFF -1
860 #define INITIAL_MOVE_DELAY_ON 0
862 // values for player movement speed (which is in fact a delay value)
863 #define MOVE_DELAY_MIN_SPEED 32
864 #define MOVE_DELAY_NORMAL_SPEED 8
865 #define MOVE_DELAY_HIGH_SPEED 4
866 #define MOVE_DELAY_MAX_SPEED 1
868 #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
869 #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
871 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
872 #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
874 // values for scroll positions
875 #define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
876 (x) > SBX_Right + MIDPOSX ? SBX_Right :\
878 #define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
879 (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
882 // values for other actions
883 #define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
884 #define MOVE_STEPSIZE_MIN (1)
885 #define MOVE_STEPSIZE_MAX (TILEX)
887 #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
888 #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
890 #define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
892 #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
893 RND(element_info[e].push_delay_random))
894 #define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
895 RND(element_info[e].drop_delay_random))
896 #define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
897 RND(element_info[e].move_delay_random))
898 #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
899 (element_info[e].move_delay_random))
900 #define GET_NEW_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
901 RND(element_info[e].step_delay_random))
902 #define GET_MAX_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
903 (element_info[e].step_delay_random))
904 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
905 RND(element_info[e].ce_value_random_initial))
906 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
907 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
908 RND((c)->delay_random * (c)->delay_frames))
909 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
910 RND((c)->delay_random))
913 #define GET_VALID_RUNTIME_ELEMENT(e) \
914 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
916 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
917 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
918 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
919 (be) + (e) - EL_SELF)
921 #define GET_PLAYER_FROM_BITS(p) \
922 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
924 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
925 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
926 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
927 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
928 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
929 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
930 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
931 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
932 RESOLVED_REFERENCE_ELEMENT(be, e) : \
935 #define CAN_GROW_INTO(e) \
936 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
938 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
939 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
942 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
943 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
944 (CAN_MOVE_INTO_ACID(e) && \
945 Tile[x][y] == EL_ACID) || \
948 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
949 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
950 (CAN_MOVE_INTO_ACID(e) && \
951 Tile[x][y] == EL_ACID) || \
954 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
955 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
957 (CAN_MOVE_INTO_ACID(e) && \
958 Tile[x][y] == EL_ACID) || \
959 (DONT_COLLIDE_WITH(e) && \
961 !PLAYER_ENEMY_PROTECTED(x, y))))
963 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
964 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
966 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
967 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
969 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
970 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
972 #define ANDROID_CAN_CLONE_FIELD(x, y) \
973 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
974 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
976 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
977 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
979 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
980 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
982 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
983 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
985 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
986 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
988 #define PIG_CAN_ENTER_FIELD(e, x, y) \
989 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
991 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
992 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
993 Tile[x][y] == EL_EM_EXIT_OPEN || \
994 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
995 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
996 IS_FOOD_PENGUIN(Tile[x][y])))
997 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
998 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
1000 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
1001 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
1003 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
1004 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
1006 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
1007 (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
1008 Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
1010 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
1012 #define CE_ENTER_FIELD_COND(e, x, y) \
1013 (!IS_PLAYER(x, y) && \
1014 IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
1016 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
1017 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
1019 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
1020 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1022 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1023 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1024 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1025 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1027 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1029 // game button identifiers
1030 #define GAME_CTRL_ID_STOP 0
1031 #define GAME_CTRL_ID_PAUSE 1
1032 #define GAME_CTRL_ID_PLAY 2
1033 #define GAME_CTRL_ID_UNDO 3
1034 #define GAME_CTRL_ID_REDO 4
1035 #define GAME_CTRL_ID_SAVE 5
1036 #define GAME_CTRL_ID_PAUSE2 6
1037 #define GAME_CTRL_ID_LOAD 7
1038 #define GAME_CTRL_ID_RESTART 8
1039 #define GAME_CTRL_ID_PANEL_STOP 9
1040 #define GAME_CTRL_ID_PANEL_PAUSE 10
1041 #define GAME_CTRL_ID_PANEL_PLAY 11
1042 #define GAME_CTRL_ID_PANEL_RESTART 12
1043 #define GAME_CTRL_ID_TOUCH_STOP 13
1044 #define GAME_CTRL_ID_TOUCH_PAUSE 14
1045 #define GAME_CTRL_ID_TOUCH_RESTART 15
1046 #define SOUND_CTRL_ID_MUSIC 16
1047 #define SOUND_CTRL_ID_LOOPS 17
1048 #define SOUND_CTRL_ID_SIMPLE 18
1049 #define SOUND_CTRL_ID_PANEL_MUSIC 19
1050 #define SOUND_CTRL_ID_PANEL_LOOPS 20
1051 #define SOUND_CTRL_ID_PANEL_SIMPLE 21
1053 #define NUM_GAME_BUTTONS 22
1056 // forward declaration for internal use
1058 static void CreateField(int, int, int);
1060 static void ResetGfxAnimation(int, int);
1062 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1063 static void AdvanceFrameAndPlayerCounters(int);
1065 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1066 static boolean MovePlayer(struct PlayerInfo *, int, int);
1067 static void ScrollPlayer(struct PlayerInfo *, int);
1068 static void ScrollScreen(struct PlayerInfo *, int);
1070 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1071 static boolean DigFieldByCE(int, int, int);
1072 static boolean SnapField(struct PlayerInfo *, int, int);
1073 static boolean DropElement(struct PlayerInfo *);
1075 static void InitBeltMovement(void);
1076 static void CloseAllOpenTimegates(void);
1077 static void CheckGravityMovement(struct PlayerInfo *);
1078 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1079 static void KillPlayerUnlessEnemyProtected(int, int);
1080 static void KillPlayerUnlessExplosionProtected(int, int);
1082 static void CheckNextToConditions(int, int);
1083 static void TestIfPlayerNextToCustomElement(int, int);
1084 static void TestIfPlayerTouchesCustomElement(int, int);
1085 static void TestIfElementNextToCustomElement(int, int);
1086 static void TestIfElementTouchesCustomElement(int, int);
1087 static void TestIfElementHitsCustomElement(int, int, int);
1089 static void HandleElementChange(int, int, int);
1090 static void ExecuteCustomElementAction(int, int, int, int);
1091 static boolean ChangeElement(int, int, int, int);
1093 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int, int, int);
1094 #define CheckTriggeredElementChange(x, y, e, ev) \
1095 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, -1)
1096 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1097 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1098 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1099 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1100 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1101 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1102 #define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
1103 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1105 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1106 #define CheckElementChange(x, y, e, te, ev) \
1107 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1108 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1109 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1110 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1111 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1112 #define CheckElementChangeByMouse(x, y, e, ev, s) \
1113 CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
1115 static void PlayLevelSound(int, int, int);
1116 static void PlayLevelSoundNearest(int, int, int);
1117 static void PlayLevelSoundAction(int, int, int);
1118 static void PlayLevelSoundElementAction(int, int, int, int);
1119 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1120 static void PlayLevelSoundActionIfLoop(int, int, int);
1121 static void StopLevelSoundActionIfLoop(int, int, int);
1122 static void PlayLevelMusic(void);
1123 static void FadeLevelSoundsAndMusic(void);
1125 static void HandleGameButtons(struct GadgetInfo *);
1127 int AmoebaNeighbourNr(int, int);
1128 void AmoebaToDiamond(int, int);
1129 void ContinueMoving(int, int);
1130 void Bang(int, int);
1131 void InitMovDir(int, int);
1132 void InitAmoebaNr(int, int);
1133 void NewHighScore(int, boolean);
1135 void TestIfGoodThingHitsBadThing(int, int, int);
1136 void TestIfBadThingHitsGoodThing(int, int, int);
1137 void TestIfPlayerTouchesBadThing(int, int);
1138 void TestIfPlayerRunsIntoBadThing(int, int, int);
1139 void TestIfBadThingTouchesPlayer(int, int);
1140 void TestIfBadThingRunsIntoPlayer(int, int, int);
1141 void TestIfFriendTouchesBadThing(int, int);
1142 void TestIfBadThingTouchesFriend(int, int);
1143 void TestIfBadThingTouchesOtherBadThing(int, int);
1144 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1146 void KillPlayer(struct PlayerInfo *);
1147 void BuryPlayer(struct PlayerInfo *);
1148 void RemovePlayer(struct PlayerInfo *);
1149 void ExitPlayer(struct PlayerInfo *);
1151 static int getInvisibleActiveFromInvisibleElement(int);
1152 static int getInvisibleFromInvisibleActiveElement(int);
1154 static void TestFieldAfterSnapping(int, int, int, int, int);
1156 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1158 // for detection of endless loops, caused by custom element programming
1159 // (using maximal playfield width x 10 is just a rough approximation)
1160 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1162 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1164 if (recursion_loop_detected) \
1167 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1169 recursion_loop_detected = TRUE; \
1170 recursion_loop_element = (e); \
1173 recursion_loop_depth++; \
1176 #define RECURSION_LOOP_DETECTION_END() \
1178 recursion_loop_depth--; \
1181 static int recursion_loop_depth;
1182 static boolean recursion_loop_detected;
1183 static boolean recursion_loop_element;
1185 static int map_player_action[MAX_PLAYERS];
1188 // ----------------------------------------------------------------------------
1189 // definition of elements that automatically change to other elements after
1190 // a specified time, eventually calling a function when changing
1191 // ----------------------------------------------------------------------------
1193 // forward declaration for changer functions
1194 static void InitBuggyBase(int, int);
1195 static void WarnBuggyBase(int, int);
1197 static void InitTrap(int, int);
1198 static void ActivateTrap(int, int);
1199 static void ChangeActiveTrap(int, int);
1201 static void InitRobotWheel(int, int);
1202 static void RunRobotWheel(int, int);
1203 static void StopRobotWheel(int, int);
1205 static void InitTimegateWheel(int, int);
1206 static void RunTimegateWheel(int, int);
1208 static void InitMagicBallDelay(int, int);
1209 static void ActivateMagicBall(int, int);
1211 struct ChangingElementInfo
1216 void (*pre_change_function)(int x, int y);
1217 void (*change_function)(int x, int y);
1218 void (*post_change_function)(int x, int y);
1221 static struct ChangingElementInfo change_delay_list[] =
1256 EL_STEEL_EXIT_OPENING,
1264 EL_STEEL_EXIT_CLOSING,
1265 EL_STEEL_EXIT_CLOSED,
1288 EL_EM_STEEL_EXIT_OPENING,
1289 EL_EM_STEEL_EXIT_OPEN,
1296 EL_EM_STEEL_EXIT_CLOSING,
1320 EL_SWITCHGATE_OPENING,
1328 EL_SWITCHGATE_CLOSING,
1329 EL_SWITCHGATE_CLOSED,
1336 EL_TIMEGATE_OPENING,
1344 EL_TIMEGATE_CLOSING,
1353 EL_ACID_SPLASH_LEFT,
1361 EL_ACID_SPLASH_RIGHT,
1370 EL_SP_BUGGY_BASE_ACTIVATING,
1377 EL_SP_BUGGY_BASE_ACTIVATING,
1378 EL_SP_BUGGY_BASE_ACTIVE,
1385 EL_SP_BUGGY_BASE_ACTIVE,
1409 EL_ROBOT_WHEEL_ACTIVE,
1417 EL_TIMEGATE_SWITCH_ACTIVE,
1425 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1426 EL_DC_TIMEGATE_SWITCH,
1433 EL_EMC_MAGIC_BALL_ACTIVE,
1434 EL_EMC_MAGIC_BALL_ACTIVE,
1441 EL_EMC_SPRING_BUMPER_ACTIVE,
1442 EL_EMC_SPRING_BUMPER,
1449 EL_DIAGONAL_SHRINKING,
1457 EL_DIAGONAL_GROWING,
1478 int push_delay_fixed, push_delay_random;
1482 { EL_SPRING, 0, 0 },
1483 { EL_BALLOON, 0, 0 },
1485 { EL_SOKOBAN_OBJECT, 2, 0 },
1486 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1487 { EL_SATELLITE, 2, 0 },
1488 { EL_SP_DISK_YELLOW, 2, 0 },
1490 { EL_UNDEFINED, 0, 0 },
1498 move_stepsize_list[] =
1500 { EL_AMOEBA_DROP, 2 },
1501 { EL_AMOEBA_DROPPING, 2 },
1502 { EL_QUICKSAND_FILLING, 1 },
1503 { EL_QUICKSAND_EMPTYING, 1 },
1504 { EL_QUICKSAND_FAST_FILLING, 2 },
1505 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1506 { EL_MAGIC_WALL_FILLING, 2 },
1507 { EL_MAGIC_WALL_EMPTYING, 2 },
1508 { EL_BD_MAGIC_WALL_FILLING, 2 },
1509 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1510 { EL_DC_MAGIC_WALL_FILLING, 2 },
1511 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1513 { EL_UNDEFINED, 0 },
1521 collect_count_list[] =
1524 { EL_BD_DIAMOND, 1 },
1525 { EL_EMERALD_YELLOW, 1 },
1526 { EL_EMERALD_RED, 1 },
1527 { EL_EMERALD_PURPLE, 1 },
1529 { EL_SP_INFOTRON, 1 },
1533 { EL_UNDEFINED, 0 },
1541 access_direction_list[] =
1543 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1544 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1545 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1546 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1547 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1548 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1549 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1550 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1551 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1552 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1553 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1555 { EL_SP_PORT_LEFT, MV_RIGHT },
1556 { EL_SP_PORT_RIGHT, MV_LEFT },
1557 { EL_SP_PORT_UP, MV_DOWN },
1558 { EL_SP_PORT_DOWN, MV_UP },
1559 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1560 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1561 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1562 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1563 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1564 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1565 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1566 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1567 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1568 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1569 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1570 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1571 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1572 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1573 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1575 { EL_UNDEFINED, MV_NONE }
1578 static struct XY xy_topdown[] =
1586 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1588 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1589 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1590 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1591 IS_JUST_CHANGING(x, y))
1593 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1595 // static variables for playfield scan mode (scanning forward or backward)
1596 static int playfield_scan_start_x = 0;
1597 static int playfield_scan_start_y = 0;
1598 static int playfield_scan_delta_x = 1;
1599 static int playfield_scan_delta_y = 1;
1601 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1602 (y) >= 0 && (y) <= lev_fieldy - 1; \
1603 (y) += playfield_scan_delta_y) \
1604 for ((x) = playfield_scan_start_x; \
1605 (x) >= 0 && (x) <= lev_fieldx - 1; \
1606 (x) += playfield_scan_delta_x)
1609 void DEBUG_SetMaximumDynamite(void)
1613 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1614 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1615 local_player->inventory_element[local_player->inventory_size++] =
1620 static void InitPlayfieldScanModeVars(void)
1622 if (game.use_reverse_scan_direction)
1624 playfield_scan_start_x = lev_fieldx - 1;
1625 playfield_scan_start_y = lev_fieldy - 1;
1627 playfield_scan_delta_x = -1;
1628 playfield_scan_delta_y = -1;
1632 playfield_scan_start_x = 0;
1633 playfield_scan_start_y = 0;
1635 playfield_scan_delta_x = 1;
1636 playfield_scan_delta_y = 1;
1640 static void InitPlayfieldScanMode(int mode)
1642 game.use_reverse_scan_direction =
1643 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1645 InitPlayfieldScanModeVars();
1648 static int get_move_delay_from_stepsize(int move_stepsize)
1651 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1653 // make sure that stepsize value is always a power of 2
1654 move_stepsize = (1 << log_2(move_stepsize));
1656 return TILEX / move_stepsize;
1659 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1662 int player_nr = player->index_nr;
1663 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1664 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1666 // do no immediately change move delay -- the player might just be moving
1667 player->move_delay_value_next = move_delay;
1669 // information if player can move must be set separately
1670 player->cannot_move = cannot_move;
1674 player->move_delay = game.initial_move_delay[player_nr];
1675 player->move_delay_value = game.initial_move_delay_value[player_nr];
1677 player->move_delay_value_next = -1;
1679 player->move_delay_reset_counter = 0;
1683 void GetPlayerConfig(void)
1685 GameFrameDelay = setup.game_frame_delay;
1687 if (!audio.sound_available)
1688 setup.sound_simple = FALSE;
1690 if (!audio.loops_available)
1691 setup.sound_loops = FALSE;
1693 if (!audio.music_available)
1694 setup.sound_music = FALSE;
1696 if (!video.fullscreen_available)
1697 setup.fullscreen = FALSE;
1699 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1701 SetAudioMode(setup.sound);
1704 int GetElementFromGroupElement(int element)
1706 if (IS_GROUP_ELEMENT(element))
1708 struct ElementGroupInfo *group = element_info[element].group;
1709 int last_anim_random_frame = gfx.anim_random_frame;
1712 if (group->choice_mode == ANIM_RANDOM)
1713 gfx.anim_random_frame = RND(group->num_elements_resolved);
1715 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1716 group->choice_mode, 0,
1719 if (group->choice_mode == ANIM_RANDOM)
1720 gfx.anim_random_frame = last_anim_random_frame;
1722 group->choice_pos++;
1724 element = group->element_resolved[element_pos];
1730 static void IncrementSokobanFieldsNeeded(void)
1732 if (level.sb_fields_needed)
1733 game.sokoban_fields_still_needed++;
1736 static void IncrementSokobanObjectsNeeded(void)
1738 if (level.sb_objects_needed)
1739 game.sokoban_objects_still_needed++;
1742 static void DecrementSokobanFieldsNeeded(void)
1744 if (game.sokoban_fields_still_needed > 0)
1745 game.sokoban_fields_still_needed--;
1748 static void DecrementSokobanObjectsNeeded(void)
1750 if (game.sokoban_objects_still_needed > 0)
1751 game.sokoban_objects_still_needed--;
1754 static void InitPlayerField(int x, int y, int element, boolean init_game)
1756 if (element == EL_SP_MURPHY)
1760 if (stored_player[0].present)
1762 Tile[x][y] = EL_SP_MURPHY_CLONE;
1768 stored_player[0].initial_element = element;
1769 stored_player[0].use_murphy = TRUE;
1771 if (!level.use_artwork_element[0])
1772 stored_player[0].artwork_element = EL_SP_MURPHY;
1775 Tile[x][y] = EL_PLAYER_1;
1781 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1782 int jx = player->jx, jy = player->jy;
1784 player->present = TRUE;
1786 player->block_last_field = (element == EL_SP_MURPHY ?
1787 level.sp_block_last_field :
1788 level.block_last_field);
1790 // ---------- initialize player's last field block delay ------------------
1792 // always start with reliable default value (no adjustment needed)
1793 player->block_delay_adjustment = 0;
1795 // special case 1: in Supaplex, Murphy blocks last field one more frame
1796 if (player->block_last_field && element == EL_SP_MURPHY)
1797 player->block_delay_adjustment = 1;
1799 // special case 2: in game engines before 3.1.1, blocking was different
1800 if (game.use_block_last_field_bug)
1801 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1803 if (!network.enabled || player->connected_network)
1805 player->active = TRUE;
1807 // remove potentially duplicate players
1808 if (IN_LEV_FIELD(jx, jy) && StorePlayer[jx][jy] == Tile[x][y])
1809 StorePlayer[jx][jy] = 0;
1811 StorePlayer[x][y] = Tile[x][y];
1813 #if DEBUG_INIT_PLAYER
1814 Debug("game:init:player", "- player element %d activated",
1815 player->element_nr);
1816 Debug("game:init:player", " (local player is %d and currently %s)",
1817 local_player->element_nr,
1818 local_player->active ? "active" : "not active");
1822 Tile[x][y] = EL_EMPTY;
1824 player->jx = player->last_jx = x;
1825 player->jy = player->last_jy = y;
1828 // always check if player was just killed and should be reanimated
1830 int player_nr = GET_PLAYER_NR(element);
1831 struct PlayerInfo *player = &stored_player[player_nr];
1833 if (player->active && player->killed)
1834 player->reanimated = TRUE; // if player was just killed, reanimate him
1838 static void InitFieldForEngine_RND(int x, int y)
1840 int element = Tile[x][y];
1842 // convert BD engine elements to corresponding R'n'D engine elements
1843 element = (element == EL_BD_EMPTY ? EL_EMPTY :
1844 element == EL_BD_PLAYER ? EL_PLAYER_1 :
1845 element == EL_BD_INBOX ? EL_PLAYER_1 :
1846 element == EL_BD_SAND ? EL_SAND :
1847 element == EL_BD_STEELWALL ? EL_STEELWALL :
1848 element == EL_BD_EXIT_CLOSED ? EL_EXIT_CLOSED :
1849 element == EL_BD_EXIT_OPEN ? EL_EXIT_OPEN :
1852 Tile[x][y] = element;
1855 static void InitFieldForEngine(int x, int y)
1857 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
1858 InitFieldForEngine_RND(x, y);
1861 static void InitField(int x, int y, boolean init_game)
1863 int element = Tile[x][y];
1872 InitPlayerField(x, y, element, init_game);
1875 case EL_SOKOBAN_FIELD_PLAYER:
1876 element = Tile[x][y] = EL_PLAYER_1;
1877 InitField(x, y, init_game);
1879 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1880 InitField(x, y, init_game);
1883 case EL_SOKOBAN_FIELD_EMPTY:
1884 IncrementSokobanFieldsNeeded();
1887 case EL_SOKOBAN_OBJECT:
1888 IncrementSokobanObjectsNeeded();
1892 if (x < lev_fieldx - 1 && Tile[x + 1][y] == EL_ACID)
1893 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1894 else if (x > 0 && Tile[x - 1][y] == EL_ACID)
1895 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1896 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPLEFT)
1897 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1898 else if (y > 0 && Tile[x][y - 1] == EL_ACID)
1899 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1900 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPRIGHT)
1901 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1910 case EL_SPACESHIP_RIGHT:
1911 case EL_SPACESHIP_UP:
1912 case EL_SPACESHIP_LEFT:
1913 case EL_SPACESHIP_DOWN:
1914 case EL_BD_BUTTERFLY:
1915 case EL_BD_BUTTERFLY_RIGHT:
1916 case EL_BD_BUTTERFLY_UP:
1917 case EL_BD_BUTTERFLY_LEFT:
1918 case EL_BD_BUTTERFLY_DOWN:
1920 case EL_BD_FIREFLY_RIGHT:
1921 case EL_BD_FIREFLY_UP:
1922 case EL_BD_FIREFLY_LEFT:
1923 case EL_BD_FIREFLY_DOWN:
1924 case EL_PACMAN_RIGHT:
1926 case EL_PACMAN_LEFT:
1927 case EL_PACMAN_DOWN:
1929 case EL_YAMYAM_LEFT:
1930 case EL_YAMYAM_RIGHT:
1932 case EL_YAMYAM_DOWN:
1933 case EL_DARK_YAMYAM:
1936 case EL_SP_SNIKSNAK:
1937 case EL_SP_ELECTRON:
1943 case EL_SPRING_LEFT:
1944 case EL_SPRING_RIGHT:
1948 case EL_AMOEBA_FULL:
1953 case EL_AMOEBA_DROP:
1954 if (y == lev_fieldy - 1)
1956 Tile[x][y] = EL_AMOEBA_GROWING;
1957 Store[x][y] = EL_AMOEBA_WET;
1961 case EL_DYNAMITE_ACTIVE:
1962 case EL_SP_DISK_RED_ACTIVE:
1963 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1964 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1965 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1966 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1967 MovDelay[x][y] = 96;
1970 case EL_EM_DYNAMITE_ACTIVE:
1971 MovDelay[x][y] = 32;
1975 game.lights_still_needed++;
1979 game.friends_still_needed++;
1984 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1987 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1988 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1989 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1990 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1991 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1992 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1993 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1994 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1995 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1996 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1997 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1998 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
2001 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
2002 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
2003 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
2005 if (game.belt_dir_nr[belt_nr] == 3) // initial value
2007 game.belt_dir[belt_nr] = belt_dir;
2008 game.belt_dir_nr[belt_nr] = belt_dir_nr;
2010 else // more than one switch -- set it like the first switch
2012 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
2017 case EL_LIGHT_SWITCH_ACTIVE:
2019 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
2022 case EL_INVISIBLE_STEELWALL:
2023 case EL_INVISIBLE_WALL:
2024 case EL_INVISIBLE_SAND:
2025 if (game.light_time_left > 0 ||
2026 game.lenses_time_left > 0)
2027 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
2030 case EL_EMC_MAGIC_BALL:
2031 if (game.ball_active)
2032 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
2035 case EL_EMC_MAGIC_BALL_SWITCH:
2036 if (game.ball_active)
2037 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
2040 case EL_TRIGGER_PLAYER:
2041 case EL_TRIGGER_ELEMENT:
2042 case EL_TRIGGER_CE_VALUE:
2043 case EL_TRIGGER_CE_SCORE:
2045 case EL_ANY_ELEMENT:
2046 case EL_CURRENT_CE_VALUE:
2047 case EL_CURRENT_CE_SCORE:
2064 // reference elements should not be used on the playfield
2065 Tile[x][y] = EL_EMPTY;
2069 if (IS_CUSTOM_ELEMENT(element))
2071 if (CAN_MOVE(element))
2074 if (!element_info[element].use_last_ce_value || init_game)
2075 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2077 else if (IS_GROUP_ELEMENT(element))
2079 Tile[x][y] = GetElementFromGroupElement(element);
2081 InitField(x, y, init_game);
2083 else if (IS_EMPTY_ELEMENT(element))
2085 GfxElementEmpty[x][y] = element;
2086 Tile[x][y] = EL_EMPTY;
2088 if (element_info[element].use_gfx_element)
2089 game.use_masked_elements = TRUE;
2096 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2099 static void InitField_WithBug1(int x, int y, boolean init_game)
2101 InitField(x, y, init_game);
2103 // not needed to call InitMovDir() -- already done by InitField()!
2104 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2105 CAN_MOVE(Tile[x][y]))
2109 static void InitField_WithBug2(int x, int y, boolean init_game)
2111 int old_element = Tile[x][y];
2113 InitField(x, y, init_game);
2115 // not needed to call InitMovDir() -- already done by InitField()!
2116 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2117 CAN_MOVE(old_element) &&
2118 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2121 /* this case is in fact a combination of not less than three bugs:
2122 first, it calls InitMovDir() for elements that can move, although this is
2123 already done by InitField(); then, it checks the element that was at this
2124 field _before_ the call to InitField() (which can change it); lastly, it
2125 was not called for "mole with direction" elements, which were treated as
2126 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2130 static int get_key_element_from_nr(int key_nr)
2132 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2133 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2134 EL_EM_KEY_1 : EL_KEY_1);
2136 return key_base_element + key_nr;
2139 static int get_next_dropped_element(struct PlayerInfo *player)
2141 return (player->inventory_size > 0 ?
2142 player->inventory_element[player->inventory_size - 1] :
2143 player->inventory_infinite_element != EL_UNDEFINED ?
2144 player->inventory_infinite_element :
2145 player->dynabombs_left > 0 ?
2146 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2150 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2152 // pos >= 0: get element from bottom of the stack;
2153 // pos < 0: get element from top of the stack
2157 int min_inventory_size = -pos;
2158 int inventory_pos = player->inventory_size - min_inventory_size;
2159 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2161 return (player->inventory_size >= min_inventory_size ?
2162 player->inventory_element[inventory_pos] :
2163 player->inventory_infinite_element != EL_UNDEFINED ?
2164 player->inventory_infinite_element :
2165 player->dynabombs_left >= min_dynabombs_left ?
2166 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2171 int min_dynabombs_left = pos + 1;
2172 int min_inventory_size = pos + 1 - player->dynabombs_left;
2173 int inventory_pos = pos - player->dynabombs_left;
2175 return (player->inventory_infinite_element != EL_UNDEFINED ?
2176 player->inventory_infinite_element :
2177 player->dynabombs_left >= min_dynabombs_left ?
2178 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2179 player->inventory_size >= min_inventory_size ?
2180 player->inventory_element[inventory_pos] :
2185 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2187 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2188 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2191 if (gpo1->sort_priority != gpo2->sort_priority)
2192 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2194 compare_result = gpo1->nr - gpo2->nr;
2196 return compare_result;
2199 int getPlayerInventorySize(int player_nr)
2201 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2202 return game_em.ply[player_nr]->dynamite;
2203 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2204 return game_sp.red_disk_count;
2206 return stored_player[player_nr].inventory_size;
2209 static void InitGameControlValues(void)
2213 for (i = 0; game_panel_controls[i].nr != -1; i++)
2215 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2216 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2217 struct TextPosInfo *pos = gpc->pos;
2219 int type = gpc->type;
2223 Error("'game_panel_controls' structure corrupted at %d", i);
2225 Fail("this should not happen -- please debug");
2228 // force update of game controls after initialization
2229 gpc->value = gpc->last_value = -1;
2230 gpc->frame = gpc->last_frame = -1;
2231 gpc->gfx_frame = -1;
2233 // determine panel value width for later calculation of alignment
2234 if (type == TYPE_INTEGER || type == TYPE_STRING)
2236 pos->width = pos->size * getFontWidth(pos->font);
2237 pos->height = getFontHeight(pos->font);
2239 else if (type == TYPE_ELEMENT)
2241 pos->width = pos->size;
2242 pos->height = pos->size;
2245 // fill structure for game panel draw order
2247 gpo->sort_priority = pos->sort_priority;
2250 // sort game panel controls according to sort_priority and control number
2251 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2252 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2255 static void UpdatePlayfieldElementCount(void)
2257 boolean use_element_count = FALSE;
2260 // first check if it is needed at all to calculate playfield element count
2261 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2262 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2263 use_element_count = TRUE;
2265 if (!use_element_count)
2268 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2269 element_info[i].element_count = 0;
2271 SCAN_PLAYFIELD(x, y)
2273 element_info[Tile[x][y]].element_count++;
2276 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2277 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2278 if (IS_IN_GROUP(j, i))
2279 element_info[EL_GROUP_START + i].element_count +=
2280 element_info[j].element_count;
2283 static void UpdateGameControlValues(void)
2286 int time = (game.LevelSolved ?
2287 game.LevelSolved_CountingTime :
2288 level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2289 game_bd.time_played :
2290 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2292 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2293 game_sp.time_played :
2294 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2295 game_mm.energy_left :
2296 game.no_level_time_limit ? TimePlayed : TimeLeft);
2297 int score = (game.LevelSolved ?
2298 game.LevelSolved_CountingScore :
2299 level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2301 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2302 game_em.lev->score :
2303 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2305 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2308 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2309 game_bd.gems_still_needed :
2310 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2311 game_em.lev->gems_needed :
2312 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2313 game_sp.infotrons_still_needed :
2314 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2315 game_mm.kettles_still_needed :
2316 game.gems_still_needed);
2317 int gems_needed = level.gems_needed;
2318 int gems_collected = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2319 game_bd.game->cave->diamonds_collected :
2320 gems_needed - gems);
2321 int gems_score = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2322 game_bd.game->cave->diamond_value :
2323 level.score[SC_EMERALD]);
2324 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2325 game_bd.gems_still_needed > 0 :
2326 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2327 game_em.lev->gems_needed > 0 :
2328 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2329 game_sp.infotrons_still_needed > 0 :
2330 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2331 game_mm.kettles_still_needed > 0 ||
2332 game_mm.lights_still_needed > 0 :
2333 game.gems_still_needed > 0 ||
2334 game.sokoban_fields_still_needed > 0 ||
2335 game.sokoban_objects_still_needed > 0 ||
2336 game.lights_still_needed > 0);
2337 int health = (game.LevelSolved ?
2338 game.LevelSolved_CountingHealth :
2339 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2340 MM_HEALTH(game_mm.laser_overload_value) :
2342 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2344 UpdatePlayfieldElementCount();
2346 // update game panel control values
2348 // used instead of "level_nr" (for network games)
2349 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2350 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2351 game_panel_controls[GAME_PANEL_GEMS_NEEDED].value = gems_needed;
2352 game_panel_controls[GAME_PANEL_GEMS_COLLECTED].value = gems_collected;
2353 game_panel_controls[GAME_PANEL_GEMS_SCORE].value = gems_score;
2355 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2356 for (i = 0; i < MAX_NUM_KEYS; i++)
2357 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2358 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2359 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2361 if (game.centered_player_nr == -1)
2363 for (i = 0; i < MAX_PLAYERS; i++)
2365 // only one player in Supaplex game engine
2366 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2369 for (k = 0; k < MAX_NUM_KEYS; k++)
2371 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2373 if (game_em.ply[i]->keys & (1 << k))
2374 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2375 get_key_element_from_nr(k);
2377 else if (stored_player[i].key[k])
2378 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2379 get_key_element_from_nr(k);
2382 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2383 getPlayerInventorySize(i);
2385 if (stored_player[i].num_white_keys > 0)
2386 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2389 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2390 stored_player[i].num_white_keys;
2395 int player_nr = game.centered_player_nr;
2397 for (k = 0; k < MAX_NUM_KEYS; k++)
2399 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2401 if (game_em.ply[player_nr]->keys & (1 << k))
2402 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2403 get_key_element_from_nr(k);
2405 else if (stored_player[player_nr].key[k])
2406 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2407 get_key_element_from_nr(k);
2410 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2411 getPlayerInventorySize(player_nr);
2413 if (stored_player[player_nr].num_white_keys > 0)
2414 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2416 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2417 stored_player[player_nr].num_white_keys;
2420 // re-arrange keys on game panel, if needed or if defined by style settings
2421 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2423 int nr = GAME_PANEL_KEY_1 + i;
2424 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2425 struct TextPosInfo *pos = gpc->pos;
2427 // skip check if key is not in the player's inventory
2428 if (gpc->value == EL_EMPTY)
2431 // check if keys should be arranged on panel from left to right
2432 if (pos->style == STYLE_LEFTMOST_POSITION)
2434 // check previous key positions (left from current key)
2435 for (k = 0; k < i; k++)
2437 int nr_new = GAME_PANEL_KEY_1 + k;
2439 if (game_panel_controls[nr_new].value == EL_EMPTY)
2441 game_panel_controls[nr_new].value = gpc->value;
2442 gpc->value = EL_EMPTY;
2449 // check if "undefined" keys can be placed at some other position
2450 if (pos->x == -1 && pos->y == -1)
2452 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2454 // 1st try: display key at the same position as normal or EM keys
2455 if (game_panel_controls[nr_new].value == EL_EMPTY)
2457 game_panel_controls[nr_new].value = gpc->value;
2461 // 2nd try: display key at the next free position in the key panel
2462 for (k = 0; k < STD_NUM_KEYS; k++)
2464 nr_new = GAME_PANEL_KEY_1 + k;
2466 if (game_panel_controls[nr_new].value == EL_EMPTY)
2468 game_panel_controls[nr_new].value = gpc->value;
2477 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2479 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2480 get_inventory_element_from_pos(local_player, i);
2481 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2482 get_inventory_element_from_pos(local_player, -i - 1);
2485 game_panel_controls[GAME_PANEL_SCORE].value = score;
2486 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2488 game_panel_controls[GAME_PANEL_TIME].value = time;
2490 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2491 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2492 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2494 if (level.time == 0)
2495 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2497 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2499 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2500 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2502 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2504 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2505 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2507 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2508 local_player->shield_normal_time_left;
2509 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2510 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2512 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2513 local_player->shield_deadly_time_left;
2515 game_panel_controls[GAME_PANEL_EXIT].value =
2516 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2518 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2519 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2520 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2521 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2522 EL_EMC_MAGIC_BALL_SWITCH);
2524 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2525 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2526 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2527 game.light_time_left;
2529 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2530 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2531 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2532 game.timegate_time_left;
2534 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2535 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2537 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2538 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2539 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2540 game.lenses_time_left;
2542 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2543 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2544 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2545 game.magnify_time_left;
2547 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2548 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2549 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2550 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2551 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2552 EL_BALLOON_SWITCH_NONE);
2554 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2555 local_player->dynabomb_count;
2556 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2557 local_player->dynabomb_size;
2558 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2559 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2561 game_panel_controls[GAME_PANEL_PENGUINS].value =
2562 game.friends_still_needed;
2564 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2565 game.sokoban_objects_still_needed;
2566 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2567 game.sokoban_fields_still_needed;
2569 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2570 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2572 for (i = 0; i < NUM_BELTS; i++)
2574 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2575 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2576 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2577 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2578 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2581 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2582 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2583 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2584 game.magic_wall_time_left;
2586 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2587 local_player->gravity;
2589 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2590 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2592 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2593 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2594 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2595 game.panel.element[i].id : EL_UNDEFINED);
2597 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2598 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2599 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2600 element_info[game.panel.element_count[i].id].element_count : 0);
2602 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2603 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2604 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2605 element_info[game.panel.ce_score[i].id].collect_score : 0);
2607 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2608 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2609 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2610 element_info[game.panel.ce_score_element[i].id].collect_score :
2613 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2614 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2615 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2617 // update game panel control frames
2619 for (i = 0; game_panel_controls[i].nr != -1; i++)
2621 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2623 if (gpc->type == TYPE_ELEMENT)
2625 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2627 int last_anim_random_frame = gfx.anim_random_frame;
2628 int element = gpc->value;
2629 int graphic = el2panelimg(element);
2630 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2632 graphic_info[graphic].anim_global_anim_sync ?
2633 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2635 if (gpc->value != gpc->last_value)
2638 gpc->gfx_random = init_gfx_random;
2644 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2645 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2646 gpc->gfx_random = init_gfx_random;
2649 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2650 gfx.anim_random_frame = gpc->gfx_random;
2652 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2653 gpc->gfx_frame = element_info[element].collect_score;
2655 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2657 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2658 gfx.anim_random_frame = last_anim_random_frame;
2661 else if (gpc->type == TYPE_GRAPHIC)
2663 if (gpc->graphic != IMG_UNDEFINED)
2665 int last_anim_random_frame = gfx.anim_random_frame;
2666 int graphic = gpc->graphic;
2667 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2669 graphic_info[graphic].anim_global_anim_sync ?
2670 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2672 if (gpc->value != gpc->last_value)
2675 gpc->gfx_random = init_gfx_random;
2681 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2682 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2683 gpc->gfx_random = init_gfx_random;
2686 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2687 gfx.anim_random_frame = gpc->gfx_random;
2689 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2691 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2692 gfx.anim_random_frame = last_anim_random_frame;
2698 static void DisplayGameControlValues(void)
2700 boolean redraw_panel = FALSE;
2703 for (i = 0; game_panel_controls[i].nr != -1; i++)
2705 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2707 if (PANEL_DEACTIVATED(gpc->pos))
2710 if (gpc->value == gpc->last_value &&
2711 gpc->frame == gpc->last_frame)
2714 redraw_panel = TRUE;
2720 // copy default game door content to main double buffer
2722 // !!! CHECK AGAIN !!!
2723 SetPanelBackground();
2724 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2725 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2727 // redraw game control buttons
2728 RedrawGameButtons();
2730 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2732 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2734 int nr = game_panel_order[i].nr;
2735 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2736 struct TextPosInfo *pos = gpc->pos;
2737 int type = gpc->type;
2738 int value = gpc->value;
2739 int frame = gpc->frame;
2740 int size = pos->size;
2741 int font = pos->font;
2742 boolean draw_masked = pos->draw_masked;
2743 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2745 if (PANEL_DEACTIVATED(pos))
2748 if (pos->class == get_hash_from_string("extra_panel_items") &&
2749 !setup.prefer_extra_panel_items)
2752 gpc->last_value = value;
2753 gpc->last_frame = frame;
2755 if (type == TYPE_INTEGER)
2757 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2758 nr == GAME_PANEL_INVENTORY_COUNT ||
2759 nr == GAME_PANEL_SCORE ||
2760 nr == GAME_PANEL_HIGHSCORE ||
2761 nr == GAME_PANEL_TIME)
2763 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2765 if (use_dynamic_size) // use dynamic number of digits
2767 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 :
2768 nr == GAME_PANEL_INVENTORY_COUNT ||
2769 nr == GAME_PANEL_TIME ? 1000 : 100000);
2770 int size_add = (nr == GAME_PANEL_LEVEL_NUMBER ||
2771 nr == GAME_PANEL_INVENTORY_COUNT ||
2772 nr == GAME_PANEL_TIME ? 1 : 2);
2773 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 :
2774 nr == GAME_PANEL_INVENTORY_COUNT ||
2775 nr == GAME_PANEL_TIME ? 3 : 5);
2776 int size2 = size1 + size_add;
2777 int font1 = pos->font;
2778 int font2 = pos->font_alt;
2780 size = (value < value_change ? size1 : size2);
2781 font = (value < value_change ? font1 : font2);
2785 // correct text size if "digits" is zero or less
2787 size = strlen(int2str(value, size));
2789 // dynamically correct text alignment
2790 pos->width = size * getFontWidth(font);
2792 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2793 int2str(value, size), font, mask_mode);
2795 else if (type == TYPE_ELEMENT)
2797 int element, graphic;
2801 int dst_x = PANEL_XPOS(pos);
2802 int dst_y = PANEL_YPOS(pos);
2804 if (value != EL_UNDEFINED && value != EL_EMPTY)
2807 graphic = el2panelimg(value);
2810 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2811 element, EL_NAME(element), size);
2814 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2817 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2820 width = graphic_info[graphic].width * size / TILESIZE;
2821 height = graphic_info[graphic].height * size / TILESIZE;
2824 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2827 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2831 else if (type == TYPE_GRAPHIC)
2833 int graphic = gpc->graphic;
2834 int graphic_active = gpc->graphic_active;
2838 int dst_x = PANEL_XPOS(pos);
2839 int dst_y = PANEL_YPOS(pos);
2840 boolean skip = (pos->class == get_hash_from_string("mm_engine_only") &&
2841 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2843 if (graphic != IMG_UNDEFINED && !skip)
2845 if (pos->style == STYLE_REVERSE)
2846 value = 100 - value;
2848 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2850 if (pos->direction & MV_HORIZONTAL)
2852 width = graphic_info[graphic_active].width * value / 100;
2853 height = graphic_info[graphic_active].height;
2855 if (pos->direction == MV_LEFT)
2857 src_x += graphic_info[graphic_active].width - width;
2858 dst_x += graphic_info[graphic_active].width - width;
2863 width = graphic_info[graphic_active].width;
2864 height = graphic_info[graphic_active].height * value / 100;
2866 if (pos->direction == MV_UP)
2868 src_y += graphic_info[graphic_active].height - height;
2869 dst_y += graphic_info[graphic_active].height - height;
2874 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2877 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2880 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2882 if (pos->direction & MV_HORIZONTAL)
2884 if (pos->direction == MV_RIGHT)
2891 dst_x = PANEL_XPOS(pos);
2894 width = graphic_info[graphic].width - width;
2898 if (pos->direction == MV_DOWN)
2905 dst_y = PANEL_YPOS(pos);
2908 height = graphic_info[graphic].height - height;
2912 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2915 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2919 else if (type == TYPE_STRING)
2921 boolean active = (value != 0);
2922 char *state_normal = "off";
2923 char *state_active = "on";
2924 char *state = (active ? state_active : state_normal);
2925 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2926 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2927 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2928 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2930 if (nr == GAME_PANEL_GRAVITY_STATE)
2932 int font1 = pos->font; // (used for normal state)
2933 int font2 = pos->font_alt; // (used for active state)
2935 font = (active ? font2 : font1);
2944 // don't truncate output if "chars" is zero or less
2947 // dynamically correct text alignment
2948 pos->width = size * getFontWidth(font);
2951 s_cut = getStringCopyN(s, size);
2953 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2954 s_cut, font, mask_mode);
2960 redraw_mask |= REDRAW_DOOR_1;
2963 SetGameStatus(GAME_MODE_PLAYING);
2966 void UpdateAndDisplayGameControlValues(void)
2968 if (tape.deactivate_display)
2971 UpdateGameControlValues();
2972 DisplayGameControlValues();
2975 void UpdateGameDoorValues(void)
2977 UpdateGameControlValues();
2980 void DrawGameDoorValues(void)
2982 DisplayGameControlValues();
2986 // ============================================================================
2988 // ----------------------------------------------------------------------------
2989 // initialize game engine due to level / tape version number
2990 // ============================================================================
2992 static void InitGameEngine(void)
2994 int i, j, k, l, x, y;
2996 // set game engine from tape file when re-playing, else from level file
2997 game.engine_version = (tape.playing ? tape.engine_version :
2998 level.game_version);
3000 // set single or multi-player game mode (needed for re-playing tapes)
3001 game.team_mode = setup.team_mode;
3005 int num_players = 0;
3007 for (i = 0; i < MAX_PLAYERS; i++)
3008 if (tape.player_participates[i])
3011 // multi-player tapes contain input data for more than one player
3012 game.team_mode = (num_players > 1);
3016 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
3017 level.game_version);
3018 Debug("game:init:level", " tape.file_version == %06d",
3020 Debug("game:init:level", " tape.game_version == %06d",
3022 Debug("game:init:level", " tape.engine_version == %06d",
3023 tape.engine_version);
3024 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
3025 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
3028 // --------------------------------------------------------------------------
3029 // set flags for bugs and changes according to active game engine version
3030 // --------------------------------------------------------------------------
3034 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
3036 Bug was introduced in version:
3039 Bug was fixed in version:
3043 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
3044 but the property "can fall" was missing, which caused some levels to be
3045 unsolvable. This was fixed in version 4.2.0.0.
3047 Affected levels/tapes:
3048 An example for a tape that was fixed by this bugfix is tape 029 from the
3049 level set "rnd_sam_bateman".
3050 The wrong behaviour will still be used for all levels or tapes that were
3051 created/recorded with it. An example for this is tape 023 from the level
3052 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
3055 boolean use_amoeba_dropping_cannot_fall_bug =
3056 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
3057 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
3059 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3060 tape.game_version < VERSION_IDENT(4,2,0,0)));
3063 Summary of bugfix/change:
3064 Fixed move speed of elements entering or leaving magic wall.
3066 Fixed/changed in version:
3070 Before 2.0.1, move speed of elements entering or leaving magic wall was
3071 twice as fast as it is now.
3072 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
3074 Affected levels/tapes:
3075 The first condition is generally needed for all levels/tapes before version
3076 2.0.1, which might use the old behaviour before it was changed; known tapes
3077 that are affected: Tape 014 from the level set "rnd_conor_mancone".
3078 The second condition is an exception from the above case and is needed for
3079 the special case of tapes recorded with game (not engine!) version 2.0.1 or
3080 above, but before it was known that this change would break tapes like the
3081 above and was fixed in 4.2.0.0, so that the changed behaviour was active
3082 although the engine version while recording maybe was before 2.0.1. There
3083 are a lot of tapes that are affected by this exception, like tape 006 from
3084 the level set "rnd_conor_mancone".
3087 boolean use_old_move_stepsize_for_magic_wall =
3088 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
3090 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3091 tape.game_version < VERSION_IDENT(4,2,0,0)));
3094 Summary of bugfix/change:
3095 Fixed handling for custom elements that change when pushed by the player.
3097 Fixed/changed in version:
3101 Before 3.1.0, custom elements that "change when pushing" changed directly
3102 after the player started pushing them (until then handled in "DigField()").
3103 Since 3.1.0, these custom elements are not changed until the "pushing"
3104 move of the element is finished (now handled in "ContinueMoving()").
3106 Affected levels/tapes:
3107 The first condition is generally needed for all levels/tapes before version
3108 3.1.0, which might use the old behaviour before it was changed; known tapes
3109 that are affected are some tapes from the level set "Walpurgis Gardens" by
3111 The second condition is an exception from the above case and is needed for
3112 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3113 above (including some development versions of 3.1.0), but before it was
3114 known that this change would break tapes like the above and was fixed in
3115 3.1.1, so that the changed behaviour was active although the engine version
3116 while recording maybe was before 3.1.0. There is at least one tape that is
3117 affected by this exception, which is the tape for the one-level set "Bug
3118 Machine" by Juergen Bonhagen.
3121 game.use_change_when_pushing_bug =
3122 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3124 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3125 tape.game_version < VERSION_IDENT(3,1,1,0)));
3128 Summary of bugfix/change:
3129 Fixed handling for blocking the field the player leaves when moving.
3131 Fixed/changed in version:
3135 Before 3.1.1, when "block last field when moving" was enabled, the field
3136 the player is leaving when moving was blocked for the time of the move,
3137 and was directly unblocked afterwards. This resulted in the last field
3138 being blocked for exactly one less than the number of frames of one player
3139 move. Additionally, even when blocking was disabled, the last field was
3140 blocked for exactly one frame.
3141 Since 3.1.1, due to changes in player movement handling, the last field
3142 is not blocked at all when blocking is disabled. When blocking is enabled,
3143 the last field is blocked for exactly the number of frames of one player
3144 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3145 last field is blocked for exactly one more than the number of frames of
3148 Affected levels/tapes:
3149 (!!! yet to be determined -- probably many !!!)
3152 game.use_block_last_field_bug =
3153 (game.engine_version < VERSION_IDENT(3,1,1,0));
3155 /* various special flags and settings for native Emerald Mine game engine */
3157 game_em.use_single_button =
3158 (game.engine_version > VERSION_IDENT(4,0,0,2));
3160 game_em.use_push_delay =
3161 (game.engine_version > VERSION_IDENT(4,3,7,1));
3163 game_em.use_snap_key_bug =
3164 (game.engine_version < VERSION_IDENT(4,0,1,0));
3166 game_em.use_random_bug =
3167 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3169 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3171 game_em.use_old_explosions = use_old_em_engine;
3172 game_em.use_old_android = use_old_em_engine;
3173 game_em.use_old_push_elements = use_old_em_engine;
3174 game_em.use_old_push_into_acid = use_old_em_engine;
3176 game_em.use_wrap_around = !use_old_em_engine;
3178 // --------------------------------------------------------------------------
3180 // set maximal allowed number of custom element changes per game frame
3181 game.max_num_changes_per_frame = 1;
3183 // default scan direction: scan playfield from top/left to bottom/right
3184 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3186 // dynamically adjust element properties according to game engine version
3187 InitElementPropertiesEngine(game.engine_version);
3189 // ---------- initialize special element properties -------------------------
3191 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3192 if (use_amoeba_dropping_cannot_fall_bug)
3193 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3195 // ---------- initialize player's initial move delay ------------------------
3197 // dynamically adjust player properties according to level information
3198 for (i = 0; i < MAX_PLAYERS; i++)
3199 game.initial_move_delay_value[i] =
3200 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3202 // dynamically adjust player properties according to game engine version
3203 for (i = 0; i < MAX_PLAYERS; i++)
3204 game.initial_move_delay[i] =
3205 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3206 game.initial_move_delay_value[i] : 0);
3208 // ---------- initialize player's initial push delay ------------------------
3210 // dynamically adjust player properties according to game engine version
3211 game.initial_push_delay_value =
3212 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3214 // ---------- initialize changing elements ----------------------------------
3216 // initialize changing elements information
3217 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3219 struct ElementInfo *ei = &element_info[i];
3221 // this pointer might have been changed in the level editor
3222 ei->change = &ei->change_page[0];
3224 if (!IS_CUSTOM_ELEMENT(i))
3226 ei->change->target_element = EL_EMPTY_SPACE;
3227 ei->change->delay_fixed = 0;
3228 ei->change->delay_random = 0;
3229 ei->change->delay_frames = 1;
3232 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3234 ei->has_change_event[j] = FALSE;
3236 ei->event_page_nr[j] = 0;
3237 ei->event_page[j] = &ei->change_page[0];
3241 // add changing elements from pre-defined list
3242 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3244 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3245 struct ElementInfo *ei = &element_info[ch_delay->element];
3247 ei->change->target_element = ch_delay->target_element;
3248 ei->change->delay_fixed = ch_delay->change_delay;
3250 ei->change->pre_change_function = ch_delay->pre_change_function;
3251 ei->change->change_function = ch_delay->change_function;
3252 ei->change->post_change_function = ch_delay->post_change_function;
3254 ei->change->can_change = TRUE;
3255 ei->change->can_change_or_has_action = TRUE;
3257 ei->has_change_event[CE_DELAY] = TRUE;
3259 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3260 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3263 // ---------- initialize if element can trigger global animations -----------
3265 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3267 struct ElementInfo *ei = &element_info[i];
3269 ei->has_anim_event = FALSE;
3272 InitGlobalAnimEventsForCustomElements();
3274 // ---------- initialize internal run-time variables ------------------------
3276 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3278 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3280 for (j = 0; j < ei->num_change_pages; j++)
3282 ei->change_page[j].can_change_or_has_action =
3283 (ei->change_page[j].can_change |
3284 ei->change_page[j].has_action);
3288 // add change events from custom element configuration
3289 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3291 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3293 for (j = 0; j < ei->num_change_pages; j++)
3295 if (!ei->change_page[j].can_change_or_has_action)
3298 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3300 // only add event page for the first page found with this event
3301 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3303 ei->has_change_event[k] = TRUE;
3305 ei->event_page_nr[k] = j;
3306 ei->event_page[k] = &ei->change_page[j];
3312 // ---------- initialize reference elements in change conditions ------------
3314 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3316 int element = EL_CUSTOM_START + i;
3317 struct ElementInfo *ei = &element_info[element];
3319 for (j = 0; j < ei->num_change_pages; j++)
3321 int trigger_element = ei->change_page[j].initial_trigger_element;
3323 if (trigger_element >= EL_PREV_CE_8 &&
3324 trigger_element <= EL_NEXT_CE_8)
3325 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3327 ei->change_page[j].trigger_element = trigger_element;
3331 // ---------- initialize run-time trigger player and element ----------------
3333 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3335 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3337 for (j = 0; j < ei->num_change_pages; j++)
3339 struct ElementChangeInfo *change = &ei->change_page[j];
3341 change->actual_trigger_element = EL_EMPTY;
3342 change->actual_trigger_player = EL_EMPTY;
3343 change->actual_trigger_player_bits = CH_PLAYER_NONE;
3344 change->actual_trigger_side = CH_SIDE_NONE;
3345 change->actual_trigger_ce_value = 0;
3346 change->actual_trigger_ce_score = 0;
3347 change->actual_trigger_x = -1;
3348 change->actual_trigger_y = -1;
3352 // ---------- initialize trigger events -------------------------------------
3354 // initialize trigger events information
3355 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3356 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3357 trigger_events[i][j] = FALSE;
3359 // add trigger events from element change event properties
3360 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3362 struct ElementInfo *ei = &element_info[i];
3364 for (j = 0; j < ei->num_change_pages; j++)
3366 struct ElementChangeInfo *change = &ei->change_page[j];
3368 if (!change->can_change_or_has_action)
3371 if (change->has_event[CE_BY_OTHER_ACTION])
3373 int trigger_element = change->trigger_element;
3375 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3377 if (change->has_event[k])
3379 if (IS_GROUP_ELEMENT(trigger_element))
3381 struct ElementGroupInfo *group =
3382 element_info[trigger_element].group;
3384 for (l = 0; l < group->num_elements_resolved; l++)
3385 trigger_events[group->element_resolved[l]][k] = TRUE;
3387 else if (trigger_element == EL_ANY_ELEMENT)
3388 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3389 trigger_events[l][k] = TRUE;
3391 trigger_events[trigger_element][k] = TRUE;
3398 // ---------- initialize push delay -----------------------------------------
3400 // initialize push delay values to default
3401 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3403 if (!IS_CUSTOM_ELEMENT(i))
3405 // set default push delay values (corrected since version 3.0.7-1)
3406 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3408 element_info[i].push_delay_fixed = 2;
3409 element_info[i].push_delay_random = 8;
3413 element_info[i].push_delay_fixed = 8;
3414 element_info[i].push_delay_random = 8;
3419 // set push delay value for certain elements from pre-defined list
3420 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3422 int e = push_delay_list[i].element;
3424 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3425 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3428 // set push delay value for Supaplex elements for newer engine versions
3429 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3431 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3433 if (IS_SP_ELEMENT(i))
3435 // set SP push delay to just enough to push under a falling zonk
3436 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3438 element_info[i].push_delay_fixed = delay;
3439 element_info[i].push_delay_random = 0;
3444 // ---------- initialize move stepsize --------------------------------------
3446 // initialize move stepsize values to default
3447 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3448 if (!IS_CUSTOM_ELEMENT(i))
3449 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3451 // set move stepsize value for certain elements from pre-defined list
3452 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3454 int e = move_stepsize_list[i].element;
3456 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3458 // set move stepsize value for certain elements for older engine versions
3459 if (use_old_move_stepsize_for_magic_wall)
3461 if (e == EL_MAGIC_WALL_FILLING ||
3462 e == EL_MAGIC_WALL_EMPTYING ||
3463 e == EL_BD_MAGIC_WALL_FILLING ||
3464 e == EL_BD_MAGIC_WALL_EMPTYING)
3465 element_info[e].move_stepsize *= 2;
3469 // ---------- initialize collect score --------------------------------------
3471 // initialize collect score values for custom elements from initial value
3472 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3473 if (IS_CUSTOM_ELEMENT(i))
3474 element_info[i].collect_score = element_info[i].collect_score_initial;
3476 // ---------- initialize collect count --------------------------------------
3478 // initialize collect count values for non-custom elements
3479 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3480 if (!IS_CUSTOM_ELEMENT(i))
3481 element_info[i].collect_count_initial = 0;
3483 // add collect count values for all elements from pre-defined list
3484 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3485 element_info[collect_count_list[i].element].collect_count_initial =
3486 collect_count_list[i].count;
3488 // ---------- initialize access direction -----------------------------------
3490 // initialize access direction values to default (access from every side)
3491 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3492 if (!IS_CUSTOM_ELEMENT(i))
3493 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3495 // set access direction value for certain elements from pre-defined list
3496 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3497 element_info[access_direction_list[i].element].access_direction =
3498 access_direction_list[i].direction;
3500 // ---------- initialize explosion content ----------------------------------
3501 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3503 if (IS_CUSTOM_ELEMENT(i))
3506 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3508 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3510 element_info[i].content.e[x][y] =
3511 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3512 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3513 i == EL_PLAYER_3 ? EL_EMERALD :
3514 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3515 i == EL_MOLE ? EL_EMERALD_RED :
3516 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3517 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3518 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3519 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3520 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3521 i == EL_WALL_EMERALD ? EL_EMERALD :
3522 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3523 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3524 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3525 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3526 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3527 i == EL_WALL_PEARL ? EL_PEARL :
3528 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3533 // ---------- initialize recursion detection --------------------------------
3534 recursion_loop_depth = 0;
3535 recursion_loop_detected = FALSE;
3536 recursion_loop_element = EL_UNDEFINED;
3538 // ---------- initialize graphics engine ------------------------------------
3539 game.scroll_delay_value =
3540 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3541 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3542 !setup.forced_scroll_delay ? 0 :
3543 setup.scroll_delay ? setup.scroll_delay_value : 0);
3544 if (game.forced_scroll_delay_value == -1)
3545 game.scroll_delay_value =
3546 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3548 // ---------- initialize game engine snapshots ------------------------------
3549 for (i = 0; i < MAX_PLAYERS; i++)
3550 game.snapshot.last_action[i] = 0;
3551 game.snapshot.changed_action = FALSE;
3552 game.snapshot.collected_item = FALSE;
3553 game.snapshot.mode =
3554 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3555 SNAPSHOT_MODE_EVERY_STEP :
3556 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3557 SNAPSHOT_MODE_EVERY_MOVE :
3558 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3559 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3560 game.snapshot.save_snapshot = FALSE;
3562 // ---------- initialize level time for Supaplex engine ---------------------
3563 // Supaplex levels with time limit currently unsupported -- should be added
3564 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3567 // ---------- initialize flags for handling game actions --------------------
3569 // set flags for game actions to default values
3570 game.use_key_actions = TRUE;
3571 game.use_mouse_actions = FALSE;
3573 // when using Mirror Magic game engine, handle mouse events only
3574 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3576 game.use_key_actions = FALSE;
3577 game.use_mouse_actions = TRUE;
3580 // check for custom elements with mouse click events
3581 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3583 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3585 int element = EL_CUSTOM_START + i;
3587 if (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3588 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3589 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3590 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3591 game.use_mouse_actions = TRUE;
3596 static int get_num_special_action(int element, int action_first,
3599 int num_special_action = 0;
3602 for (i = action_first; i <= action_last; i++)
3604 boolean found = FALSE;
3606 for (j = 0; j < NUM_DIRECTIONS; j++)
3607 if (el_act_dir2img(element, i, j) !=
3608 el_act_dir2img(element, ACTION_DEFAULT, j))
3612 num_special_action++;
3617 return num_special_action;
3621 // ============================================================================
3623 // ----------------------------------------------------------------------------
3624 // initialize and start new game
3625 // ============================================================================
3627 #if DEBUG_INIT_PLAYER
3628 static void DebugPrintPlayerStatus(char *message)
3635 Debug("game:init:player", "%s:", message);
3637 for (i = 0; i < MAX_PLAYERS; i++)
3639 struct PlayerInfo *player = &stored_player[i];
3641 Debug("game:init:player",
3642 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3646 player->connected_locally,
3647 player->connected_network,
3649 (local_player == player ? " (local player)" : ""));
3656 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3657 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3658 int fade_mask = REDRAW_FIELD;
3659 boolean restarting = (game_status == GAME_MODE_PLAYING);
3660 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3661 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3662 int initial_move_dir = MV_DOWN;
3665 // required here to update video display before fading (FIX THIS)
3666 DrawMaskedBorder(REDRAW_DOOR_2);
3668 if (!game.restart_level)
3669 CloseDoor(DOOR_CLOSE_1);
3673 // force fading out global animations displayed during game play
3674 SetGameStatus(GAME_MODE_PSEUDO_RESTARTING);
3678 SetGameStatus(GAME_MODE_PLAYING);
3681 if (level_editor_test_game)
3682 FadeSkipNextFadeOut();
3684 FadeSetEnterScreen();
3687 fade_mask = REDRAW_ALL;
3689 FadeLevelSoundsAndMusic();
3691 ExpireSoundLoops(TRUE);
3697 // force restarting global animations displayed during game play
3698 RestartGlobalAnimsByStatus(GAME_MODE_PSEUDO_RESTARTING);
3700 // this is required for "transforming" fade modes like cross-fading
3701 // (else global animations will be stopped, but not restarted here)
3702 SetAnimStatusBeforeFading(GAME_MODE_PSEUDO_RESTARTING);
3704 SetGameStatus(GAME_MODE_PLAYING);
3707 if (level_editor_test_game)
3708 FadeSkipNextFadeIn();
3710 // needed if different viewport properties defined for playing
3711 ChangeViewportPropertiesIfNeeded();
3715 DrawCompleteVideoDisplay();
3717 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3720 InitGameControlValues();
3724 // initialize tape actions from game when recording tape
3725 tape.use_key_actions = game.use_key_actions;
3726 tape.use_mouse_actions = game.use_mouse_actions;
3728 // initialize visible playfield size when recording tape (for team mode)
3729 tape.scr_fieldx = SCR_FIELDX;
3730 tape.scr_fieldy = SCR_FIELDY;
3733 // don't play tapes over network
3734 network_playing = (network.enabled && !tape.playing);
3736 for (i = 0; i < MAX_PLAYERS; i++)
3738 struct PlayerInfo *player = &stored_player[i];
3740 player->index_nr = i;
3741 player->index_bit = (1 << i);
3742 player->element_nr = EL_PLAYER_1 + i;
3744 player->present = FALSE;
3745 player->active = FALSE;
3746 player->mapped = FALSE;
3748 player->killed = FALSE;
3749 player->reanimated = FALSE;
3750 player->buried = FALSE;
3753 player->effective_action = 0;
3754 player->programmed_action = 0;
3755 player->snap_action = 0;
3757 player->mouse_action.lx = 0;
3758 player->mouse_action.ly = 0;
3759 player->mouse_action.button = 0;
3760 player->mouse_action.button_hint = 0;
3762 player->effective_mouse_action.lx = 0;
3763 player->effective_mouse_action.ly = 0;
3764 player->effective_mouse_action.button = 0;
3765 player->effective_mouse_action.button_hint = 0;
3767 for (j = 0; j < MAX_NUM_KEYS; j++)
3768 player->key[j] = FALSE;
3770 player->num_white_keys = 0;
3772 player->dynabomb_count = 0;
3773 player->dynabomb_size = 1;
3774 player->dynabombs_left = 0;
3775 player->dynabomb_xl = FALSE;
3777 player->MovDir = initial_move_dir;
3780 player->GfxDir = initial_move_dir;
3781 player->GfxAction = ACTION_DEFAULT;
3783 player->StepFrame = 0;
3785 player->initial_element = player->element_nr;
3786 player->artwork_element =
3787 (level.use_artwork_element[i] ? level.artwork_element[i] :
3788 player->element_nr);
3789 player->use_murphy = FALSE;
3791 player->block_last_field = FALSE; // initialized in InitPlayerField()
3792 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3794 player->gravity = level.initial_player_gravity[i];
3796 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3798 player->actual_frame_counter.count = 0;
3799 player->actual_frame_counter.value = 1;
3801 player->step_counter = 0;
3803 player->last_move_dir = initial_move_dir;
3805 player->is_active = FALSE;
3807 player->is_waiting = FALSE;
3808 player->is_moving = FALSE;
3809 player->is_auto_moving = FALSE;
3810 player->is_digging = FALSE;
3811 player->is_snapping = FALSE;
3812 player->is_collecting = FALSE;
3813 player->is_pushing = FALSE;
3814 player->is_switching = FALSE;
3815 player->is_dropping = FALSE;
3816 player->is_dropping_pressed = FALSE;
3818 player->is_bored = FALSE;
3819 player->is_sleeping = FALSE;
3821 player->was_waiting = TRUE;
3822 player->was_moving = FALSE;
3823 player->was_snapping = FALSE;
3824 player->was_dropping = FALSE;
3826 player->force_dropping = FALSE;
3828 player->frame_counter_bored = -1;
3829 player->frame_counter_sleeping = -1;
3831 player->anim_delay_counter = 0;
3832 player->post_delay_counter = 0;
3834 player->dir_waiting = initial_move_dir;
3835 player->action_waiting = ACTION_DEFAULT;
3836 player->last_action_waiting = ACTION_DEFAULT;
3837 player->special_action_bored = ACTION_DEFAULT;
3838 player->special_action_sleeping = ACTION_DEFAULT;
3840 player->switch_x = -1;
3841 player->switch_y = -1;
3843 player->drop_x = -1;
3844 player->drop_y = -1;
3846 player->show_envelope = 0;
3848 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3850 player->push_delay = -1; // initialized when pushing starts
3851 player->push_delay_value = game.initial_push_delay_value;
3853 player->drop_delay = 0;
3854 player->drop_pressed_delay = 0;
3856 player->last_jx = -1;
3857 player->last_jy = -1;
3861 player->shield_normal_time_left = 0;
3862 player->shield_deadly_time_left = 0;
3864 player->last_removed_element = EL_UNDEFINED;
3866 player->inventory_infinite_element = EL_UNDEFINED;
3867 player->inventory_size = 0;
3869 if (level.use_initial_inventory[i])
3871 for (j = 0; j < level.initial_inventory_size[i]; j++)
3873 int element = level.initial_inventory_content[i][j];
3874 int collect_count = element_info[element].collect_count_initial;
3877 if (!IS_CUSTOM_ELEMENT(element))
3880 if (collect_count == 0)
3881 player->inventory_infinite_element = element;
3883 for (k = 0; k < collect_count; k++)
3884 if (player->inventory_size < MAX_INVENTORY_SIZE)
3885 player->inventory_element[player->inventory_size++] = element;
3889 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3890 SnapField(player, 0, 0);
3892 map_player_action[i] = i;
3895 network_player_action_received = FALSE;
3897 // initial null action
3898 if (network_playing)
3899 SendToServer_MovePlayer(MV_NONE);
3904 TimeLeft = level.time;
3909 ScreenMovDir = MV_NONE;
3913 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3915 game.robot_wheel_x = -1;
3916 game.robot_wheel_y = -1;
3921 game.all_players_gone = FALSE;
3923 game.LevelSolved = FALSE;
3924 game.GameOver = FALSE;
3926 game.GamePlayed = !tape.playing;
3928 game.LevelSolved_GameWon = FALSE;
3929 game.LevelSolved_GameEnd = FALSE;
3930 game.LevelSolved_SaveTape = FALSE;
3931 game.LevelSolved_SaveScore = FALSE;
3933 game.LevelSolved_CountingTime = 0;
3934 game.LevelSolved_CountingScore = 0;
3935 game.LevelSolved_CountingHealth = 0;
3937 game.RestartGameRequested = FALSE;
3939 game.panel.active = TRUE;
3941 game.no_level_time_limit = (level.time == 0);
3942 game.time_limit = (leveldir_current->time_limit && setup.time_limit);
3944 game.yamyam_content_nr = 0;
3945 game.robot_wheel_active = FALSE;
3946 game.magic_wall_active = FALSE;
3947 game.magic_wall_time_left = 0;
3948 game.light_time_left = 0;
3949 game.timegate_time_left = 0;
3950 game.switchgate_pos = 0;
3951 game.wind_direction = level.wind_direction_initial;
3953 game.time_final = 0;
3954 game.score_time_final = 0;
3957 game.score_final = 0;
3959 game.health = MAX_HEALTH;
3960 game.health_final = MAX_HEALTH;
3962 game.gems_still_needed = level.gems_needed;
3963 game.sokoban_fields_still_needed = 0;
3964 game.sokoban_objects_still_needed = 0;
3965 game.lights_still_needed = 0;
3966 game.players_still_needed = 0;
3967 game.friends_still_needed = 0;
3969 game.lenses_time_left = 0;
3970 game.magnify_time_left = 0;
3972 game.ball_active = level.ball_active_initial;
3973 game.ball_content_nr = 0;
3975 game.explosions_delayed = TRUE;
3977 // special case: set custom artwork setting to initial value
3978 game.use_masked_elements = game.use_masked_elements_initial;
3980 for (i = 0; i < NUM_BELTS; i++)
3982 game.belt_dir[i] = MV_NONE;
3983 game.belt_dir_nr[i] = 3; // not moving, next moving left
3986 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3987 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3989 #if DEBUG_INIT_PLAYER
3990 DebugPrintPlayerStatus("Player status at level initialization");
3993 SCAN_PLAYFIELD(x, y)
3995 Tile[x][y] = Last[x][y] = level.field[x][y];
3996 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3997 ChangeDelay[x][y] = 0;
3998 ChangePage[x][y] = -1;
3999 CustomValue[x][y] = 0; // initialized in InitField()
4000 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
4002 WasJustMoving[x][y] = 0;
4003 WasJustFalling[x][y] = 0;
4004 CheckCollision[x][y] = 0;
4005 CheckImpact[x][y] = 0;
4007 Pushed[x][y] = FALSE;
4009 ChangeCount[x][y] = 0;
4010 ChangeEvent[x][y] = -1;
4012 ExplodePhase[x][y] = 0;
4013 ExplodeDelay[x][y] = 0;
4014 ExplodeField[x][y] = EX_TYPE_NONE;
4016 RunnerVisit[x][y] = 0;
4017 PlayerVisit[x][y] = 0;
4020 GfxRandom[x][y] = INIT_GFX_RANDOM();
4021 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
4022 GfxElement[x][y] = EL_UNDEFINED;
4023 GfxElementEmpty[x][y] = EL_EMPTY;
4024 GfxAction[x][y] = ACTION_DEFAULT;
4025 GfxDir[x][y] = MV_NONE;
4026 GfxRedraw[x][y] = GFX_REDRAW_NONE;
4029 SCAN_PLAYFIELD(x, y)
4031 InitFieldForEngine(x, y);
4033 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
4035 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
4038 InitField(x, y, TRUE);
4040 ResetGfxAnimation(x, y);
4045 // required if level does not contain any "empty space" element
4046 if (element_info[EL_EMPTY].use_gfx_element)
4047 game.use_masked_elements = TRUE;
4049 for (i = 0; i < MAX_PLAYERS; i++)
4051 struct PlayerInfo *player = &stored_player[i];
4053 // set number of special actions for bored and sleeping animation
4054 player->num_special_action_bored =
4055 get_num_special_action(player->artwork_element,
4056 ACTION_BORING_1, ACTION_BORING_LAST);
4057 player->num_special_action_sleeping =
4058 get_num_special_action(player->artwork_element,
4059 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
4062 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
4063 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
4065 // initialize type of slippery elements
4066 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4068 if (!IS_CUSTOM_ELEMENT(i))
4070 // default: elements slip down either to the left or right randomly
4071 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
4073 // SP style elements prefer to slip down on the left side
4074 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
4075 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4077 // BD style elements prefer to slip down on the left side
4078 if (game.emulation == EMU_BOULDERDASH)
4079 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4083 // initialize explosion and ignition delay
4084 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4086 if (!IS_CUSTOM_ELEMENT(i))
4089 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
4090 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
4091 game.emulation == EMU_SUPAPLEX ? 3 : 2);
4092 int last_phase = (num_phase + 1) * delay;
4093 int half_phase = (num_phase / 2) * delay;
4095 element_info[i].explosion_delay = last_phase - 1;
4096 element_info[i].ignition_delay = half_phase;
4098 if (i == EL_BLACK_ORB)
4099 element_info[i].ignition_delay = 1;
4103 // correct non-moving belts to start moving left
4104 for (i = 0; i < NUM_BELTS; i++)
4105 if (game.belt_dir[i] == MV_NONE)
4106 game.belt_dir_nr[i] = 3; // not moving, next moving left
4108 #if USE_NEW_PLAYER_ASSIGNMENTS
4109 // use preferred player also in local single-player mode
4110 if (!network.enabled && !game.team_mode)
4112 int new_index_nr = setup.network_player_nr;
4114 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
4116 for (i = 0; i < MAX_PLAYERS; i++)
4117 stored_player[i].connected_locally = FALSE;
4119 stored_player[new_index_nr].connected_locally = TRUE;
4123 for (i = 0; i < MAX_PLAYERS; i++)
4125 stored_player[i].connected = FALSE;
4127 // in network game mode, the local player might not be the first player
4128 if (stored_player[i].connected_locally)
4129 local_player = &stored_player[i];
4132 if (!network.enabled)
4133 local_player->connected = TRUE;
4137 for (i = 0; i < MAX_PLAYERS; i++)
4138 stored_player[i].connected = tape.player_participates[i];
4140 else if (network.enabled)
4142 // add team mode players connected over the network (needed for correct
4143 // assignment of player figures from level to locally playing players)
4145 for (i = 0; i < MAX_PLAYERS; i++)
4146 if (stored_player[i].connected_network)
4147 stored_player[i].connected = TRUE;
4149 else if (game.team_mode)
4151 // try to guess locally connected team mode players (needed for correct
4152 // assignment of player figures from level to locally playing players)
4154 for (i = 0; i < MAX_PLAYERS; i++)
4155 if (setup.input[i].use_joystick ||
4156 setup.input[i].key.left != KSYM_UNDEFINED)
4157 stored_player[i].connected = TRUE;
4160 #if DEBUG_INIT_PLAYER
4161 DebugPrintPlayerStatus("Player status after level initialization");
4164 #if DEBUG_INIT_PLAYER
4165 Debug("game:init:player", "Reassigning players ...");
4168 // check if any connected player was not found in playfield
4169 for (i = 0; i < MAX_PLAYERS; i++)
4171 struct PlayerInfo *player = &stored_player[i];
4173 if (player->connected && !player->present)
4175 struct PlayerInfo *field_player = NULL;
4177 #if DEBUG_INIT_PLAYER
4178 Debug("game:init:player",
4179 "- looking for field player for player %d ...", i + 1);
4182 // assign first free player found that is present in the playfield
4184 // first try: look for unmapped playfield player that is not connected
4185 for (j = 0; j < MAX_PLAYERS; j++)
4186 if (field_player == NULL &&
4187 stored_player[j].present &&
4188 !stored_player[j].mapped &&
4189 !stored_player[j].connected)
4190 field_player = &stored_player[j];
4192 // second try: look for *any* unmapped playfield player
4193 for (j = 0; j < MAX_PLAYERS; j++)
4194 if (field_player == NULL &&
4195 stored_player[j].present &&
4196 !stored_player[j].mapped)
4197 field_player = &stored_player[j];
4199 if (field_player != NULL)
4201 int jx = field_player->jx, jy = field_player->jy;
4203 #if DEBUG_INIT_PLAYER
4204 Debug("game:init:player", "- found player %d",
4205 field_player->index_nr + 1);
4208 player->present = FALSE;
4209 player->active = FALSE;
4211 field_player->present = TRUE;
4212 field_player->active = TRUE;
4215 player->initial_element = field_player->initial_element;
4216 player->artwork_element = field_player->artwork_element;
4218 player->block_last_field = field_player->block_last_field;
4219 player->block_delay_adjustment = field_player->block_delay_adjustment;
4222 StorePlayer[jx][jy] = field_player->element_nr;
4224 field_player->jx = field_player->last_jx = jx;
4225 field_player->jy = field_player->last_jy = jy;
4227 if (local_player == player)
4228 local_player = field_player;
4230 map_player_action[field_player->index_nr] = i;
4232 field_player->mapped = TRUE;
4234 #if DEBUG_INIT_PLAYER
4235 Debug("game:init:player", "- map_player_action[%d] == %d",
4236 field_player->index_nr + 1, i + 1);
4241 if (player->connected && player->present)
4242 player->mapped = TRUE;
4245 #if DEBUG_INIT_PLAYER
4246 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4251 // check if any connected player was not found in playfield
4252 for (i = 0; i < MAX_PLAYERS; i++)
4254 struct PlayerInfo *player = &stored_player[i];
4256 if (player->connected && !player->present)
4258 for (j = 0; j < MAX_PLAYERS; j++)
4260 struct PlayerInfo *field_player = &stored_player[j];
4261 int jx = field_player->jx, jy = field_player->jy;
4263 // assign first free player found that is present in the playfield
4264 if (field_player->present && !field_player->connected)
4266 player->present = TRUE;
4267 player->active = TRUE;
4269 field_player->present = FALSE;
4270 field_player->active = FALSE;
4272 player->initial_element = field_player->initial_element;
4273 player->artwork_element = field_player->artwork_element;
4275 player->block_last_field = field_player->block_last_field;
4276 player->block_delay_adjustment = field_player->block_delay_adjustment;
4278 StorePlayer[jx][jy] = player->element_nr;
4280 player->jx = player->last_jx = jx;
4281 player->jy = player->last_jy = jy;
4291 Debug("game:init:player", "local_player->present == %d",
4292 local_player->present);
4295 // set focus to local player for network games, else to all players
4296 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4297 game.centered_player_nr_next = game.centered_player_nr;
4298 game.set_centered_player = FALSE;
4299 game.set_centered_player_wrap = FALSE;
4301 if (network_playing && tape.recording)
4303 // store client dependent player focus when recording network games
4304 tape.centered_player_nr_next = game.centered_player_nr_next;
4305 tape.set_centered_player = TRUE;
4310 // when playing a tape, eliminate all players who do not participate
4312 #if USE_NEW_PLAYER_ASSIGNMENTS
4314 if (!game.team_mode)
4316 for (i = 0; i < MAX_PLAYERS; i++)
4318 if (stored_player[i].active &&
4319 !tape.player_participates[map_player_action[i]])
4321 struct PlayerInfo *player = &stored_player[i];
4322 int jx = player->jx, jy = player->jy;
4324 #if DEBUG_INIT_PLAYER
4325 Debug("game:init:player", "Removing player %d at (%d, %d)",
4329 player->active = FALSE;
4330 StorePlayer[jx][jy] = 0;
4331 Tile[jx][jy] = EL_EMPTY;
4338 for (i = 0; i < MAX_PLAYERS; i++)
4340 if (stored_player[i].active &&
4341 !tape.player_participates[i])
4343 struct PlayerInfo *player = &stored_player[i];
4344 int jx = player->jx, jy = player->jy;
4346 player->active = FALSE;
4347 StorePlayer[jx][jy] = 0;
4348 Tile[jx][jy] = EL_EMPTY;
4353 else if (!network.enabled && !game.team_mode) // && !tape.playing
4355 // when in single player mode, eliminate all but the local player
4357 for (i = 0; i < MAX_PLAYERS; i++)
4359 struct PlayerInfo *player = &stored_player[i];
4361 if (player->active && player != local_player)
4363 int jx = player->jx, jy = player->jy;
4365 player->active = FALSE;
4366 player->present = FALSE;
4368 StorePlayer[jx][jy] = 0;
4369 Tile[jx][jy] = EL_EMPTY;
4374 for (i = 0; i < MAX_PLAYERS; i++)
4375 if (stored_player[i].active)
4376 game.players_still_needed++;
4378 if (level.solved_by_one_player)
4379 game.players_still_needed = 1;
4381 // when recording the game, store which players take part in the game
4384 #if USE_NEW_PLAYER_ASSIGNMENTS
4385 for (i = 0; i < MAX_PLAYERS; i++)
4386 if (stored_player[i].connected)
4387 tape.player_participates[i] = TRUE;
4389 for (i = 0; i < MAX_PLAYERS; i++)
4390 if (stored_player[i].active)
4391 tape.player_participates[i] = TRUE;
4395 #if DEBUG_INIT_PLAYER
4396 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4399 if (BorderElement == EL_EMPTY)
4402 SBX_Right = lev_fieldx - SCR_FIELDX;
4404 SBY_Lower = lev_fieldy - SCR_FIELDY;
4409 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4411 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4414 if (full_lev_fieldx <= SCR_FIELDX)
4415 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4416 if (full_lev_fieldy <= SCR_FIELDY)
4417 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4419 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4421 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4424 // if local player not found, look for custom element that might create
4425 // the player (make some assumptions about the right custom element)
4426 if (!local_player->present)
4428 int start_x = 0, start_y = 0;
4429 int found_rating = 0;
4430 int found_element = EL_UNDEFINED;
4431 int player_nr = local_player->index_nr;
4433 SCAN_PLAYFIELD(x, y)
4435 int element = Tile[x][y];
4440 if (level.use_start_element[player_nr] &&
4441 level.start_element[player_nr] == element &&
4448 found_element = element;
4451 if (!IS_CUSTOM_ELEMENT(element))
4454 if (CAN_CHANGE(element))
4456 for (i = 0; i < element_info[element].num_change_pages; i++)
4458 // check for player created from custom element as single target
4459 content = element_info[element].change_page[i].target_element;
4460 is_player = IS_PLAYER_ELEMENT(content);
4462 if (is_player && (found_rating < 3 ||
4463 (found_rating == 3 && element < found_element)))
4469 found_element = element;
4474 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4476 // check for player created from custom element as explosion content
4477 content = element_info[element].content.e[xx][yy];
4478 is_player = IS_PLAYER_ELEMENT(content);
4480 if (is_player && (found_rating < 2 ||
4481 (found_rating == 2 && element < found_element)))
4483 start_x = x + xx - 1;
4484 start_y = y + yy - 1;
4487 found_element = element;
4490 if (!CAN_CHANGE(element))
4493 for (i = 0; i < element_info[element].num_change_pages; i++)
4495 // check for player created from custom element as extended target
4497 element_info[element].change_page[i].target_content.e[xx][yy];
4499 is_player = IS_PLAYER_ELEMENT(content);
4501 if (is_player && (found_rating < 1 ||
4502 (found_rating == 1 && element < found_element)))
4504 start_x = x + xx - 1;
4505 start_y = y + yy - 1;
4508 found_element = element;
4514 scroll_x = SCROLL_POSITION_X(start_x);
4515 scroll_y = SCROLL_POSITION_Y(start_y);
4519 scroll_x = SCROLL_POSITION_X(local_player->jx);
4520 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4523 if (game.forced_scroll_x != ARG_UNDEFINED_VALUE)
4524 scroll_x = game.forced_scroll_x;
4525 if (game.forced_scroll_y != ARG_UNDEFINED_VALUE)
4526 scroll_y = game.forced_scroll_y;
4528 // !!! FIX THIS (START) !!!
4529 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
4531 InitGameEngine_BD();
4533 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4535 InitGameEngine_EM();
4537 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4539 InitGameEngine_SP();
4541 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4543 InitGameEngine_MM();
4547 DrawLevel(REDRAW_FIELD);
4550 // after drawing the level, correct some elements
4551 if (game.timegate_time_left == 0)
4552 CloseAllOpenTimegates();
4555 // blit playfield from scroll buffer to normal back buffer for fading in
4556 BlitScreenToBitmap(backbuffer);
4557 // !!! FIX THIS (END) !!!
4559 DrawMaskedBorder(fade_mask);
4564 // full screen redraw is required at this point in the following cases:
4565 // - special editor door undrawn when game was started from level editor
4566 // - drawing area (playfield) was changed and has to be removed completely
4567 redraw_mask = REDRAW_ALL;
4571 if (!game.restart_level)
4573 // copy default game door content to main double buffer
4575 // !!! CHECK AGAIN !!!
4576 SetPanelBackground();
4577 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4578 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4581 SetPanelBackground();
4582 SetDrawBackgroundMask(REDRAW_DOOR_1);
4584 UpdateAndDisplayGameControlValues();
4586 if (!game.restart_level)
4592 CreateGameButtons();
4597 // copy actual game door content to door double buffer for OpenDoor()
4598 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4600 OpenDoor(DOOR_OPEN_ALL);
4602 KeyboardAutoRepeatOffUnlessAutoplay();
4604 #if DEBUG_INIT_PLAYER
4605 DebugPrintPlayerStatus("Player status (final)");
4614 if (!game.restart_level && !tape.playing)
4616 LevelStats_incPlayed(level_nr);
4618 SaveLevelSetup_SeriesInfo();
4621 game.restart_level = FALSE;
4622 game.request_active = FALSE;
4623 game.envelope_active = FALSE;
4625 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4626 InitGameActions_MM();
4628 SaveEngineSnapshotToListInitial();
4630 if (!game.restart_level)
4632 PlaySound(SND_GAME_STARTING);
4634 if (setup.sound_music)
4638 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4641 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4642 int actual_player_x, int actual_player_y)
4644 // this is used for non-R'n'D game engines to update certain engine values
4646 // needed to determine if sounds are played within the visible screen area
4647 scroll_x = actual_scroll_x;
4648 scroll_y = actual_scroll_y;
4650 // needed to get player position for "follow finger" playing input method
4651 local_player->jx = actual_player_x;
4652 local_player->jy = actual_player_y;
4655 void InitMovDir(int x, int y)
4657 int i, element = Tile[x][y];
4658 static int xy[4][2] =
4665 static int direction[3][4] =
4667 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4668 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4669 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4678 Tile[x][y] = EL_BUG;
4679 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4682 case EL_SPACESHIP_RIGHT:
4683 case EL_SPACESHIP_UP:
4684 case EL_SPACESHIP_LEFT:
4685 case EL_SPACESHIP_DOWN:
4686 Tile[x][y] = EL_SPACESHIP;
4687 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4690 case EL_BD_BUTTERFLY_RIGHT:
4691 case EL_BD_BUTTERFLY_UP:
4692 case EL_BD_BUTTERFLY_LEFT:
4693 case EL_BD_BUTTERFLY_DOWN:
4694 Tile[x][y] = EL_BD_BUTTERFLY;
4695 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4698 case EL_BD_FIREFLY_RIGHT:
4699 case EL_BD_FIREFLY_UP:
4700 case EL_BD_FIREFLY_LEFT:
4701 case EL_BD_FIREFLY_DOWN:
4702 Tile[x][y] = EL_BD_FIREFLY;
4703 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4706 case EL_PACMAN_RIGHT:
4708 case EL_PACMAN_LEFT:
4709 case EL_PACMAN_DOWN:
4710 Tile[x][y] = EL_PACMAN;
4711 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4714 case EL_YAMYAM_LEFT:
4715 case EL_YAMYAM_RIGHT:
4717 case EL_YAMYAM_DOWN:
4718 Tile[x][y] = EL_YAMYAM;
4719 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4722 case EL_SP_SNIKSNAK:
4723 MovDir[x][y] = MV_UP;
4726 case EL_SP_ELECTRON:
4727 MovDir[x][y] = MV_LEFT;
4734 Tile[x][y] = EL_MOLE;
4735 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4738 case EL_SPRING_LEFT:
4739 case EL_SPRING_RIGHT:
4740 Tile[x][y] = EL_SPRING;
4741 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4745 if (IS_CUSTOM_ELEMENT(element))
4747 struct ElementInfo *ei = &element_info[element];
4748 int move_direction_initial = ei->move_direction_initial;
4749 int move_pattern = ei->move_pattern;
4751 if (move_direction_initial == MV_START_PREVIOUS)
4753 if (MovDir[x][y] != MV_NONE)
4756 move_direction_initial = MV_START_AUTOMATIC;
4759 if (move_direction_initial == MV_START_RANDOM)
4760 MovDir[x][y] = 1 << RND(4);
4761 else if (move_direction_initial & MV_ANY_DIRECTION)
4762 MovDir[x][y] = move_direction_initial;
4763 else if (move_pattern == MV_ALL_DIRECTIONS ||
4764 move_pattern == MV_TURNING_LEFT ||
4765 move_pattern == MV_TURNING_RIGHT ||
4766 move_pattern == MV_TURNING_LEFT_RIGHT ||
4767 move_pattern == MV_TURNING_RIGHT_LEFT ||
4768 move_pattern == MV_TURNING_RANDOM)
4769 MovDir[x][y] = 1 << RND(4);
4770 else if (move_pattern == MV_HORIZONTAL)
4771 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4772 else if (move_pattern == MV_VERTICAL)
4773 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4774 else if (move_pattern & MV_ANY_DIRECTION)
4775 MovDir[x][y] = element_info[element].move_pattern;
4776 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4777 move_pattern == MV_ALONG_RIGHT_SIDE)
4779 // use random direction as default start direction
4780 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4781 MovDir[x][y] = 1 << RND(4);
4783 for (i = 0; i < NUM_DIRECTIONS; i++)
4785 int x1 = x + xy[i][0];
4786 int y1 = y + xy[i][1];
4788 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4790 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4791 MovDir[x][y] = direction[0][i];
4793 MovDir[x][y] = direction[1][i];
4802 MovDir[x][y] = 1 << RND(4);
4804 if (element != EL_BUG &&
4805 element != EL_SPACESHIP &&
4806 element != EL_BD_BUTTERFLY &&
4807 element != EL_BD_FIREFLY)
4810 for (i = 0; i < NUM_DIRECTIONS; i++)
4812 int x1 = x + xy[i][0];
4813 int y1 = y + xy[i][1];
4815 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4817 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4819 MovDir[x][y] = direction[0][i];
4822 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4823 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4825 MovDir[x][y] = direction[1][i];
4834 GfxDir[x][y] = MovDir[x][y];
4837 void InitAmoebaNr(int x, int y)
4840 int group_nr = AmoebaNeighbourNr(x, y);
4844 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4846 if (AmoebaCnt[i] == 0)
4854 AmoebaNr[x][y] = group_nr;
4855 AmoebaCnt[group_nr]++;
4856 AmoebaCnt2[group_nr]++;
4859 static void LevelSolved_SetFinalGameValues(void)
4861 game.time_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.time_played :
4862 game.no_level_time_limit ? TimePlayed : TimeLeft);
4863 game.score_time_final = (level.use_step_counter ? TimePlayed :
4864 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4866 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.score :
4867 level.game_engine_type == GAME_ENGINE_TYPE_EM ? game_em.lev->score :
4868 level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.score :
4871 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4872 MM_HEALTH(game_mm.laser_overload_value) :
4875 game.LevelSolved_CountingTime = game.time_final;
4876 game.LevelSolved_CountingScore = game.score_final;
4877 game.LevelSolved_CountingHealth = game.health_final;
4880 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4882 game.LevelSolved_CountingTime = time;
4883 game.LevelSolved_CountingScore = score;
4884 game.LevelSolved_CountingHealth = health;
4886 game_panel_controls[GAME_PANEL_TIME].value = time;
4887 game_panel_controls[GAME_PANEL_SCORE].value = score;
4888 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4890 DisplayGameControlValues();
4893 static void LevelSolved(void)
4895 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4896 game.players_still_needed > 0)
4899 game.LevelSolved = TRUE;
4900 game.GameOver = TRUE;
4904 // needed here to display correct panel values while player walks into exit
4905 LevelSolved_SetFinalGameValues();
4908 static boolean AdvanceToNextLevel(void)
4910 if (setup.increment_levels &&
4911 level_nr < leveldir_current->last_level &&
4914 level_nr++; // advance to next level
4915 TapeErase(); // start with empty tape
4917 if (setup.auto_play_next_level)
4919 scores.continue_playing = TRUE;
4920 scores.next_level_nr = level_nr;
4922 LoadLevel(level_nr);
4924 SaveLevelSetup_SeriesInfo();
4935 static int time_count_steps;
4936 static int time, time_final;
4937 static float score, score_final; // needed for time score < 10 for 10 seconds
4938 static int health, health_final;
4939 static int game_over_delay_1 = 0;
4940 static int game_over_delay_2 = 0;
4941 static int game_over_delay_3 = 0;
4942 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4943 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4945 if (!game.LevelSolved_GameWon)
4949 // do not start end game actions before the player stops moving (to exit)
4950 if (local_player->active && local_player->MovPos)
4953 // calculate final game values after player finished walking into exit
4954 LevelSolved_SetFinalGameValues();
4956 game.LevelSolved_GameWon = TRUE;
4957 game.LevelSolved_SaveTape = tape.recording;
4958 game.LevelSolved_SaveScore = !tape.playing;
4962 LevelStats_incSolved(level_nr);
4964 SaveLevelSetup_SeriesInfo();
4967 if (tape.auto_play) // tape might already be stopped here
4968 tape.auto_play_level_solved = TRUE;
4972 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4973 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4974 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4976 time = time_final = game.time_final;
4977 score = score_final = game.score_final;
4978 health = health_final = game.health_final;
4980 // update game panel values before (delayed) counting of score (if any)
4981 LevelSolved_DisplayFinalGameValues(time, score, health);
4983 // if level has time score defined, calculate new final game values
4986 int time_final_max = 999;
4987 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4988 int time_frames = 0;
4989 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4990 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4995 time_frames = time_frames_left;
4997 else if (game.no_level_time_limit && TimePlayed < time_final_max)
4999 time_final = time_final_max;
5000 time_frames = time_frames_final_max - time_frames_played;
5003 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
5005 time_count_steps = MAX(1, ABS(time_final - time) / 100);
5007 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
5009 // keep previous values (final values already processed here)
5011 score_final = score;
5013 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
5016 score_final += health * time_score;
5019 game.score_final = score_final;
5020 game.health_final = health_final;
5023 // if not counting score after game, immediately update game panel values
5024 if (level_editor_test_game || !setup.count_score_after_game ||
5025 level.game_engine_type == GAME_ENGINE_TYPE_BD)
5028 score = score_final;
5030 LevelSolved_DisplayFinalGameValues(time, score, health);
5033 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
5035 // check if last player has left the level
5036 if (game.exit_x >= 0 &&
5039 int x = game.exit_x;
5040 int y = game.exit_y;
5041 int element = Tile[x][y];
5043 // close exit door after last player
5044 if ((game.all_players_gone &&
5045 (element == EL_EXIT_OPEN ||
5046 element == EL_SP_EXIT_OPEN ||
5047 element == EL_STEEL_EXIT_OPEN)) ||
5048 element == EL_EM_EXIT_OPEN ||
5049 element == EL_EM_STEEL_EXIT_OPEN)
5053 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
5054 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
5055 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
5056 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
5057 EL_EM_STEEL_EXIT_CLOSING);
5059 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
5062 // player disappears
5063 DrawLevelField(x, y);
5066 for (i = 0; i < MAX_PLAYERS; i++)
5068 struct PlayerInfo *player = &stored_player[i];
5070 if (player->present)
5072 RemovePlayer(player);
5074 // player disappears
5075 DrawLevelField(player->jx, player->jy);
5080 PlaySound(SND_GAME_WINNING);
5083 if (setup.count_score_after_game)
5085 if (time != time_final)
5087 if (game_over_delay_1 > 0)
5089 game_over_delay_1--;
5094 int time_to_go = ABS(time_final - time);
5095 int time_count_dir = (time < time_final ? +1 : -1);
5097 if (time_to_go < time_count_steps)
5098 time_count_steps = 1;
5100 time += time_count_steps * time_count_dir;
5101 score += time_count_steps * time_score;
5103 // set final score to correct rounding differences after counting score
5104 if (time == time_final)
5105 score = score_final;
5107 LevelSolved_DisplayFinalGameValues(time, score, health);
5109 if (time == time_final)
5110 StopSound(SND_GAME_LEVELTIME_BONUS);
5111 else if (setup.sound_loops)
5112 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5114 PlaySound(SND_GAME_LEVELTIME_BONUS);
5119 if (health != health_final)
5121 if (game_over_delay_2 > 0)
5123 game_over_delay_2--;
5128 int health_count_dir = (health < health_final ? +1 : -1);
5130 health += health_count_dir;
5131 score += time_score;
5133 LevelSolved_DisplayFinalGameValues(time, score, health);
5135 if (health == health_final)
5136 StopSound(SND_GAME_LEVELTIME_BONUS);
5137 else if (setup.sound_loops)
5138 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5140 PlaySound(SND_GAME_LEVELTIME_BONUS);
5146 game.panel.active = FALSE;
5148 if (game_over_delay_3 > 0)
5150 game_over_delay_3--;
5160 // used instead of "level_nr" (needed for network games)
5161 int last_level_nr = levelset.level_nr;
5162 boolean tape_saved = FALSE;
5164 // Important note: This function is not only called after "GameWon()", but also after
5165 // "game over" (if automatically asking for restarting the game is disabled in setup)
5167 if (game.LevelSolved)
5168 game.LevelSolved_GameEnd = TRUE;
5170 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5172 // make sure that request dialog to save tape does not open door again
5173 if (!global.use_envelope_request)
5174 CloseDoor(DOOR_CLOSE_1);
5177 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5179 // set unique basename for score tape (also saved in high score table)
5180 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5183 // if no tape is to be saved, close both doors simultaneously
5184 CloseDoor(DOOR_CLOSE_ALL);
5186 if (level_editor_test_game || score_info_tape_play)
5188 SetGameStatus(GAME_MODE_MAIN);
5195 if (!game.GamePlayed || (!game.LevelSolved_SaveScore && !level.bd_intermission))
5197 SetGameStatus(GAME_MODE_MAIN);
5204 if (level_nr == leveldir_current->handicap_level)
5206 leveldir_current->handicap_level++;
5208 SaveLevelSetup_SeriesInfo();
5211 // save score and score tape before potentially erasing tape below
5212 if (game.LevelSolved_SaveScore)
5213 NewHighScore(last_level_nr, tape_saved);
5215 // increment and load next level (if possible and not configured otherwise)
5216 AdvanceToNextLevel();
5218 if (game.LevelSolved_SaveScore && scores.last_added >= 0 && setup.show_scores_after_game)
5220 SetGameStatus(GAME_MODE_SCORES);
5222 DrawHallOfFame(last_level_nr);
5224 else if (scores.continue_playing)
5226 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5230 SetGameStatus(GAME_MODE_MAIN);
5236 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5237 boolean one_score_entry_per_name)
5241 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5244 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5246 struct ScoreEntry *entry = &list->entry[i];
5247 boolean score_is_better = (new_entry->score > entry->score);
5248 boolean score_is_equal = (new_entry->score == entry->score);
5249 boolean time_is_better = (new_entry->time < entry->time);
5250 boolean time_is_equal = (new_entry->time == entry->time);
5251 boolean better_by_score = (score_is_better ||
5252 (score_is_equal && time_is_better));
5253 boolean better_by_time = (time_is_better ||
5254 (time_is_equal && score_is_better));
5255 boolean is_better = (level.rate_time_over_score ? better_by_time :
5257 boolean entry_is_empty = (entry->score == 0 &&
5260 // prevent adding server score entries if also existing in local score file
5261 // (special case: historic score entries have an empty tape basename entry)
5262 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5263 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5265 // add fields from server score entry not stored in local score entry
5266 // (currently, this means setting platform, version and country fields;
5267 // in rare cases, this may also correct an invalid score value, as
5268 // historic scores might have been truncated to 16-bit values locally)
5269 *entry = *new_entry;
5274 if (is_better || entry_is_empty)
5276 // player has made it to the hall of fame
5278 if (i < MAX_SCORE_ENTRIES - 1)
5280 int m = MAX_SCORE_ENTRIES - 1;
5283 if (one_score_entry_per_name)
5285 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5286 if (strEqual(list->entry[l].name, new_entry->name))
5289 if (m == i) // player's new highscore overwrites his old one
5293 for (l = m; l > i; l--)
5294 list->entry[l] = list->entry[l - 1];
5299 *entry = *new_entry;
5303 else if (one_score_entry_per_name &&
5304 strEqual(entry->name, new_entry->name))
5306 // player already in high score list with better score or time
5312 // special case: new score is beyond the last high score list position
5313 return MAX_SCORE_ENTRIES;
5316 void NewHighScore(int level_nr, boolean tape_saved)
5318 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5319 boolean one_per_name = FALSE;
5321 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5322 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5324 new_entry.score = game.score_final;
5325 new_entry.time = game.score_time_final;
5327 LoadScore(level_nr);
5329 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5331 if (scores.last_added >= MAX_SCORE_ENTRIES)
5333 scores.last_added = MAX_SCORE_ENTRIES - 1;
5334 scores.force_last_added = TRUE;
5336 scores.entry[scores.last_added] = new_entry;
5338 // store last added local score entry (before merging server scores)
5339 scores.last_added_local = scores.last_added;
5344 if (scores.last_added < 0)
5347 SaveScore(level_nr);
5349 // store last added local score entry (before merging server scores)
5350 scores.last_added_local = scores.last_added;
5352 if (!game.LevelSolved_SaveTape)
5355 SaveScoreTape(level_nr);
5357 if (setup.ask_for_using_api_server)
5359 setup.use_api_server =
5360 Request("Upload your score and tape to the high score server?", REQ_ASK);
5362 if (!setup.use_api_server)
5363 Request("Not using high score server! Use setup menu to enable again!",
5366 runtime.use_api_server = setup.use_api_server;
5368 // after asking for using API server once, do not ask again
5369 setup.ask_for_using_api_server = FALSE;
5371 SaveSetup_ServerSetup();
5374 SaveServerScore(level_nr, tape_saved);
5377 void MergeServerScore(void)
5379 struct ScoreEntry last_added_entry;
5380 boolean one_per_name = FALSE;
5383 if (scores.last_added >= 0)
5384 last_added_entry = scores.entry[scores.last_added];
5386 for (i = 0; i < server_scores.num_entries; i++)
5388 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5390 if (pos >= 0 && pos <= scores.last_added)
5391 scores.last_added++;
5394 if (scores.last_added >= MAX_SCORE_ENTRIES)
5396 scores.last_added = MAX_SCORE_ENTRIES - 1;
5397 scores.force_last_added = TRUE;
5399 scores.entry[scores.last_added] = last_added_entry;
5403 static int getElementMoveStepsizeExt(int x, int y, int direction)
5405 int element = Tile[x][y];
5406 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5407 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5408 int horiz_move = (dx != 0);
5409 int sign = (horiz_move ? dx : dy);
5410 int step = sign * element_info[element].move_stepsize;
5412 // special values for move stepsize for spring and things on conveyor belt
5415 if (CAN_FALL(element) &&
5416 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5417 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5418 else if (element == EL_SPRING)
5419 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5425 static int getElementMoveStepsize(int x, int y)
5427 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5430 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5432 if (player->GfxAction != action || player->GfxDir != dir)
5434 player->GfxAction = action;
5435 player->GfxDir = dir;
5437 player->StepFrame = 0;
5441 static void ResetGfxFrame(int x, int y)
5443 // profiling showed that "autotest" spends 10~20% of its time in this function
5444 if (DrawingDeactivatedField())
5447 int element = Tile[x][y];
5448 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5450 if (graphic_info[graphic].anim_global_sync)
5451 GfxFrame[x][y] = FrameCounter;
5452 else if (graphic_info[graphic].anim_global_anim_sync)
5453 GfxFrame[x][y] = getGlobalAnimSyncFrame();
5454 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5455 GfxFrame[x][y] = CustomValue[x][y];
5456 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5457 GfxFrame[x][y] = element_info[element].collect_score;
5458 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5459 GfxFrame[x][y] = ChangeDelay[x][y];
5462 static void ResetGfxAnimation(int x, int y)
5464 GfxAction[x][y] = ACTION_DEFAULT;
5465 GfxDir[x][y] = MovDir[x][y];
5468 ResetGfxFrame(x, y);
5471 static void ResetRandomAnimationValue(int x, int y)
5473 GfxRandom[x][y] = INIT_GFX_RANDOM();
5476 static void InitMovingField(int x, int y, int direction)
5478 int element = Tile[x][y];
5479 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5480 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5483 boolean is_moving_before, is_moving_after;
5485 // check if element was/is moving or being moved before/after mode change
5486 is_moving_before = (WasJustMoving[x][y] != 0);
5487 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5489 // reset animation only for moving elements which change direction of moving
5490 // or which just started or stopped moving
5491 // (else CEs with property "can move" / "not moving" are reset each frame)
5492 if (is_moving_before != is_moving_after ||
5493 direction != MovDir[x][y])
5494 ResetGfxAnimation(x, y);
5496 MovDir[x][y] = direction;
5497 GfxDir[x][y] = direction;
5499 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5500 direction == MV_DOWN && CAN_FALL(element) ?
5501 ACTION_FALLING : ACTION_MOVING);
5503 // this is needed for CEs with property "can move" / "not moving"
5505 if (is_moving_after)
5507 if (Tile[newx][newy] == EL_EMPTY)
5508 Tile[newx][newy] = EL_BLOCKED;
5510 MovDir[newx][newy] = MovDir[x][y];
5512 CustomValue[newx][newy] = CustomValue[x][y];
5514 GfxFrame[newx][newy] = GfxFrame[x][y];
5515 GfxRandom[newx][newy] = GfxRandom[x][y];
5516 GfxAction[newx][newy] = GfxAction[x][y];
5517 GfxDir[newx][newy] = GfxDir[x][y];
5521 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5523 int direction = MovDir[x][y];
5524 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5525 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5531 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5533 int direction = MovDir[x][y];
5534 int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0);
5535 int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0);
5537 *comes_from_x = oldx;
5538 *comes_from_y = oldy;
5541 static int MovingOrBlocked2Element(int x, int y)
5543 int element = Tile[x][y];
5545 if (element == EL_BLOCKED)
5549 Blocked2Moving(x, y, &oldx, &oldy);
5551 return Tile[oldx][oldy];
5557 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5559 // like MovingOrBlocked2Element(), but if element is moving
5560 // and (x, y) is the field the moving element is just leaving,
5561 // return EL_BLOCKED instead of the element value
5562 int element = Tile[x][y];
5564 if (IS_MOVING(x, y))
5566 if (element == EL_BLOCKED)
5570 Blocked2Moving(x, y, &oldx, &oldy);
5571 return Tile[oldx][oldy];
5580 static void RemoveField(int x, int y)
5582 Tile[x][y] = EL_EMPTY;
5588 CustomValue[x][y] = 0;
5591 ChangeDelay[x][y] = 0;
5592 ChangePage[x][y] = -1;
5593 Pushed[x][y] = FALSE;
5595 GfxElement[x][y] = EL_UNDEFINED;
5596 GfxAction[x][y] = ACTION_DEFAULT;
5597 GfxDir[x][y] = MV_NONE;
5600 static void RemoveMovingField(int x, int y)
5602 int oldx = x, oldy = y, newx = x, newy = y;
5603 int element = Tile[x][y];
5604 int next_element = EL_UNDEFINED;
5606 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5609 if (IS_MOVING(x, y))
5611 Moving2Blocked(x, y, &newx, &newy);
5613 if (Tile[newx][newy] != EL_BLOCKED)
5615 // element is moving, but target field is not free (blocked), but
5616 // already occupied by something different (example: acid pool);
5617 // in this case, only remove the moving field, but not the target
5619 RemoveField(oldx, oldy);
5621 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5623 TEST_DrawLevelField(oldx, oldy);
5628 else if (element == EL_BLOCKED)
5630 Blocked2Moving(x, y, &oldx, &oldy);
5631 if (!IS_MOVING(oldx, oldy))
5635 if (element == EL_BLOCKED &&
5636 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5637 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5638 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5639 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5640 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5641 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5642 next_element = get_next_element(Tile[oldx][oldy]);
5644 RemoveField(oldx, oldy);
5645 RemoveField(newx, newy);
5647 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5649 if (next_element != EL_UNDEFINED)
5650 Tile[oldx][oldy] = next_element;
5652 TEST_DrawLevelField(oldx, oldy);
5653 TEST_DrawLevelField(newx, newy);
5656 void DrawDynamite(int x, int y)
5658 int sx = SCREENX(x), sy = SCREENY(y);
5659 int graphic = el2img(Tile[x][y]);
5662 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5665 if (IS_WALKABLE_INSIDE(Back[x][y]))
5669 DrawLevelElement(x, y, Back[x][y]);
5670 else if (Store[x][y])
5671 DrawLevelElement(x, y, Store[x][y]);
5672 else if (game.use_masked_elements)
5673 DrawLevelElement(x, y, EL_EMPTY);
5675 frame = getGraphicAnimationFrameXY(graphic, x, y);
5677 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5678 DrawGraphicThruMask(sx, sy, graphic, frame);
5680 DrawGraphic(sx, sy, graphic, frame);
5683 static void CheckDynamite(int x, int y)
5685 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5689 if (MovDelay[x][y] != 0)
5692 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5698 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5703 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5705 boolean num_checked_players = 0;
5708 for (i = 0; i < MAX_PLAYERS; i++)
5710 if (stored_player[i].active)
5712 int sx = stored_player[i].jx;
5713 int sy = stored_player[i].jy;
5715 if (num_checked_players == 0)
5722 *sx1 = MIN(*sx1, sx);
5723 *sy1 = MIN(*sy1, sy);
5724 *sx2 = MAX(*sx2, sx);
5725 *sy2 = MAX(*sy2, sy);
5728 num_checked_players++;
5733 static boolean checkIfAllPlayersFitToScreen_RND(void)
5735 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5737 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5739 return (sx2 - sx1 < SCR_FIELDX &&
5740 sy2 - sy1 < SCR_FIELDY);
5743 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5745 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5747 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5749 *sx = (sx1 + sx2) / 2;
5750 *sy = (sy1 + sy2) / 2;
5753 static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
5754 boolean center_screen, boolean quick_relocation)
5756 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5757 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5758 boolean no_delay = (tape.warp_forward);
5759 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5760 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5761 int new_scroll_x, new_scroll_y;
5763 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5765 // case 1: quick relocation inside visible screen (without scrolling)
5772 if (!level.shifted_relocation || center_screen)
5774 // relocation _with_ centering of screen
5776 new_scroll_x = SCROLL_POSITION_X(x);
5777 new_scroll_y = SCROLL_POSITION_Y(y);
5781 // relocation _without_ centering of screen
5783 // apply distance between old and new player position to scroll position
5784 int shifted_scroll_x = scroll_x + (x - old_x);
5785 int shifted_scroll_y = scroll_y + (y - old_y);
5787 // make sure that shifted scroll position does not scroll beyond screen
5788 new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
5789 new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
5791 // special case for teleporting from one end of the playfield to the other
5792 // (this kludge prevents the destination area to be shifted by half a tile
5793 // against the source destination for even screen width or screen height;
5794 // probably most useful when used with high "game.forced_scroll_delay_value"
5795 // in combination with "game.forced_scroll_x" and "game.forced_scroll_y")
5796 if (quick_relocation)
5798 if (EVEN(SCR_FIELDX))
5800 // relocate (teleport) between left and right border (half or full)
5801 if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1)
5802 new_scroll_x = SBX_Right;
5803 else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right)
5804 new_scroll_x = SBX_Right - 1;
5805 else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1)
5806 new_scroll_x = SBX_Left;
5807 else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left)
5808 new_scroll_x = SBX_Left + 1;
5811 if (EVEN(SCR_FIELDY))
5813 // relocate (teleport) between top and bottom border (half or full)
5814 if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1)
5815 new_scroll_y = SBY_Lower;
5816 else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower)
5817 new_scroll_y = SBY_Lower - 1;
5818 else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1)
5819 new_scroll_y = SBY_Upper;
5820 else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper)
5821 new_scroll_y = SBY_Upper + 1;
5826 if (quick_relocation)
5828 // case 2: quick relocation (redraw without visible scrolling)
5830 scroll_x = new_scroll_x;
5831 scroll_y = new_scroll_y;
5838 // case 3: visible relocation (with scrolling to new position)
5840 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5842 SetVideoFrameDelay(wait_delay_value);
5844 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5846 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5847 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5849 if (dx == 0 && dy == 0) // no scrolling needed at all
5855 // set values for horizontal/vertical screen scrolling (half tile size)
5856 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5857 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5858 int pos_x = dx * TILEX / 2;
5859 int pos_y = dy * TILEY / 2;
5860 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5861 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5863 ScrollLevel(dx, dy);
5866 // scroll in two steps of half tile size to make things smoother
5867 BlitScreenToBitmapExt_RND(window, fx, fy);
5869 // scroll second step to align at full tile size
5870 BlitScreenToBitmap(window);
5876 SetVideoFrameDelay(frame_delay_value_old);
5879 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5881 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5882 int player_nr = GET_PLAYER_NR(el_player);
5883 struct PlayerInfo *player = &stored_player[player_nr];
5884 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5885 boolean no_delay = (tape.warp_forward);
5886 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5887 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5888 int old_jx = player->jx;
5889 int old_jy = player->jy;
5890 int old_element = Tile[old_jx][old_jy];
5891 int element = Tile[jx][jy];
5892 boolean player_relocated = (old_jx != jx || old_jy != jy);
5894 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5895 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5896 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5897 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5898 int leave_side_horiz = move_dir_horiz;
5899 int leave_side_vert = move_dir_vert;
5900 int enter_side = enter_side_horiz | enter_side_vert;
5901 int leave_side = leave_side_horiz | leave_side_vert;
5903 if (player->buried) // do not reanimate dead player
5906 if (!player_relocated) // no need to relocate the player
5909 if (IS_PLAYER(jx, jy)) // player already placed at new position
5911 RemoveField(jx, jy); // temporarily remove newly placed player
5912 DrawLevelField(jx, jy);
5915 if (player->present)
5917 while (player->MovPos)
5919 ScrollPlayer(player, SCROLL_GO_ON);
5920 ScrollScreen(NULL, SCROLL_GO_ON);
5922 AdvanceFrameAndPlayerCounters(player->index_nr);
5926 BackToFront_WithFrameDelay(wait_delay_value);
5929 DrawPlayer(player); // needed here only to cleanup last field
5930 DrawLevelField(player->jx, player->jy); // remove player graphic
5932 player->is_moving = FALSE;
5935 if (IS_CUSTOM_ELEMENT(old_element))
5936 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5938 player->index_bit, leave_side);
5940 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5942 player->index_bit, leave_side);
5944 Tile[jx][jy] = el_player;
5945 InitPlayerField(jx, jy, el_player, TRUE);
5947 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5948 possible that the relocation target field did not contain a player element,
5949 but a walkable element, to which the new player was relocated -- in this
5950 case, restore that (already initialized!) element on the player field */
5951 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5953 Tile[jx][jy] = element; // restore previously existing element
5956 // only visually relocate centered player
5957 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy,
5958 FALSE, level.instant_relocation);
5960 TestIfPlayerTouchesBadThing(jx, jy);
5961 TestIfPlayerTouchesCustomElement(jx, jy);
5963 if (IS_CUSTOM_ELEMENT(element))
5964 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5965 player->index_bit, enter_side);
5967 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5968 player->index_bit, enter_side);
5970 if (player->is_switching)
5972 /* ensure that relocation while still switching an element does not cause
5973 a new element to be treated as also switched directly after relocation
5974 (this is important for teleporter switches that teleport the player to
5975 a place where another teleporter switch is in the same direction, which
5976 would then incorrectly be treated as immediately switched before the
5977 direction key that caused the switch was released) */
5979 player->switch_x += jx - old_jx;
5980 player->switch_y += jy - old_jy;
5984 static void Explode(int ex, int ey, int phase, int mode)
5990 if (game.explosions_delayed)
5992 ExplodeField[ex][ey] = mode;
5996 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5998 int center_element = Tile[ex][ey];
5999 int ce_value = CustomValue[ex][ey];
6000 int ce_score = element_info[center_element].collect_score;
6001 int artwork_element, explosion_element; // set these values later
6003 // remove things displayed in background while burning dynamite
6004 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
6007 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
6009 // put moving element to center field (and let it explode there)
6010 center_element = MovingOrBlocked2Element(ex, ey);
6011 RemoveMovingField(ex, ey);
6012 Tile[ex][ey] = center_element;
6015 // now "center_element" is finally determined -- set related values now
6016 artwork_element = center_element; // for custom player artwork
6017 explosion_element = center_element; // for custom player artwork
6019 if (IS_PLAYER(ex, ey))
6021 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
6023 artwork_element = stored_player[player_nr].artwork_element;
6025 if (level.use_explosion_element[player_nr])
6027 explosion_element = level.explosion_element[player_nr];
6028 artwork_element = explosion_element;
6032 if (mode == EX_TYPE_NORMAL ||
6033 mode == EX_TYPE_CENTER ||
6034 mode == EX_TYPE_CROSS)
6035 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
6037 last_phase = element_info[explosion_element].explosion_delay + 1;
6039 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
6041 int xx = x - ex + 1;
6042 int yy = y - ey + 1;
6045 if (!IN_LEV_FIELD(x, y) ||
6046 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
6047 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
6050 element = Tile[x][y];
6052 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
6054 element = MovingOrBlocked2Element(x, y);
6056 if (!IS_EXPLOSION_PROOF(element))
6057 RemoveMovingField(x, y);
6060 // indestructible elements can only explode in center (but not flames)
6061 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
6062 mode == EX_TYPE_BORDER)) ||
6063 element == EL_FLAMES)
6066 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
6067 behaviour, for example when touching a yamyam that explodes to rocks
6068 with active deadly shield, a rock is created under the player !!! */
6069 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
6071 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
6072 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
6073 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
6075 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
6078 if (IS_ACTIVE_BOMB(element))
6080 // re-activate things under the bomb like gate or penguin
6081 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
6088 // save walkable background elements while explosion on same tile
6089 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
6090 (x != ex || y != ey || mode == EX_TYPE_BORDER))
6091 Back[x][y] = element;
6093 // ignite explodable elements reached by other explosion
6094 if (element == EL_EXPLOSION)
6095 element = Store2[x][y];
6097 if (AmoebaNr[x][y] &&
6098 (element == EL_AMOEBA_FULL ||
6099 element == EL_BD_AMOEBA ||
6100 element == EL_AMOEBA_GROWING))
6102 AmoebaCnt[AmoebaNr[x][y]]--;
6103 AmoebaCnt2[AmoebaNr[x][y]]--;
6108 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
6110 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
6112 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
6114 if (PLAYERINFO(ex, ey)->use_murphy)
6115 Store[x][y] = EL_EMPTY;
6118 // !!! check this case -- currently needed for rnd_rado_negundo_v,
6119 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
6120 else if (IS_PLAYER_ELEMENT(center_element))
6121 Store[x][y] = EL_EMPTY;
6122 else if (center_element == EL_YAMYAM)
6123 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
6124 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
6125 Store[x][y] = element_info[center_element].content.e[xx][yy];
6127 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
6128 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
6129 // otherwise) -- FIX THIS !!!
6130 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
6131 Store[x][y] = element_info[element].content.e[1][1];
6133 else if (!CAN_EXPLODE(element))
6134 Store[x][y] = element_info[element].content.e[1][1];
6137 Store[x][y] = EL_EMPTY;
6139 if (IS_CUSTOM_ELEMENT(center_element))
6140 Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value :
6141 Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score :
6142 Store[x][y] >= EL_PREV_CE_8 &&
6143 Store[x][y] <= EL_NEXT_CE_8 ?
6144 RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) :
6147 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
6148 center_element == EL_AMOEBA_TO_DIAMOND)
6149 Store2[x][y] = element;
6151 Tile[x][y] = EL_EXPLOSION;
6152 GfxElement[x][y] = artwork_element;
6154 ExplodePhase[x][y] = 1;
6155 ExplodeDelay[x][y] = last_phase;
6160 if (center_element == EL_YAMYAM)
6161 game.yamyam_content_nr =
6162 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
6174 GfxFrame[x][y] = 0; // restart explosion animation
6176 last_phase = ExplodeDelay[x][y];
6178 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
6180 // this can happen if the player leaves an explosion just in time
6181 if (GfxElement[x][y] == EL_UNDEFINED)
6182 GfxElement[x][y] = EL_EMPTY;
6184 border_element = Store2[x][y];
6185 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6186 border_element = StorePlayer[x][y];
6188 if (phase == element_info[border_element].ignition_delay ||
6189 phase == last_phase)
6191 boolean border_explosion = FALSE;
6193 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6194 !PLAYER_EXPLOSION_PROTECTED(x, y))
6196 KillPlayerUnlessExplosionProtected(x, y);
6197 border_explosion = TRUE;
6199 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6201 Tile[x][y] = Store2[x][y];
6204 border_explosion = TRUE;
6206 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6208 AmoebaToDiamond(x, y);
6210 border_explosion = TRUE;
6213 // if an element just explodes due to another explosion (chain-reaction),
6214 // do not immediately end the new explosion when it was the last frame of
6215 // the explosion (as it would be done in the following "if"-statement!)
6216 if (border_explosion && phase == last_phase)
6220 // this can happen if the player was just killed by an explosion
6221 if (GfxElement[x][y] == EL_UNDEFINED)
6222 GfxElement[x][y] = EL_EMPTY;
6224 if (phase == last_phase)
6228 element = Tile[x][y] = Store[x][y];
6229 Store[x][y] = Store2[x][y] = 0;
6230 GfxElement[x][y] = EL_UNDEFINED;
6232 // player can escape from explosions and might therefore be still alive
6233 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6234 element <= EL_PLAYER_IS_EXPLODING_4)
6236 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6237 int explosion_element = EL_PLAYER_1 + player_nr;
6238 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6239 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6241 if (level.use_explosion_element[player_nr])
6242 explosion_element = level.explosion_element[player_nr];
6244 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6245 element_info[explosion_element].content.e[xx][yy]);
6248 // restore probably existing indestructible background element
6249 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6250 element = Tile[x][y] = Back[x][y];
6253 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6254 GfxDir[x][y] = MV_NONE;
6255 ChangeDelay[x][y] = 0;
6256 ChangePage[x][y] = -1;
6258 CustomValue[x][y] = 0;
6260 InitField_WithBug2(x, y, FALSE);
6262 TEST_DrawLevelField(x, y);
6264 TestIfElementTouchesCustomElement(x, y);
6266 if (GFX_CRUMBLED(element))
6267 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6269 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6270 StorePlayer[x][y] = 0;
6272 if (IS_PLAYER_ELEMENT(element))
6273 RelocatePlayer(x, y, element);
6275 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6277 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6278 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6281 TEST_DrawLevelFieldCrumbled(x, y);
6283 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6285 DrawLevelElement(x, y, Back[x][y]);
6286 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6288 else if (IS_WALKABLE_UNDER(Back[x][y]))
6290 DrawLevelGraphic(x, y, graphic, frame);
6291 DrawLevelElementThruMask(x, y, Back[x][y]);
6293 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6294 DrawLevelGraphic(x, y, graphic, frame);
6298 static void DynaExplode(int ex, int ey)
6301 int dynabomb_element = Tile[ex][ey];
6302 int dynabomb_size = 1;
6303 boolean dynabomb_xl = FALSE;
6304 struct PlayerInfo *player;
6305 struct XY *xy = xy_topdown;
6307 if (IS_ACTIVE_BOMB(dynabomb_element))
6309 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6310 dynabomb_size = player->dynabomb_size;
6311 dynabomb_xl = player->dynabomb_xl;
6312 player->dynabombs_left++;
6315 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6317 for (i = 0; i < NUM_DIRECTIONS; i++)
6319 for (j = 1; j <= dynabomb_size; j++)
6321 int x = ex + j * xy[i].x;
6322 int y = ey + j * xy[i].y;
6325 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6328 element = Tile[x][y];
6330 // do not restart explosions of fields with active bombs
6331 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6334 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6336 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6337 !IS_DIGGABLE(element) && !dynabomb_xl)
6343 void Bang(int x, int y)
6345 int element = MovingOrBlocked2Element(x, y);
6346 int explosion_type = EX_TYPE_NORMAL;
6348 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6350 struct PlayerInfo *player = PLAYERINFO(x, y);
6352 element = Tile[x][y] = player->initial_element;
6354 if (level.use_explosion_element[player->index_nr])
6356 int explosion_element = level.explosion_element[player->index_nr];
6358 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6359 explosion_type = EX_TYPE_CROSS;
6360 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6361 explosion_type = EX_TYPE_CENTER;
6369 case EL_BD_BUTTERFLY:
6372 case EL_DARK_YAMYAM:
6376 RaiseScoreElement(element);
6379 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6380 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6381 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6382 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6383 case EL_DYNABOMB_INCREASE_NUMBER:
6384 case EL_DYNABOMB_INCREASE_SIZE:
6385 case EL_DYNABOMB_INCREASE_POWER:
6386 explosion_type = EX_TYPE_DYNA;
6389 case EL_DC_LANDMINE:
6390 explosion_type = EX_TYPE_CENTER;
6395 case EL_LAMP_ACTIVE:
6396 case EL_AMOEBA_TO_DIAMOND:
6397 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6398 explosion_type = EX_TYPE_CENTER;
6402 if (element_info[element].explosion_type == EXPLODES_CROSS)
6403 explosion_type = EX_TYPE_CROSS;
6404 else if (element_info[element].explosion_type == EXPLODES_1X1)
6405 explosion_type = EX_TYPE_CENTER;
6409 if (explosion_type == EX_TYPE_DYNA)
6412 Explode(x, y, EX_PHASE_START, explosion_type);
6414 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6417 static void SplashAcid(int x, int y)
6419 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6420 (!IN_LEV_FIELD(x - 1, y - 2) ||
6421 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6422 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6424 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6425 (!IN_LEV_FIELD(x + 1, y - 2) ||
6426 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6427 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6429 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6432 static void InitBeltMovement(void)
6434 static int belt_base_element[4] =
6436 EL_CONVEYOR_BELT_1_LEFT,
6437 EL_CONVEYOR_BELT_2_LEFT,
6438 EL_CONVEYOR_BELT_3_LEFT,
6439 EL_CONVEYOR_BELT_4_LEFT
6441 static int belt_base_active_element[4] =
6443 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6444 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6445 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6446 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6451 // set frame order for belt animation graphic according to belt direction
6452 for (i = 0; i < NUM_BELTS; i++)
6456 for (j = 0; j < NUM_BELT_PARTS; j++)
6458 int element = belt_base_active_element[belt_nr] + j;
6459 int graphic_1 = el2img(element);
6460 int graphic_2 = el2panelimg(element);
6462 if (game.belt_dir[i] == MV_LEFT)
6464 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6465 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6469 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6470 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6475 SCAN_PLAYFIELD(x, y)
6477 int element = Tile[x][y];
6479 for (i = 0; i < NUM_BELTS; i++)
6481 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6483 int e_belt_nr = getBeltNrFromBeltElement(element);
6486 if (e_belt_nr == belt_nr)
6488 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6490 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6497 static void ToggleBeltSwitch(int x, int y)
6499 static int belt_base_element[4] =
6501 EL_CONVEYOR_BELT_1_LEFT,
6502 EL_CONVEYOR_BELT_2_LEFT,
6503 EL_CONVEYOR_BELT_3_LEFT,
6504 EL_CONVEYOR_BELT_4_LEFT
6506 static int belt_base_active_element[4] =
6508 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6509 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6510 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6511 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6513 static int belt_base_switch_element[4] =
6515 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6516 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6517 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6518 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6520 static int belt_move_dir[4] =
6528 int element = Tile[x][y];
6529 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6530 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6531 int belt_dir = belt_move_dir[belt_dir_nr];
6534 if (!IS_BELT_SWITCH(element))
6537 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6538 game.belt_dir[belt_nr] = belt_dir;
6540 if (belt_dir_nr == 3)
6543 // set frame order for belt animation graphic according to belt direction
6544 for (i = 0; i < NUM_BELT_PARTS; i++)
6546 int element = belt_base_active_element[belt_nr] + i;
6547 int graphic_1 = el2img(element);
6548 int graphic_2 = el2panelimg(element);
6550 if (belt_dir == MV_LEFT)
6552 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6553 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6557 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6558 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6562 SCAN_PLAYFIELD(xx, yy)
6564 int element = Tile[xx][yy];
6566 if (IS_BELT_SWITCH(element))
6568 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6570 if (e_belt_nr == belt_nr)
6572 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6573 TEST_DrawLevelField(xx, yy);
6576 else if (IS_BELT(element) && belt_dir != MV_NONE)
6578 int e_belt_nr = getBeltNrFromBeltElement(element);
6580 if (e_belt_nr == belt_nr)
6582 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6584 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6585 TEST_DrawLevelField(xx, yy);
6588 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6590 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6592 if (e_belt_nr == belt_nr)
6594 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6596 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6597 TEST_DrawLevelField(xx, yy);
6603 static void ToggleSwitchgateSwitch(void)
6607 game.switchgate_pos = !game.switchgate_pos;
6609 SCAN_PLAYFIELD(xx, yy)
6611 int element = Tile[xx][yy];
6613 if (element == EL_SWITCHGATE_SWITCH_UP)
6615 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6616 TEST_DrawLevelField(xx, yy);
6618 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6620 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6621 TEST_DrawLevelField(xx, yy);
6623 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6625 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6626 TEST_DrawLevelField(xx, yy);
6628 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6630 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6631 TEST_DrawLevelField(xx, yy);
6633 else if (element == EL_SWITCHGATE_OPEN ||
6634 element == EL_SWITCHGATE_OPENING)
6636 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6638 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6640 else if (element == EL_SWITCHGATE_CLOSED ||
6641 element == EL_SWITCHGATE_CLOSING)
6643 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6645 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6650 static int getInvisibleActiveFromInvisibleElement(int element)
6652 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6653 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6654 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6658 static int getInvisibleFromInvisibleActiveElement(int element)
6660 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6661 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6662 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6666 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6670 SCAN_PLAYFIELD(x, y)
6672 int element = Tile[x][y];
6674 if (element == EL_LIGHT_SWITCH &&
6675 game.light_time_left > 0)
6677 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6678 TEST_DrawLevelField(x, y);
6680 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6681 game.light_time_left == 0)
6683 Tile[x][y] = EL_LIGHT_SWITCH;
6684 TEST_DrawLevelField(x, y);
6686 else if (element == EL_EMC_DRIPPER &&
6687 game.light_time_left > 0)
6689 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6690 TEST_DrawLevelField(x, y);
6692 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6693 game.light_time_left == 0)
6695 Tile[x][y] = EL_EMC_DRIPPER;
6696 TEST_DrawLevelField(x, y);
6698 else if (element == EL_INVISIBLE_STEELWALL ||
6699 element == EL_INVISIBLE_WALL ||
6700 element == EL_INVISIBLE_SAND)
6702 if (game.light_time_left > 0)
6703 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6705 TEST_DrawLevelField(x, y);
6707 // uncrumble neighbour fields, if needed
6708 if (element == EL_INVISIBLE_SAND)
6709 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6711 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6712 element == EL_INVISIBLE_WALL_ACTIVE ||
6713 element == EL_INVISIBLE_SAND_ACTIVE)
6715 if (game.light_time_left == 0)
6716 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6718 TEST_DrawLevelField(x, y);
6720 // re-crumble neighbour fields, if needed
6721 if (element == EL_INVISIBLE_SAND)
6722 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6727 static void RedrawAllInvisibleElementsForLenses(void)
6731 SCAN_PLAYFIELD(x, y)
6733 int element = Tile[x][y];
6735 if (element == EL_EMC_DRIPPER &&
6736 game.lenses_time_left > 0)
6738 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6739 TEST_DrawLevelField(x, y);
6741 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6742 game.lenses_time_left == 0)
6744 Tile[x][y] = EL_EMC_DRIPPER;
6745 TEST_DrawLevelField(x, y);
6747 else if (element == EL_INVISIBLE_STEELWALL ||
6748 element == EL_INVISIBLE_WALL ||
6749 element == EL_INVISIBLE_SAND)
6751 if (game.lenses_time_left > 0)
6752 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6754 TEST_DrawLevelField(x, y);
6756 // uncrumble neighbour fields, if needed
6757 if (element == EL_INVISIBLE_SAND)
6758 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6760 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6761 element == EL_INVISIBLE_WALL_ACTIVE ||
6762 element == EL_INVISIBLE_SAND_ACTIVE)
6764 if (game.lenses_time_left == 0)
6765 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6767 TEST_DrawLevelField(x, y);
6769 // re-crumble neighbour fields, if needed
6770 if (element == EL_INVISIBLE_SAND)
6771 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6776 static void RedrawAllInvisibleElementsForMagnifier(void)
6780 SCAN_PLAYFIELD(x, y)
6782 int element = Tile[x][y];
6784 if (element == EL_EMC_FAKE_GRASS &&
6785 game.magnify_time_left > 0)
6787 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6788 TEST_DrawLevelField(x, y);
6790 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6791 game.magnify_time_left == 0)
6793 Tile[x][y] = EL_EMC_FAKE_GRASS;
6794 TEST_DrawLevelField(x, y);
6796 else if (IS_GATE_GRAY(element) &&
6797 game.magnify_time_left > 0)
6799 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6800 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6801 IS_EM_GATE_GRAY(element) ?
6802 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6803 IS_EMC_GATE_GRAY(element) ?
6804 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6805 IS_DC_GATE_GRAY(element) ?
6806 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6808 TEST_DrawLevelField(x, y);
6810 else if (IS_GATE_GRAY_ACTIVE(element) &&
6811 game.magnify_time_left == 0)
6813 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6814 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6815 IS_EM_GATE_GRAY_ACTIVE(element) ?
6816 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6817 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6818 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6819 IS_DC_GATE_GRAY_ACTIVE(element) ?
6820 EL_DC_GATE_WHITE_GRAY :
6822 TEST_DrawLevelField(x, y);
6827 static void ToggleLightSwitch(int x, int y)
6829 int element = Tile[x][y];
6831 game.light_time_left =
6832 (element == EL_LIGHT_SWITCH ?
6833 level.time_light * FRAMES_PER_SECOND : 0);
6835 RedrawAllLightSwitchesAndInvisibleElements();
6838 static void ActivateTimegateSwitch(int x, int y)
6842 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6844 SCAN_PLAYFIELD(xx, yy)
6846 int element = Tile[xx][yy];
6848 if (element == EL_TIMEGATE_CLOSED ||
6849 element == EL_TIMEGATE_CLOSING)
6851 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6852 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6856 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6858 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6859 TEST_DrawLevelField(xx, yy);
6865 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6866 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6869 static void Impact(int x, int y)
6871 boolean last_line = (y == lev_fieldy - 1);
6872 boolean object_hit = FALSE;
6873 boolean impact = (last_line || object_hit);
6874 int element = Tile[x][y];
6875 int smashed = EL_STEELWALL;
6877 if (!last_line) // check if element below was hit
6879 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6882 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6883 MovDir[x][y + 1] != MV_DOWN ||
6884 MovPos[x][y + 1] <= TILEY / 2));
6886 // do not smash moving elements that left the smashed field in time
6887 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6888 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6891 #if USE_QUICKSAND_IMPACT_BUGFIX
6892 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6894 RemoveMovingField(x, y + 1);
6895 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6896 Tile[x][y + 2] = EL_ROCK;
6897 TEST_DrawLevelField(x, y + 2);
6902 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6904 RemoveMovingField(x, y + 1);
6905 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6906 Tile[x][y + 2] = EL_ROCK;
6907 TEST_DrawLevelField(x, y + 2);
6914 smashed = MovingOrBlocked2Element(x, y + 1);
6916 impact = (last_line || object_hit);
6919 if (!last_line && smashed == EL_ACID) // element falls into acid
6921 SplashAcid(x, y + 1);
6925 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6926 // only reset graphic animation if graphic really changes after impact
6928 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6930 ResetGfxAnimation(x, y);
6931 TEST_DrawLevelField(x, y);
6934 if (impact && CAN_EXPLODE_IMPACT(element))
6939 else if (impact && element == EL_PEARL &&
6940 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6942 ResetGfxAnimation(x, y);
6944 Tile[x][y] = EL_PEARL_BREAKING;
6945 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6948 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6950 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6955 if (impact && element == EL_AMOEBA_DROP)
6957 if (object_hit && IS_PLAYER(x, y + 1))
6958 KillPlayerUnlessEnemyProtected(x, y + 1);
6959 else if (object_hit && smashed == EL_PENGUIN)
6963 Tile[x][y] = EL_AMOEBA_GROWING;
6964 Store[x][y] = EL_AMOEBA_WET;
6966 ResetRandomAnimationValue(x, y);
6971 if (object_hit) // check which object was hit
6973 if ((CAN_PASS_MAGIC_WALL(element) &&
6974 (smashed == EL_MAGIC_WALL ||
6975 smashed == EL_BD_MAGIC_WALL)) ||
6976 (CAN_PASS_DC_MAGIC_WALL(element) &&
6977 smashed == EL_DC_MAGIC_WALL))
6980 int activated_magic_wall =
6981 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6982 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6983 EL_DC_MAGIC_WALL_ACTIVE);
6985 // activate magic wall / mill
6986 SCAN_PLAYFIELD(xx, yy)
6988 if (Tile[xx][yy] == smashed)
6989 Tile[xx][yy] = activated_magic_wall;
6992 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6993 game.magic_wall_active = TRUE;
6995 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6996 SND_MAGIC_WALL_ACTIVATING :
6997 smashed == EL_BD_MAGIC_WALL ?
6998 SND_BD_MAGIC_WALL_ACTIVATING :
6999 SND_DC_MAGIC_WALL_ACTIVATING));
7002 if (IS_PLAYER(x, y + 1))
7004 if (CAN_SMASH_PLAYER(element))
7006 KillPlayerUnlessEnemyProtected(x, y + 1);
7010 else if (smashed == EL_PENGUIN)
7012 if (CAN_SMASH_PLAYER(element))
7018 else if (element == EL_BD_DIAMOND)
7020 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
7026 else if (((element == EL_SP_INFOTRON ||
7027 element == EL_SP_ZONK) &&
7028 (smashed == EL_SP_SNIKSNAK ||
7029 smashed == EL_SP_ELECTRON ||
7030 smashed == EL_SP_DISK_ORANGE)) ||
7031 (element == EL_SP_INFOTRON &&
7032 smashed == EL_SP_DISK_YELLOW))
7037 else if (CAN_SMASH_EVERYTHING(element))
7039 if (IS_CLASSIC_ENEMY(smashed) ||
7040 CAN_EXPLODE_SMASHED(smashed))
7045 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
7047 if (smashed == EL_LAMP ||
7048 smashed == EL_LAMP_ACTIVE)
7053 else if (smashed == EL_NUT)
7055 Tile[x][y + 1] = EL_NUT_BREAKING;
7056 PlayLevelSound(x, y, SND_NUT_BREAKING);
7057 RaiseScoreElement(EL_NUT);
7060 else if (smashed == EL_PEARL)
7062 ResetGfxAnimation(x, y);
7064 Tile[x][y + 1] = EL_PEARL_BREAKING;
7065 PlayLevelSound(x, y, SND_PEARL_BREAKING);
7068 else if (smashed == EL_DIAMOND)
7070 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
7071 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
7074 else if (IS_BELT_SWITCH(smashed))
7076 ToggleBeltSwitch(x, y + 1);
7078 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
7079 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
7080 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
7081 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
7083 ToggleSwitchgateSwitch();
7085 else if (smashed == EL_LIGHT_SWITCH ||
7086 smashed == EL_LIGHT_SWITCH_ACTIVE)
7088 ToggleLightSwitch(x, y + 1);
7092 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7094 CheckElementChangeBySide(x, y + 1, smashed, element,
7095 CE_SWITCHED, CH_SIDE_TOP);
7096 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
7102 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7107 // play sound of magic wall / mill
7109 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7110 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
7111 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
7113 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7114 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
7115 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7116 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
7117 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7118 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
7123 // play sound of object that hits the ground
7124 if (last_line || object_hit)
7125 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
7128 static void TurnRoundExt(int x, int y)
7140 { 0, 0 }, { 0, 0 }, { 0, 0 },
7145 int left, right, back;
7149 { MV_DOWN, MV_UP, MV_RIGHT },
7150 { MV_UP, MV_DOWN, MV_LEFT },
7152 { MV_LEFT, MV_RIGHT, MV_DOWN },
7156 { MV_RIGHT, MV_LEFT, MV_UP }
7159 int element = Tile[x][y];
7160 int move_pattern = element_info[element].move_pattern;
7162 int old_move_dir = MovDir[x][y];
7163 int left_dir = turn[old_move_dir].left;
7164 int right_dir = turn[old_move_dir].right;
7165 int back_dir = turn[old_move_dir].back;
7167 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
7168 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
7169 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
7170 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
7172 int left_x = x + left_dx, left_y = y + left_dy;
7173 int right_x = x + right_dx, right_y = y + right_dy;
7174 int move_x = x + move_dx, move_y = y + move_dy;
7178 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
7180 TestIfBadThingTouchesOtherBadThing(x, y);
7182 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
7183 MovDir[x][y] = right_dir;
7184 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7185 MovDir[x][y] = left_dir;
7187 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7189 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7192 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7194 TestIfBadThingTouchesOtherBadThing(x, y);
7196 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7197 MovDir[x][y] = left_dir;
7198 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7199 MovDir[x][y] = right_dir;
7201 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7203 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7206 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7208 TestIfBadThingTouchesOtherBadThing(x, y);
7210 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7211 MovDir[x][y] = left_dir;
7212 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7213 MovDir[x][y] = right_dir;
7215 if (MovDir[x][y] != old_move_dir)
7218 else if (element == EL_YAMYAM)
7220 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7221 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7223 if (can_turn_left && can_turn_right)
7224 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7225 else if (can_turn_left)
7226 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7227 else if (can_turn_right)
7228 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7230 MovDir[x][y] = back_dir;
7232 MovDelay[x][y] = 16 + 16 * RND(3);
7234 else if (element == EL_DARK_YAMYAM)
7236 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7238 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7241 if (can_turn_left && can_turn_right)
7242 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7243 else if (can_turn_left)
7244 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7245 else if (can_turn_right)
7246 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7248 MovDir[x][y] = back_dir;
7250 MovDelay[x][y] = 16 + 16 * RND(3);
7252 else if (element == EL_PACMAN)
7254 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7255 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7257 if (can_turn_left && can_turn_right)
7258 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7259 else if (can_turn_left)
7260 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7261 else if (can_turn_right)
7262 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7264 MovDir[x][y] = back_dir;
7266 MovDelay[x][y] = 6 + RND(40);
7268 else if (element == EL_PIG)
7270 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7271 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7272 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7273 boolean should_turn_left, should_turn_right, should_move_on;
7275 int rnd = RND(rnd_value);
7277 should_turn_left = (can_turn_left &&
7279 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7280 y + back_dy + left_dy)));
7281 should_turn_right = (can_turn_right &&
7283 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7284 y + back_dy + right_dy)));
7285 should_move_on = (can_move_on &&
7288 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7289 y + move_dy + left_dy) ||
7290 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7291 y + move_dy + right_dy)));
7293 if (should_turn_left || should_turn_right || should_move_on)
7295 if (should_turn_left && should_turn_right && should_move_on)
7296 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7297 rnd < 2 * rnd_value / 3 ? right_dir :
7299 else if (should_turn_left && should_turn_right)
7300 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7301 else if (should_turn_left && should_move_on)
7302 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7303 else if (should_turn_right && should_move_on)
7304 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7305 else if (should_turn_left)
7306 MovDir[x][y] = left_dir;
7307 else if (should_turn_right)
7308 MovDir[x][y] = right_dir;
7309 else if (should_move_on)
7310 MovDir[x][y] = old_move_dir;
7312 else if (can_move_on && rnd > rnd_value / 8)
7313 MovDir[x][y] = old_move_dir;
7314 else if (can_turn_left && can_turn_right)
7315 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7316 else if (can_turn_left && rnd > rnd_value / 8)
7317 MovDir[x][y] = left_dir;
7318 else if (can_turn_right && rnd > rnd_value/8)
7319 MovDir[x][y] = right_dir;
7321 MovDir[x][y] = back_dir;
7323 xx = x + move_xy[MovDir[x][y]].dx;
7324 yy = y + move_xy[MovDir[x][y]].dy;
7326 if (!IN_LEV_FIELD(xx, yy) ||
7327 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7328 MovDir[x][y] = old_move_dir;
7332 else if (element == EL_DRAGON)
7334 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7335 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7336 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7338 int rnd = RND(rnd_value);
7340 if (can_move_on && rnd > rnd_value / 8)
7341 MovDir[x][y] = old_move_dir;
7342 else if (can_turn_left && can_turn_right)
7343 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7344 else if (can_turn_left && rnd > rnd_value / 8)
7345 MovDir[x][y] = left_dir;
7346 else if (can_turn_right && rnd > rnd_value / 8)
7347 MovDir[x][y] = right_dir;
7349 MovDir[x][y] = back_dir;
7351 xx = x + move_xy[MovDir[x][y]].dx;
7352 yy = y + move_xy[MovDir[x][y]].dy;
7354 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7355 MovDir[x][y] = old_move_dir;
7359 else if (element == EL_MOLE)
7361 boolean can_move_on =
7362 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7363 IS_AMOEBOID(Tile[move_x][move_y]) ||
7364 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7367 boolean can_turn_left =
7368 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7369 IS_AMOEBOID(Tile[left_x][left_y])));
7371 boolean can_turn_right =
7372 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7373 IS_AMOEBOID(Tile[right_x][right_y])));
7375 if (can_turn_left && can_turn_right)
7376 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7377 else if (can_turn_left)
7378 MovDir[x][y] = left_dir;
7380 MovDir[x][y] = right_dir;
7383 if (MovDir[x][y] != old_move_dir)
7386 else if (element == EL_BALLOON)
7388 MovDir[x][y] = game.wind_direction;
7391 else if (element == EL_SPRING)
7393 if (MovDir[x][y] & MV_HORIZONTAL)
7395 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7396 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7398 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7399 ResetGfxAnimation(move_x, move_y);
7400 TEST_DrawLevelField(move_x, move_y);
7402 MovDir[x][y] = back_dir;
7404 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7405 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7406 MovDir[x][y] = MV_NONE;
7411 else if (element == EL_ROBOT ||
7412 element == EL_SATELLITE ||
7413 element == EL_PENGUIN ||
7414 element == EL_EMC_ANDROID)
7416 int attr_x = -1, attr_y = -1;
7418 if (game.all_players_gone)
7420 attr_x = game.exit_x;
7421 attr_y = game.exit_y;
7427 for (i = 0; i < MAX_PLAYERS; i++)
7429 struct PlayerInfo *player = &stored_player[i];
7430 int jx = player->jx, jy = player->jy;
7432 if (!player->active)
7436 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7444 if (element == EL_ROBOT &&
7445 game.robot_wheel_x >= 0 &&
7446 game.robot_wheel_y >= 0 &&
7447 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7448 game.engine_version < VERSION_IDENT(3,1,0,0)))
7450 attr_x = game.robot_wheel_x;
7451 attr_y = game.robot_wheel_y;
7454 if (element == EL_PENGUIN)
7457 struct XY *xy = xy_topdown;
7459 for (i = 0; i < NUM_DIRECTIONS; i++)
7461 int ex = x + xy[i].x;
7462 int ey = y + xy[i].y;
7464 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7465 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7466 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7467 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7476 MovDir[x][y] = MV_NONE;
7478 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7479 else if (attr_x > x)
7480 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7482 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7483 else if (attr_y > y)
7484 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7486 if (element == EL_ROBOT)
7490 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7491 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7492 Moving2Blocked(x, y, &newx, &newy);
7494 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7495 MovDelay[x][y] = 8 + 8 * !RND(3);
7497 MovDelay[x][y] = 16;
7499 else if (element == EL_PENGUIN)
7505 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7507 boolean first_horiz = RND(2);
7508 int new_move_dir = MovDir[x][y];
7511 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7512 Moving2Blocked(x, y, &newx, &newy);
7514 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7518 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7519 Moving2Blocked(x, y, &newx, &newy);
7521 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7524 MovDir[x][y] = old_move_dir;
7528 else if (element == EL_SATELLITE)
7534 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7536 boolean first_horiz = RND(2);
7537 int new_move_dir = MovDir[x][y];
7540 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7541 Moving2Blocked(x, y, &newx, &newy);
7543 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7547 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7548 Moving2Blocked(x, y, &newx, &newy);
7550 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7553 MovDir[x][y] = old_move_dir;
7557 else if (element == EL_EMC_ANDROID)
7559 static int check_pos[16] =
7561 -1, // 0 => (invalid)
7564 -1, // 3 => (invalid)
7566 0, // 5 => MV_LEFT | MV_UP
7567 2, // 6 => MV_RIGHT | MV_UP
7568 -1, // 7 => (invalid)
7570 6, // 9 => MV_LEFT | MV_DOWN
7571 4, // 10 => MV_RIGHT | MV_DOWN
7572 -1, // 11 => (invalid)
7573 -1, // 12 => (invalid)
7574 -1, // 13 => (invalid)
7575 -1, // 14 => (invalid)
7576 -1, // 15 => (invalid)
7584 { -1, -1, MV_LEFT | MV_UP },
7586 { +1, -1, MV_RIGHT | MV_UP },
7587 { +1, 0, MV_RIGHT },
7588 { +1, +1, MV_RIGHT | MV_DOWN },
7590 { -1, +1, MV_LEFT | MV_DOWN },
7593 int start_pos, check_order;
7594 boolean can_clone = FALSE;
7597 // check if there is any free field around current position
7598 for (i = 0; i < 8; i++)
7600 int newx = x + check_xy[i].dx;
7601 int newy = y + check_xy[i].dy;
7603 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7611 if (can_clone) // randomly find an element to clone
7615 start_pos = check_pos[RND(8)];
7616 check_order = (RND(2) ? -1 : +1);
7618 for (i = 0; i < 8; i++)
7620 int pos_raw = start_pos + i * check_order;
7621 int pos = (pos_raw + 8) % 8;
7622 int newx = x + check_xy[pos].dx;
7623 int newy = y + check_xy[pos].dy;
7625 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7627 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7628 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7630 Store[x][y] = Tile[newx][newy];
7639 if (can_clone) // randomly find a direction to move
7643 start_pos = check_pos[RND(8)];
7644 check_order = (RND(2) ? -1 : +1);
7646 for (i = 0; i < 8; i++)
7648 int pos_raw = start_pos + i * check_order;
7649 int pos = (pos_raw + 8) % 8;
7650 int newx = x + check_xy[pos].dx;
7651 int newy = y + check_xy[pos].dy;
7652 int new_move_dir = check_xy[pos].dir;
7654 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7656 MovDir[x][y] = new_move_dir;
7657 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7666 if (can_clone) // cloning and moving successful
7669 // cannot clone -- try to move towards player
7671 start_pos = check_pos[MovDir[x][y] & 0x0f];
7672 check_order = (RND(2) ? -1 : +1);
7674 for (i = 0; i < 3; i++)
7676 // first check start_pos, then previous/next or (next/previous) pos
7677 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7678 int pos = (pos_raw + 8) % 8;
7679 int newx = x + check_xy[pos].dx;
7680 int newy = y + check_xy[pos].dy;
7681 int new_move_dir = check_xy[pos].dir;
7683 if (IS_PLAYER(newx, newy))
7686 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7688 MovDir[x][y] = new_move_dir;
7689 MovDelay[x][y] = level.android_move_time * 8 + 1;
7696 else if (move_pattern == MV_TURNING_LEFT ||
7697 move_pattern == MV_TURNING_RIGHT ||
7698 move_pattern == MV_TURNING_LEFT_RIGHT ||
7699 move_pattern == MV_TURNING_RIGHT_LEFT ||
7700 move_pattern == MV_TURNING_RANDOM ||
7701 move_pattern == MV_ALL_DIRECTIONS)
7703 boolean can_turn_left =
7704 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7705 boolean can_turn_right =
7706 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y);
7708 if (element_info[element].move_stepsize == 0) // "not moving"
7711 if (move_pattern == MV_TURNING_LEFT)
7712 MovDir[x][y] = left_dir;
7713 else if (move_pattern == MV_TURNING_RIGHT)
7714 MovDir[x][y] = right_dir;
7715 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7716 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7717 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7718 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7719 else if (move_pattern == MV_TURNING_RANDOM)
7720 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7721 can_turn_right && !can_turn_left ? right_dir :
7722 RND(2) ? left_dir : right_dir);
7723 else if (can_turn_left && can_turn_right)
7724 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7725 else if (can_turn_left)
7726 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7727 else if (can_turn_right)
7728 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7730 MovDir[x][y] = back_dir;
7732 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7734 else if (move_pattern == MV_HORIZONTAL ||
7735 move_pattern == MV_VERTICAL)
7737 if (move_pattern & old_move_dir)
7738 MovDir[x][y] = back_dir;
7739 else if (move_pattern == MV_HORIZONTAL)
7740 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7741 else if (move_pattern == MV_VERTICAL)
7742 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7744 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7746 else if (move_pattern & MV_ANY_DIRECTION)
7748 MovDir[x][y] = move_pattern;
7749 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7751 else if (move_pattern & MV_WIND_DIRECTION)
7753 MovDir[x][y] = game.wind_direction;
7754 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7756 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7758 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7759 MovDir[x][y] = left_dir;
7760 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7761 MovDir[x][y] = right_dir;
7763 if (MovDir[x][y] != old_move_dir)
7764 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7766 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7768 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7769 MovDir[x][y] = right_dir;
7770 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7771 MovDir[x][y] = left_dir;
7773 if (MovDir[x][y] != old_move_dir)
7774 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7776 else if (move_pattern == MV_TOWARDS_PLAYER ||
7777 move_pattern == MV_AWAY_FROM_PLAYER)
7779 int attr_x = -1, attr_y = -1;
7781 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7783 if (game.all_players_gone)
7785 attr_x = game.exit_x;
7786 attr_y = game.exit_y;
7792 for (i = 0; i < MAX_PLAYERS; i++)
7794 struct PlayerInfo *player = &stored_player[i];
7795 int jx = player->jx, jy = player->jy;
7797 if (!player->active)
7801 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7809 MovDir[x][y] = MV_NONE;
7811 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7812 else if (attr_x > x)
7813 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7815 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7816 else if (attr_y > y)
7817 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7819 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7821 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7823 boolean first_horiz = RND(2);
7824 int new_move_dir = MovDir[x][y];
7826 if (element_info[element].move_stepsize == 0) // "not moving"
7828 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7829 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7835 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7836 Moving2Blocked(x, y, &newx, &newy);
7838 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7842 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7843 Moving2Blocked(x, y, &newx, &newy);
7845 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7848 MovDir[x][y] = old_move_dir;
7851 else if (move_pattern == MV_WHEN_PUSHED ||
7852 move_pattern == MV_WHEN_DROPPED)
7854 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7855 MovDir[x][y] = MV_NONE;
7859 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7861 struct XY *test_xy = xy_topdown;
7862 static int test_dir[4] =
7869 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7870 int move_preference = -1000000; // start with very low preference
7871 int new_move_dir = MV_NONE;
7872 int start_test = RND(4);
7875 for (i = 0; i < NUM_DIRECTIONS; i++)
7877 int j = (start_test + i) % 4;
7878 int move_dir = test_dir[j];
7879 int move_dir_preference;
7881 xx = x + test_xy[j].x;
7882 yy = y + test_xy[j].y;
7884 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7885 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7887 new_move_dir = move_dir;
7892 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7895 move_dir_preference = -1 * RunnerVisit[xx][yy];
7896 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7897 move_dir_preference = PlayerVisit[xx][yy];
7899 if (move_dir_preference > move_preference)
7901 // prefer field that has not been visited for the longest time
7902 move_preference = move_dir_preference;
7903 new_move_dir = move_dir;
7905 else if (move_dir_preference == move_preference &&
7906 move_dir == old_move_dir)
7908 // prefer last direction when all directions are preferred equally
7909 move_preference = move_dir_preference;
7910 new_move_dir = move_dir;
7914 MovDir[x][y] = new_move_dir;
7915 if (old_move_dir != new_move_dir)
7916 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7920 static void TurnRound(int x, int y)
7922 int direction = MovDir[x][y];
7926 GfxDir[x][y] = MovDir[x][y];
7928 if (direction != MovDir[x][y])
7932 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7934 ResetGfxFrame(x, y);
7937 static boolean JustBeingPushed(int x, int y)
7941 for (i = 0; i < MAX_PLAYERS; i++)
7943 struct PlayerInfo *player = &stored_player[i];
7945 if (player->active && player->is_pushing && player->MovPos)
7947 int next_jx = player->jx + (player->jx - player->last_jx);
7948 int next_jy = player->jy + (player->jy - player->last_jy);
7950 if (x == next_jx && y == next_jy)
7958 static void StartMoving(int x, int y)
7960 boolean started_moving = FALSE; // some elements can fall _and_ move
7961 int element = Tile[x][y];
7966 if (MovDelay[x][y] == 0)
7967 GfxAction[x][y] = ACTION_DEFAULT;
7969 if (CAN_FALL(element) && y < lev_fieldy - 1)
7971 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7972 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7973 if (JustBeingPushed(x, y))
7976 if (element == EL_QUICKSAND_FULL)
7978 if (IS_FREE(x, y + 1))
7980 InitMovingField(x, y, MV_DOWN);
7981 started_moving = TRUE;
7983 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7984 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7985 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7986 Store[x][y] = EL_ROCK;
7988 Store[x][y] = EL_ROCK;
7991 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7993 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7995 if (!MovDelay[x][y])
7997 MovDelay[x][y] = TILEY + 1;
7999 ResetGfxAnimation(x, y);
8000 ResetGfxAnimation(x, y + 1);
8005 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
8006 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8013 Tile[x][y] = EL_QUICKSAND_EMPTY;
8014 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8015 Store[x][y + 1] = Store[x][y];
8018 PlayLevelSoundAction(x, y, ACTION_FILLING);
8020 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8022 if (!MovDelay[x][y])
8024 MovDelay[x][y] = TILEY + 1;
8026 ResetGfxAnimation(x, y);
8027 ResetGfxAnimation(x, y + 1);
8032 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
8033 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8040 Tile[x][y] = EL_QUICKSAND_EMPTY;
8041 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8042 Store[x][y + 1] = Store[x][y];
8045 PlayLevelSoundAction(x, y, ACTION_FILLING);
8048 else if (element == EL_QUICKSAND_FAST_FULL)
8050 if (IS_FREE(x, y + 1))
8052 InitMovingField(x, y, MV_DOWN);
8053 started_moving = TRUE;
8055 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
8056 #if USE_QUICKSAND_BD_ROCK_BUGFIX
8057 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
8058 Store[x][y] = EL_ROCK;
8060 Store[x][y] = EL_ROCK;
8063 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
8065 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8067 if (!MovDelay[x][y])
8069 MovDelay[x][y] = TILEY + 1;
8071 ResetGfxAnimation(x, y);
8072 ResetGfxAnimation(x, y + 1);
8077 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8078 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8085 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8086 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8087 Store[x][y + 1] = Store[x][y];
8090 PlayLevelSoundAction(x, y, ACTION_FILLING);
8092 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8094 if (!MovDelay[x][y])
8096 MovDelay[x][y] = TILEY + 1;
8098 ResetGfxAnimation(x, y);
8099 ResetGfxAnimation(x, y + 1);
8104 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8105 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8112 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8113 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8114 Store[x][y + 1] = Store[x][y];
8117 PlayLevelSoundAction(x, y, ACTION_FILLING);
8120 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8121 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8123 InitMovingField(x, y, MV_DOWN);
8124 started_moving = TRUE;
8126 Tile[x][y] = EL_QUICKSAND_FILLING;
8127 Store[x][y] = element;
8129 PlayLevelSoundAction(x, y, ACTION_FILLING);
8131 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8132 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8134 InitMovingField(x, y, MV_DOWN);
8135 started_moving = TRUE;
8137 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
8138 Store[x][y] = element;
8140 PlayLevelSoundAction(x, y, ACTION_FILLING);
8142 else if (element == EL_MAGIC_WALL_FULL)
8144 if (IS_FREE(x, y + 1))
8146 InitMovingField(x, y, MV_DOWN);
8147 started_moving = TRUE;
8149 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
8150 Store[x][y] = EL_CHANGED(Store[x][y]);
8152 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
8154 if (!MovDelay[x][y])
8155 MovDelay[x][y] = TILEY / 4 + 1;
8164 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
8165 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
8166 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8170 else if (element == EL_BD_MAGIC_WALL_FULL)
8172 if (IS_FREE(x, y + 1))
8174 InitMovingField(x, y, MV_DOWN);
8175 started_moving = TRUE;
8177 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8178 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8180 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8182 if (!MovDelay[x][y])
8183 MovDelay[x][y] = TILEY / 4 + 1;
8192 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8193 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8194 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8198 else if (element == EL_DC_MAGIC_WALL_FULL)
8200 if (IS_FREE(x, y + 1))
8202 InitMovingField(x, y, MV_DOWN);
8203 started_moving = TRUE;
8205 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8206 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8208 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8210 if (!MovDelay[x][y])
8211 MovDelay[x][y] = TILEY / 4 + 1;
8220 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8221 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8222 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8226 else if ((CAN_PASS_MAGIC_WALL(element) &&
8227 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8228 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8229 (CAN_PASS_DC_MAGIC_WALL(element) &&
8230 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8233 InitMovingField(x, y, MV_DOWN);
8234 started_moving = TRUE;
8237 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8238 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8239 EL_DC_MAGIC_WALL_FILLING);
8240 Store[x][y] = element;
8242 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8244 SplashAcid(x, y + 1);
8246 InitMovingField(x, y, MV_DOWN);
8247 started_moving = TRUE;
8249 Store[x][y] = EL_ACID;
8252 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8253 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8254 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8255 CAN_FALL(element) && WasJustFalling[x][y] &&
8256 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8258 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8259 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8260 (Tile[x][y + 1] == EL_BLOCKED)))
8262 /* this is needed for a special case not covered by calling "Impact()"
8263 from "ContinueMoving()": if an element moves to a tile directly below
8264 another element which was just falling on that tile (which was empty
8265 in the previous frame), the falling element above would just stop
8266 instead of smashing the element below (in previous version, the above
8267 element was just checked for "moving" instead of "falling", resulting
8268 in incorrect smashes caused by horizontal movement of the above
8269 element; also, the case of the player being the element to smash was
8270 simply not covered here... :-/ ) */
8272 CheckCollision[x][y] = 0;
8273 CheckImpact[x][y] = 0;
8277 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8279 if (MovDir[x][y] == MV_NONE)
8281 InitMovingField(x, y, MV_DOWN);
8282 started_moving = TRUE;
8285 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8287 if (WasJustFalling[x][y]) // prevent animation from being restarted
8288 MovDir[x][y] = MV_DOWN;
8290 InitMovingField(x, y, MV_DOWN);
8291 started_moving = TRUE;
8293 else if (element == EL_AMOEBA_DROP)
8295 Tile[x][y] = EL_AMOEBA_GROWING;
8296 Store[x][y] = EL_AMOEBA_WET;
8298 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8299 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8300 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8301 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8303 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8304 (IS_FREE(x - 1, y + 1) ||
8305 Tile[x - 1][y + 1] == EL_ACID));
8306 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8307 (IS_FREE(x + 1, y + 1) ||
8308 Tile[x + 1][y + 1] == EL_ACID));
8309 boolean can_fall_any = (can_fall_left || can_fall_right);
8310 boolean can_fall_both = (can_fall_left && can_fall_right);
8311 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8313 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8315 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8316 can_fall_right = FALSE;
8317 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8318 can_fall_left = FALSE;
8319 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8320 can_fall_right = FALSE;
8321 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8322 can_fall_left = FALSE;
8324 can_fall_any = (can_fall_left || can_fall_right);
8325 can_fall_both = FALSE;
8330 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8331 can_fall_right = FALSE; // slip down on left side
8333 can_fall_left = !(can_fall_right = RND(2));
8335 can_fall_both = FALSE;
8340 // if not determined otherwise, prefer left side for slipping down
8341 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8342 started_moving = TRUE;
8345 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8347 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8348 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8349 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8350 int belt_dir = game.belt_dir[belt_nr];
8352 if ((belt_dir == MV_LEFT && left_is_free) ||
8353 (belt_dir == MV_RIGHT && right_is_free))
8355 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8357 InitMovingField(x, y, belt_dir);
8358 started_moving = TRUE;
8360 Pushed[x][y] = TRUE;
8361 Pushed[nextx][y] = TRUE;
8363 GfxAction[x][y] = ACTION_DEFAULT;
8367 MovDir[x][y] = 0; // if element was moving, stop it
8372 // not "else if" because of elements that can fall and move (EL_SPRING)
8373 if (CAN_MOVE(element) && !started_moving)
8375 int move_pattern = element_info[element].move_pattern;
8378 Moving2Blocked(x, y, &newx, &newy);
8380 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8383 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8384 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8386 WasJustMoving[x][y] = 0;
8387 CheckCollision[x][y] = 0;
8389 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8391 if (Tile[x][y] != element) // element has changed
8395 if (!MovDelay[x][y]) // start new movement phase
8397 // all objects that can change their move direction after each step
8398 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8400 if (element != EL_YAMYAM &&
8401 element != EL_DARK_YAMYAM &&
8402 element != EL_PACMAN &&
8403 !(move_pattern & MV_ANY_DIRECTION) &&
8404 move_pattern != MV_TURNING_LEFT &&
8405 move_pattern != MV_TURNING_RIGHT &&
8406 move_pattern != MV_TURNING_LEFT_RIGHT &&
8407 move_pattern != MV_TURNING_RIGHT_LEFT &&
8408 move_pattern != MV_TURNING_RANDOM)
8412 if (MovDelay[x][y] && (element == EL_BUG ||
8413 element == EL_SPACESHIP ||
8414 element == EL_SP_SNIKSNAK ||
8415 element == EL_SP_ELECTRON ||
8416 element == EL_MOLE))
8417 TEST_DrawLevelField(x, y);
8421 if (MovDelay[x][y]) // wait some time before next movement
8425 if (element == EL_ROBOT ||
8426 element == EL_YAMYAM ||
8427 element == EL_DARK_YAMYAM)
8429 DrawLevelElementAnimationIfNeeded(x, y, element);
8430 PlayLevelSoundAction(x, y, ACTION_WAITING);
8432 else if (element == EL_SP_ELECTRON)
8433 DrawLevelElementAnimationIfNeeded(x, y, element);
8434 else if (element == EL_DRAGON)
8437 int dir = MovDir[x][y];
8438 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8439 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8440 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8441 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8442 dir == MV_UP ? IMG_FLAMES_1_UP :
8443 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8444 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8446 GfxAction[x][y] = ACTION_ATTACKING;
8448 if (IS_PLAYER(x, y))
8449 DrawPlayerField(x, y);
8451 TEST_DrawLevelField(x, y);
8453 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8455 for (i = 1; i <= 3; i++)
8457 int xx = x + i * dx;
8458 int yy = y + i * dy;
8459 int sx = SCREENX(xx);
8460 int sy = SCREENY(yy);
8461 int flame_graphic = graphic + (i - 1);
8463 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8468 int flamed = MovingOrBlocked2Element(xx, yy);
8470 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8473 RemoveMovingField(xx, yy);
8475 ChangeDelay[xx][yy] = 0;
8477 Tile[xx][yy] = EL_FLAMES;
8479 if (IN_SCR_FIELD(sx, sy))
8481 TEST_DrawLevelFieldCrumbled(xx, yy);
8482 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8487 if (Tile[xx][yy] == EL_FLAMES)
8488 Tile[xx][yy] = EL_EMPTY;
8489 TEST_DrawLevelField(xx, yy);
8494 if (MovDelay[x][y]) // element still has to wait some time
8496 PlayLevelSoundAction(x, y, ACTION_WAITING);
8502 // now make next step
8504 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8506 if (DONT_COLLIDE_WITH(element) &&
8507 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8508 !PLAYER_ENEMY_PROTECTED(newx, newy))
8510 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8515 else if (CAN_MOVE_INTO_ACID(element) &&
8516 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8517 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8518 (MovDir[x][y] == MV_DOWN ||
8519 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8521 SplashAcid(newx, newy);
8522 Store[x][y] = EL_ACID;
8524 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8526 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8527 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8528 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8529 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8532 TEST_DrawLevelField(x, y);
8534 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8535 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8536 DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0);
8538 game.friends_still_needed--;
8539 if (!game.friends_still_needed &&
8541 game.all_players_gone)
8546 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8548 if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING)
8549 TEST_DrawLevelField(newx, newy);
8551 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8553 else if (!IS_FREE(newx, newy))
8555 GfxAction[x][y] = ACTION_WAITING;
8557 if (IS_PLAYER(x, y))
8558 DrawPlayerField(x, y);
8560 TEST_DrawLevelField(x, y);
8565 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8567 if (IS_FOOD_PIG(Tile[newx][newy]))
8569 if (IS_MOVING(newx, newy))
8570 RemoveMovingField(newx, newy);
8573 Tile[newx][newy] = EL_EMPTY;
8574 TEST_DrawLevelField(newx, newy);
8577 PlayLevelSound(x, y, SND_PIG_DIGGING);
8579 else if (!IS_FREE(newx, newy))
8581 if (IS_PLAYER(x, y))
8582 DrawPlayerField(x, y);
8584 TEST_DrawLevelField(x, y);
8589 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8591 if (Store[x][y] != EL_EMPTY)
8593 boolean can_clone = FALSE;
8596 // check if element to clone is still there
8597 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8599 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8607 // cannot clone or target field not free anymore -- do not clone
8608 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8609 Store[x][y] = EL_EMPTY;
8612 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8614 if (IS_MV_DIAGONAL(MovDir[x][y]))
8616 int diagonal_move_dir = MovDir[x][y];
8617 int stored = Store[x][y];
8618 int change_delay = 8;
8621 // android is moving diagonally
8623 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8625 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8626 GfxElement[x][y] = EL_EMC_ANDROID;
8627 GfxAction[x][y] = ACTION_SHRINKING;
8628 GfxDir[x][y] = diagonal_move_dir;
8629 ChangeDelay[x][y] = change_delay;
8631 if (Store[x][y] == EL_EMPTY)
8632 Store[x][y] = GfxElementEmpty[x][y];
8634 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8637 DrawLevelGraphicAnimation(x, y, graphic);
8638 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8640 if (Tile[newx][newy] == EL_ACID)
8642 SplashAcid(newx, newy);
8647 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8649 Store[newx][newy] = EL_EMC_ANDROID;
8650 GfxElement[newx][newy] = EL_EMC_ANDROID;
8651 GfxAction[newx][newy] = ACTION_GROWING;
8652 GfxDir[newx][newy] = diagonal_move_dir;
8653 ChangeDelay[newx][newy] = change_delay;
8655 graphic = el_act_dir2img(GfxElement[newx][newy],
8656 GfxAction[newx][newy], GfxDir[newx][newy]);
8658 DrawLevelGraphicAnimation(newx, newy, graphic);
8659 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8665 Tile[newx][newy] = EL_EMPTY;
8666 TEST_DrawLevelField(newx, newy);
8668 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8671 else if (!IS_FREE(newx, newy))
8676 else if (IS_CUSTOM_ELEMENT(element) &&
8677 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8679 if (!DigFieldByCE(newx, newy, element))
8682 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8684 RunnerVisit[x][y] = FrameCounter;
8685 PlayerVisit[x][y] /= 8; // expire player visit path
8688 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8690 if (!IS_FREE(newx, newy))
8692 if (IS_PLAYER(x, y))
8693 DrawPlayerField(x, y);
8695 TEST_DrawLevelField(x, y);
8701 boolean wanna_flame = !RND(10);
8702 int dx = newx - x, dy = newy - y;
8703 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8704 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8705 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8706 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8707 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8708 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8711 IS_CLASSIC_ENEMY(element1) ||
8712 IS_CLASSIC_ENEMY(element2)) &&
8713 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8714 element1 != EL_FLAMES && element2 != EL_FLAMES)
8716 ResetGfxAnimation(x, y);
8717 GfxAction[x][y] = ACTION_ATTACKING;
8719 if (IS_PLAYER(x, y))
8720 DrawPlayerField(x, y);
8722 TEST_DrawLevelField(x, y);
8724 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8726 MovDelay[x][y] = 50;
8728 Tile[newx][newy] = EL_FLAMES;
8729 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8730 Tile[newx1][newy1] = EL_FLAMES;
8731 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8732 Tile[newx2][newy2] = EL_FLAMES;
8738 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8739 Tile[newx][newy] == EL_DIAMOND)
8741 if (IS_MOVING(newx, newy))
8742 RemoveMovingField(newx, newy);
8745 Tile[newx][newy] = EL_EMPTY;
8746 TEST_DrawLevelField(newx, newy);
8749 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8751 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8752 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8754 if (AmoebaNr[newx][newy])
8756 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8757 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8758 Tile[newx][newy] == EL_BD_AMOEBA)
8759 AmoebaCnt[AmoebaNr[newx][newy]]--;
8762 if (IS_MOVING(newx, newy))
8764 RemoveMovingField(newx, newy);
8768 Tile[newx][newy] = EL_EMPTY;
8769 TEST_DrawLevelField(newx, newy);
8772 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8774 else if ((element == EL_PACMAN || element == EL_MOLE)
8775 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8777 if (AmoebaNr[newx][newy])
8779 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8780 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8781 Tile[newx][newy] == EL_BD_AMOEBA)
8782 AmoebaCnt[AmoebaNr[newx][newy]]--;
8785 if (element == EL_MOLE)
8787 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8788 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8790 ResetGfxAnimation(x, y);
8791 GfxAction[x][y] = ACTION_DIGGING;
8792 TEST_DrawLevelField(x, y);
8794 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8796 return; // wait for shrinking amoeba
8798 else // element == EL_PACMAN
8800 Tile[newx][newy] = EL_EMPTY;
8801 TEST_DrawLevelField(newx, newy);
8802 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8805 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8806 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8807 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8809 // wait for shrinking amoeba to completely disappear
8812 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8814 // object was running against a wall
8818 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8819 DrawLevelElementAnimation(x, y, element);
8821 if (DONT_TOUCH(element))
8822 TestIfBadThingTouchesPlayer(x, y);
8827 InitMovingField(x, y, MovDir[x][y]);
8829 PlayLevelSoundAction(x, y, ACTION_MOVING);
8833 ContinueMoving(x, y);
8836 void ContinueMoving(int x, int y)
8838 int element = Tile[x][y];
8839 struct ElementInfo *ei = &element_info[element];
8840 int direction = MovDir[x][y];
8841 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8842 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8843 int newx = x + dx, newy = y + dy;
8844 int stored = Store[x][y];
8845 int stored_new = Store[newx][newy];
8846 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8847 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8848 boolean last_line = (newy == lev_fieldy - 1);
8849 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8851 if (pushed_by_player) // special case: moving object pushed by player
8853 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos));
8855 else if (use_step_delay) // special case: moving object has step delay
8857 if (!MovDelay[x][y])
8858 MovPos[x][y] += getElementMoveStepsize(x, y);
8863 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8867 TEST_DrawLevelField(x, y);
8869 return; // element is still waiting
8872 else // normal case: generically moving object
8874 MovPos[x][y] += getElementMoveStepsize(x, y);
8877 if (ABS(MovPos[x][y]) < TILEX)
8879 TEST_DrawLevelField(x, y);
8881 return; // element is still moving
8884 // element reached destination field
8886 Tile[x][y] = EL_EMPTY;
8887 Tile[newx][newy] = element;
8888 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8890 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8892 element = Tile[newx][newy] = EL_ACID;
8894 else if (element == EL_MOLE)
8896 Tile[x][y] = EL_SAND;
8898 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8900 else if (element == EL_QUICKSAND_FILLING)
8902 element = Tile[newx][newy] = get_next_element(element);
8903 Store[newx][newy] = Store[x][y];
8905 else if (element == EL_QUICKSAND_EMPTYING)
8907 Tile[x][y] = get_next_element(element);
8908 element = Tile[newx][newy] = Store[x][y];
8910 else if (element == EL_QUICKSAND_FAST_FILLING)
8912 element = Tile[newx][newy] = get_next_element(element);
8913 Store[newx][newy] = Store[x][y];
8915 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8917 Tile[x][y] = get_next_element(element);
8918 element = Tile[newx][newy] = Store[x][y];
8920 else if (element == EL_MAGIC_WALL_FILLING)
8922 element = Tile[newx][newy] = get_next_element(element);
8923 if (!game.magic_wall_active)
8924 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8925 Store[newx][newy] = Store[x][y];
8927 else if (element == EL_MAGIC_WALL_EMPTYING)
8929 Tile[x][y] = get_next_element(element);
8930 if (!game.magic_wall_active)
8931 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8932 element = Tile[newx][newy] = Store[x][y];
8934 InitField(newx, newy, FALSE);
8936 else if (element == EL_BD_MAGIC_WALL_FILLING)
8938 element = Tile[newx][newy] = get_next_element(element);
8939 if (!game.magic_wall_active)
8940 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8941 Store[newx][newy] = Store[x][y];
8943 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8945 Tile[x][y] = get_next_element(element);
8946 if (!game.magic_wall_active)
8947 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8948 element = Tile[newx][newy] = Store[x][y];
8950 InitField(newx, newy, FALSE);
8952 else if (element == EL_DC_MAGIC_WALL_FILLING)
8954 element = Tile[newx][newy] = get_next_element(element);
8955 if (!game.magic_wall_active)
8956 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8957 Store[newx][newy] = Store[x][y];
8959 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8961 Tile[x][y] = get_next_element(element);
8962 if (!game.magic_wall_active)
8963 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8964 element = Tile[newx][newy] = Store[x][y];
8966 InitField(newx, newy, FALSE);
8968 else if (element == EL_AMOEBA_DROPPING)
8970 Tile[x][y] = get_next_element(element);
8971 element = Tile[newx][newy] = Store[x][y];
8973 else if (element == EL_SOKOBAN_OBJECT)
8976 Tile[x][y] = Back[x][y];
8978 if (Back[newx][newy])
8979 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8981 Back[x][y] = Back[newx][newy] = 0;
8984 Store[x][y] = EL_EMPTY;
8989 MovDelay[newx][newy] = 0;
8991 if (CAN_CHANGE_OR_HAS_ACTION(element))
8993 // copy element change control values to new field
8994 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8995 ChangePage[newx][newy] = ChangePage[x][y];
8996 ChangeCount[newx][newy] = ChangeCount[x][y];
8997 ChangeEvent[newx][newy] = ChangeEvent[x][y];
9000 CustomValue[newx][newy] = CustomValue[x][y];
9002 ChangeDelay[x][y] = 0;
9003 ChangePage[x][y] = -1;
9004 ChangeCount[x][y] = 0;
9005 ChangeEvent[x][y] = -1;
9007 CustomValue[x][y] = 0;
9009 // copy animation control values to new field
9010 GfxFrame[newx][newy] = GfxFrame[x][y];
9011 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
9012 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
9013 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
9015 Pushed[x][y] = Pushed[newx][newy] = FALSE;
9017 // some elements can leave other elements behind after moving
9018 if (ei->move_leave_element != EL_EMPTY &&
9019 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
9020 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
9022 int move_leave_element = ei->move_leave_element;
9024 // this makes it possible to leave the removed element again
9025 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
9026 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
9028 Tile[x][y] = move_leave_element;
9030 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
9031 MovDir[x][y] = direction;
9033 InitField(x, y, FALSE);
9035 if (GFX_CRUMBLED(Tile[x][y]))
9036 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
9038 if (IS_PLAYER_ELEMENT(move_leave_element))
9039 RelocatePlayer(x, y, move_leave_element);
9042 // do this after checking for left-behind element
9043 ResetGfxAnimation(x, y); // reset animation values for old field
9045 if (!CAN_MOVE(element) ||
9046 (CAN_FALL(element) && direction == MV_DOWN &&
9047 (element == EL_SPRING ||
9048 element_info[element].move_pattern == MV_WHEN_PUSHED ||
9049 element_info[element].move_pattern == MV_WHEN_DROPPED)))
9050 GfxDir[x][y] = MovDir[newx][newy] = 0;
9052 TEST_DrawLevelField(x, y);
9053 TEST_DrawLevelField(newx, newy);
9055 Stop[newx][newy] = TRUE; // ignore this element until the next frame
9057 // prevent pushed element from moving on in pushed direction
9058 if (pushed_by_player && CAN_MOVE(element) &&
9059 element_info[element].move_pattern & MV_ANY_DIRECTION &&
9060 !(element_info[element].move_pattern & direction))
9061 TurnRound(newx, newy);
9063 // prevent elements on conveyor belt from moving on in last direction
9064 if (pushed_by_conveyor && CAN_FALL(element) &&
9065 direction & MV_HORIZONTAL)
9066 MovDir[newx][newy] = 0;
9068 if (!pushed_by_player)
9070 int nextx = newx + dx, nexty = newy + dy;
9071 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
9073 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
9075 if (CAN_FALL(element) && direction == MV_DOWN)
9076 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
9078 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
9079 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
9081 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
9082 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
9085 if (DONT_TOUCH(element)) // object may be nasty to player or others
9087 TestIfBadThingTouchesPlayer(newx, newy);
9088 TestIfBadThingTouchesFriend(newx, newy);
9090 if (!IS_CUSTOM_ELEMENT(element))
9091 TestIfBadThingTouchesOtherBadThing(newx, newy);
9093 else if (element == EL_PENGUIN)
9094 TestIfFriendTouchesBadThing(newx, newy);
9096 if (DONT_GET_HIT_BY(element))
9098 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
9101 // give the player one last chance (one more frame) to move away
9102 if (CAN_FALL(element) && direction == MV_DOWN &&
9103 (last_line || (!IS_FREE(x, newy + 1) &&
9104 (!IS_PLAYER(x, newy + 1) ||
9105 game.engine_version < VERSION_IDENT(3,1,1,0)))))
9108 if (pushed_by_player && !game.use_change_when_pushing_bug)
9110 int push_side = MV_DIR_OPPOSITE(direction);
9111 struct PlayerInfo *player = PLAYERINFO(x, y);
9113 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
9114 player->index_bit, push_side);
9115 CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X,
9116 player->index_bit, push_side);
9119 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
9120 MovDelay[newx][newy] = 1;
9122 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
9124 TestIfElementTouchesCustomElement(x, y); // empty or new element
9125 TestIfElementHitsCustomElement(newx, newy, direction);
9126 TestIfPlayerTouchesCustomElement(newx, newy);
9127 TestIfElementTouchesCustomElement(newx, newy);
9129 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
9130 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
9131 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
9132 MV_DIR_OPPOSITE(direction));
9135 int AmoebaNeighbourNr(int ax, int ay)
9138 int element = Tile[ax][ay];
9140 struct XY *xy = xy_topdown;
9142 for (i = 0; i < NUM_DIRECTIONS; i++)
9144 int x = ax + xy[i].x;
9145 int y = ay + xy[i].y;
9147 if (!IN_LEV_FIELD(x, y))
9150 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
9151 group_nr = AmoebaNr[x][y];
9157 static void AmoebaMerge(int ax, int ay)
9159 int i, x, y, xx, yy;
9160 int new_group_nr = AmoebaNr[ax][ay];
9161 struct XY *xy = xy_topdown;
9163 if (new_group_nr == 0)
9166 for (i = 0; i < NUM_DIRECTIONS; i++)
9171 if (!IN_LEV_FIELD(x, y))
9174 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9175 Tile[x][y] == EL_BD_AMOEBA ||
9176 Tile[x][y] == EL_AMOEBA_DEAD) &&
9177 AmoebaNr[x][y] != new_group_nr)
9179 int old_group_nr = AmoebaNr[x][y];
9181 if (old_group_nr == 0)
9184 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9185 AmoebaCnt[old_group_nr] = 0;
9186 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9187 AmoebaCnt2[old_group_nr] = 0;
9189 SCAN_PLAYFIELD(xx, yy)
9191 if (AmoebaNr[xx][yy] == old_group_nr)
9192 AmoebaNr[xx][yy] = new_group_nr;
9198 void AmoebaToDiamond(int ax, int ay)
9202 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9204 int group_nr = AmoebaNr[ax][ay];
9209 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9210 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9216 SCAN_PLAYFIELD(x, y)
9218 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9221 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9225 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9226 SND_AMOEBA_TURNING_TO_GEM :
9227 SND_AMOEBA_TURNING_TO_ROCK));
9232 struct XY *xy = xy_topdown;
9234 for (i = 0; i < NUM_DIRECTIONS; i++)
9239 if (!IN_LEV_FIELD(x, y))
9242 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9244 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9245 SND_AMOEBA_TURNING_TO_GEM :
9246 SND_AMOEBA_TURNING_TO_ROCK));
9253 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9256 int group_nr = AmoebaNr[ax][ay];
9257 boolean done = FALSE;
9262 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9263 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9269 SCAN_PLAYFIELD(x, y)
9271 if (AmoebaNr[x][y] == group_nr &&
9272 (Tile[x][y] == EL_AMOEBA_DEAD ||
9273 Tile[x][y] == EL_BD_AMOEBA ||
9274 Tile[x][y] == EL_AMOEBA_GROWING))
9277 Tile[x][y] = new_element;
9278 InitField(x, y, FALSE);
9279 TEST_DrawLevelField(x, y);
9285 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9286 SND_BD_AMOEBA_TURNING_TO_ROCK :
9287 SND_BD_AMOEBA_TURNING_TO_GEM));
9290 static void AmoebaGrowing(int x, int y)
9292 static DelayCounter sound_delay = { 0 };
9294 if (!MovDelay[x][y]) // start new growing cycle
9298 if (DelayReached(&sound_delay))
9300 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9301 sound_delay.value = 30;
9305 if (MovDelay[x][y]) // wait some time before growing bigger
9308 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9310 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9311 6 - MovDelay[x][y]);
9313 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9316 if (!MovDelay[x][y])
9318 Tile[x][y] = Store[x][y];
9320 TEST_DrawLevelField(x, y);
9325 static void AmoebaShrinking(int x, int y)
9327 static DelayCounter sound_delay = { 0 };
9329 if (!MovDelay[x][y]) // start new shrinking cycle
9333 if (DelayReached(&sound_delay))
9334 sound_delay.value = 30;
9337 if (MovDelay[x][y]) // wait some time before shrinking
9340 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9342 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9343 6 - MovDelay[x][y]);
9345 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9348 if (!MovDelay[x][y])
9350 Tile[x][y] = EL_EMPTY;
9351 TEST_DrawLevelField(x, y);
9353 // don't let mole enter this field in this cycle;
9354 // (give priority to objects falling to this field from above)
9360 static void AmoebaReproduce(int ax, int ay)
9363 int element = Tile[ax][ay];
9364 int graphic = el2img(element);
9365 int newax = ax, neway = ay;
9366 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9367 struct XY *xy = xy_topdown;
9369 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9371 Tile[ax][ay] = EL_AMOEBA_DEAD;
9372 TEST_DrawLevelField(ax, ay);
9376 if (IS_ANIMATED(graphic))
9377 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9379 if (!MovDelay[ax][ay]) // start making new amoeba field
9380 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9382 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9385 if (MovDelay[ax][ay])
9389 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9392 int x = ax + xy[start].x;
9393 int y = ay + xy[start].y;
9395 if (!IN_LEV_FIELD(x, y))
9398 if (IS_FREE(x, y) ||
9399 CAN_GROW_INTO(Tile[x][y]) ||
9400 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9401 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9407 if (newax == ax && neway == ay)
9410 else // normal or "filled" (BD style) amoeba
9413 boolean waiting_for_player = FALSE;
9415 for (i = 0; i < NUM_DIRECTIONS; i++)
9417 int j = (start + i) % 4;
9418 int x = ax + xy[j].x;
9419 int y = ay + xy[j].y;
9421 if (!IN_LEV_FIELD(x, y))
9424 if (IS_FREE(x, y) ||
9425 CAN_GROW_INTO(Tile[x][y]) ||
9426 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9427 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9433 else if (IS_PLAYER(x, y))
9434 waiting_for_player = TRUE;
9437 if (newax == ax && neway == ay) // amoeba cannot grow
9439 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9441 Tile[ax][ay] = EL_AMOEBA_DEAD;
9442 TEST_DrawLevelField(ax, ay);
9443 AmoebaCnt[AmoebaNr[ax][ay]]--;
9445 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9447 if (element == EL_AMOEBA_FULL)
9448 AmoebaToDiamond(ax, ay);
9449 else if (element == EL_BD_AMOEBA)
9450 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9455 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9457 // amoeba gets larger by growing in some direction
9459 int new_group_nr = AmoebaNr[ax][ay];
9462 if (new_group_nr == 0)
9464 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9466 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9472 AmoebaNr[newax][neway] = new_group_nr;
9473 AmoebaCnt[new_group_nr]++;
9474 AmoebaCnt2[new_group_nr]++;
9476 // if amoeba touches other amoeba(s) after growing, unify them
9477 AmoebaMerge(newax, neway);
9479 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9481 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9487 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9488 (neway == lev_fieldy - 1 && newax != ax))
9490 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9491 Store[newax][neway] = element;
9493 else if (neway == ay || element == EL_EMC_DRIPPER)
9495 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9497 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9501 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9502 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9503 Store[ax][ay] = EL_AMOEBA_DROP;
9504 ContinueMoving(ax, ay);
9508 TEST_DrawLevelField(newax, neway);
9511 static void Life(int ax, int ay)
9515 int element = Tile[ax][ay];
9516 int graphic = el2img(element);
9517 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9519 boolean changed = FALSE;
9521 if (IS_ANIMATED(graphic))
9522 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9527 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9528 MovDelay[ax][ay] = life_time;
9530 if (MovDelay[ax][ay]) // wait some time before next cycle
9533 if (MovDelay[ax][ay])
9537 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9539 int xx = ax + x1, yy = ay + y1;
9540 int old_element = Tile[xx][yy];
9541 int num_neighbours = 0;
9543 if (!IN_LEV_FIELD(xx, yy))
9546 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9548 int x = xx + x2, y = yy + y2;
9550 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9553 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9554 boolean is_neighbour = FALSE;
9556 if (level.use_life_bugs)
9558 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9559 (IS_FREE(x, y) && Stop[x][y]));
9562 (Last[x][y] == element || is_player_cell);
9568 boolean is_free = FALSE;
9570 if (level.use_life_bugs)
9571 is_free = (IS_FREE(xx, yy));
9573 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9575 if (xx == ax && yy == ay) // field in the middle
9577 if (num_neighbours < life_parameter[0] ||
9578 num_neighbours > life_parameter[1])
9580 Tile[xx][yy] = EL_EMPTY;
9581 if (Tile[xx][yy] != old_element)
9582 TEST_DrawLevelField(xx, yy);
9583 Stop[xx][yy] = TRUE;
9587 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9588 { // free border field
9589 if (num_neighbours >= life_parameter[2] &&
9590 num_neighbours <= life_parameter[3])
9592 Tile[xx][yy] = element;
9593 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9594 if (Tile[xx][yy] != old_element)
9595 TEST_DrawLevelField(xx, yy);
9596 Stop[xx][yy] = TRUE;
9603 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9604 SND_GAME_OF_LIFE_GROWING);
9607 static void InitRobotWheel(int x, int y)
9609 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9612 static void RunRobotWheel(int x, int y)
9614 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9617 static void StopRobotWheel(int x, int y)
9619 if (game.robot_wheel_x == x &&
9620 game.robot_wheel_y == y)
9622 game.robot_wheel_x = -1;
9623 game.robot_wheel_y = -1;
9624 game.robot_wheel_active = FALSE;
9628 static void InitTimegateWheel(int x, int y)
9630 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9633 static void RunTimegateWheel(int x, int y)
9635 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9638 static void InitMagicBallDelay(int x, int y)
9640 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9643 static void ActivateMagicBall(int bx, int by)
9647 if (level.ball_random)
9649 int pos_border = RND(8); // select one of the eight border elements
9650 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9651 int xx = pos_content % 3;
9652 int yy = pos_content / 3;
9657 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9658 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9662 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9664 int xx = x - bx + 1;
9665 int yy = y - by + 1;
9667 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9668 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9672 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9675 static void CheckExit(int x, int y)
9677 if (game.gems_still_needed > 0 ||
9678 game.sokoban_fields_still_needed > 0 ||
9679 game.sokoban_objects_still_needed > 0 ||
9680 game.lights_still_needed > 0)
9682 int element = Tile[x][y];
9683 int graphic = el2img(element);
9685 if (IS_ANIMATED(graphic))
9686 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9691 // do not re-open exit door closed after last player
9692 if (game.all_players_gone)
9695 Tile[x][y] = EL_EXIT_OPENING;
9697 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9700 static void CheckExitEM(int x, int y)
9702 if (game.gems_still_needed > 0 ||
9703 game.sokoban_fields_still_needed > 0 ||
9704 game.sokoban_objects_still_needed > 0 ||
9705 game.lights_still_needed > 0)
9707 int element = Tile[x][y];
9708 int graphic = el2img(element);
9710 if (IS_ANIMATED(graphic))
9711 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9716 // do not re-open exit door closed after last player
9717 if (game.all_players_gone)
9720 Tile[x][y] = EL_EM_EXIT_OPENING;
9722 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9725 static void CheckExitSteel(int x, int y)
9727 if (game.gems_still_needed > 0 ||
9728 game.sokoban_fields_still_needed > 0 ||
9729 game.sokoban_objects_still_needed > 0 ||
9730 game.lights_still_needed > 0)
9732 int element = Tile[x][y];
9733 int graphic = el2img(element);
9735 if (IS_ANIMATED(graphic))
9736 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9741 // do not re-open exit door closed after last player
9742 if (game.all_players_gone)
9745 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9747 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9750 static void CheckExitSteelEM(int x, int y)
9752 if (game.gems_still_needed > 0 ||
9753 game.sokoban_fields_still_needed > 0 ||
9754 game.sokoban_objects_still_needed > 0 ||
9755 game.lights_still_needed > 0)
9757 int element = Tile[x][y];
9758 int graphic = el2img(element);
9760 if (IS_ANIMATED(graphic))
9761 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9766 // do not re-open exit door closed after last player
9767 if (game.all_players_gone)
9770 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9772 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9775 static void CheckExitSP(int x, int y)
9777 if (game.gems_still_needed > 0)
9779 int element = Tile[x][y];
9780 int graphic = el2img(element);
9782 if (IS_ANIMATED(graphic))
9783 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9788 // do not re-open exit door closed after last player
9789 if (game.all_players_gone)
9792 Tile[x][y] = EL_SP_EXIT_OPENING;
9794 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9797 static void CloseAllOpenTimegates(void)
9801 SCAN_PLAYFIELD(x, y)
9803 int element = Tile[x][y];
9805 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9807 Tile[x][y] = EL_TIMEGATE_CLOSING;
9809 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9814 static void DrawTwinkleOnField(int x, int y)
9816 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9819 if (Tile[x][y] == EL_BD_DIAMOND)
9822 if (MovDelay[x][y] == 0) // next animation frame
9823 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9825 if (MovDelay[x][y] != 0) // wait some time before next frame
9829 DrawLevelElementAnimation(x, y, Tile[x][y]);
9831 if (MovDelay[x][y] != 0)
9833 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9834 10 - MovDelay[x][y]);
9836 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9841 static void WallGrowing(int x, int y)
9845 if (!MovDelay[x][y]) // next animation frame
9846 MovDelay[x][y] = 3 * delay;
9848 if (MovDelay[x][y]) // wait some time before next frame
9852 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9854 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9855 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9857 DrawLevelGraphic(x, y, graphic, frame);
9860 if (!MovDelay[x][y])
9862 if (MovDir[x][y] == MV_LEFT)
9864 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9865 TEST_DrawLevelField(x - 1, y);
9867 else if (MovDir[x][y] == MV_RIGHT)
9869 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9870 TEST_DrawLevelField(x + 1, y);
9872 else if (MovDir[x][y] == MV_UP)
9874 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9875 TEST_DrawLevelField(x, y - 1);
9879 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9880 TEST_DrawLevelField(x, y + 1);
9883 Tile[x][y] = Store[x][y];
9885 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9886 TEST_DrawLevelField(x, y);
9891 static void CheckWallGrowing(int ax, int ay)
9893 int element = Tile[ax][ay];
9894 int graphic = el2img(element);
9895 boolean free_top = FALSE;
9896 boolean free_bottom = FALSE;
9897 boolean free_left = FALSE;
9898 boolean free_right = FALSE;
9899 boolean stop_top = FALSE;
9900 boolean stop_bottom = FALSE;
9901 boolean stop_left = FALSE;
9902 boolean stop_right = FALSE;
9903 boolean new_wall = FALSE;
9905 boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9906 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9907 element == EL_EXPANDABLE_STEELWALL_ANY);
9909 boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9910 element == EL_EXPANDABLE_WALL_ANY ||
9911 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9912 element == EL_EXPANDABLE_STEELWALL_ANY);
9914 boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9915 element == EL_EXPANDABLE_WALL_ANY ||
9916 element == EL_EXPANDABLE_WALL ||
9917 element == EL_BD_EXPANDABLE_WALL ||
9918 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9919 element == EL_EXPANDABLE_STEELWALL_ANY);
9921 boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9922 element == EL_EXPANDABLE_STEELWALL_VERTICAL);
9924 boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9925 element == EL_EXPANDABLE_WALL ||
9926 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL);
9928 int wall_growing = (is_steelwall ?
9929 EL_EXPANDABLE_STEELWALL_GROWING :
9930 EL_EXPANDABLE_WALL_GROWING);
9932 int gfx_wall_growing_up = (is_steelwall ?
9933 IMG_EXPANDABLE_STEELWALL_GROWING_UP :
9934 IMG_EXPANDABLE_WALL_GROWING_UP);
9935 int gfx_wall_growing_down = (is_steelwall ?
9936 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN :
9937 IMG_EXPANDABLE_WALL_GROWING_DOWN);
9938 int gfx_wall_growing_left = (is_steelwall ?
9939 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT :
9940 IMG_EXPANDABLE_WALL_GROWING_LEFT);
9941 int gfx_wall_growing_right = (is_steelwall ?
9942 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT :
9943 IMG_EXPANDABLE_WALL_GROWING_RIGHT);
9945 if (IS_ANIMATED(graphic))
9946 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9948 if (!MovDelay[ax][ay]) // start building new wall
9949 MovDelay[ax][ay] = 6;
9951 if (MovDelay[ax][ay]) // wait some time before building new wall
9954 if (MovDelay[ax][ay])
9958 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9960 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9962 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9964 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9971 Tile[ax][ay - 1] = wall_growing;
9972 Store[ax][ay - 1] = element;
9973 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9975 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9976 DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0);
9983 Tile[ax][ay + 1] = wall_growing;
9984 Store[ax][ay + 1] = element;
9985 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9987 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9988 DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0);
9994 if (grow_horizontal)
9998 Tile[ax - 1][ay] = wall_growing;
9999 Store[ax - 1][ay] = element;
10000 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
10002 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
10003 DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0);
10010 Tile[ax + 1][ay] = wall_growing;
10011 Store[ax + 1][ay] = element;
10012 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
10014 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
10015 DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0);
10021 if (element == EL_EXPANDABLE_WALL && (free_left || free_right))
10022 TEST_DrawLevelField(ax, ay);
10024 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
10026 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
10027 stop_bottom = TRUE;
10028 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
10030 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
10033 if (((stop_top && stop_bottom) || stop_horizontal) &&
10034 ((stop_left && stop_right) || stop_vertical))
10035 Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL);
10038 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
10041 static void CheckForDragon(int x, int y)
10044 boolean dragon_found = FALSE;
10045 struct XY *xy = xy_topdown;
10047 for (i = 0; i < NUM_DIRECTIONS; i++)
10049 for (j = 0; j < 4; j++)
10051 int xx = x + j * xy[i].x;
10052 int yy = y + j * xy[i].y;
10054 if (IN_LEV_FIELD(xx, yy) &&
10055 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
10057 if (Tile[xx][yy] == EL_DRAGON)
10058 dragon_found = TRUE;
10067 for (i = 0; i < NUM_DIRECTIONS; i++)
10069 for (j = 0; j < 3; j++)
10071 int xx = x + j * xy[i].x;
10072 int yy = y + j * xy[i].y;
10074 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
10076 Tile[xx][yy] = EL_EMPTY;
10077 TEST_DrawLevelField(xx, yy);
10086 static void InitBuggyBase(int x, int y)
10088 int element = Tile[x][y];
10089 int activating_delay = FRAMES_PER_SECOND / 4;
10091 ChangeDelay[x][y] =
10092 (element == EL_SP_BUGGY_BASE ?
10093 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10094 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10096 element == EL_SP_BUGGY_BASE_ACTIVE ?
10097 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10100 static void WarnBuggyBase(int x, int y)
10103 struct XY *xy = xy_topdown;
10105 for (i = 0; i < NUM_DIRECTIONS; i++)
10107 int xx = x + xy[i].x;
10108 int yy = y + xy[i].y;
10110 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10112 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10119 static void InitTrap(int x, int y)
10121 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10124 static void ActivateTrap(int x, int y)
10126 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10129 static void ChangeActiveTrap(int x, int y)
10131 int graphic = IMG_TRAP_ACTIVE;
10133 // if new animation frame was drawn, correct crumbled sand border
10134 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10135 TEST_DrawLevelFieldCrumbled(x, y);
10138 static int getSpecialActionElement(int element, int number, int base_element)
10140 return (element != EL_EMPTY ? element :
10141 number != -1 ? base_element + number - 1 :
10145 static int getModifiedActionNumber(int value_old, int operator, int operand,
10146 int value_min, int value_max)
10148 int value_new = (operator == CA_MODE_SET ? operand :
10149 operator == CA_MODE_ADD ? value_old + operand :
10150 operator == CA_MODE_SUBTRACT ? value_old - operand :
10151 operator == CA_MODE_MULTIPLY ? value_old * operand :
10152 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10153 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10156 return (value_new < value_min ? value_min :
10157 value_new > value_max ? value_max :
10161 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10163 struct ElementInfo *ei = &element_info[element];
10164 struct ElementChangeInfo *change = &ei->change_page[page];
10165 int target_element = change->target_element;
10166 int action_type = change->action_type;
10167 int action_mode = change->action_mode;
10168 int action_arg = change->action_arg;
10169 int action_element = change->action_element;
10172 if (!change->has_action)
10175 // ---------- determine action paramater values -----------------------------
10177 int level_time_value =
10178 (level.time > 0 ? TimeLeft :
10181 int action_arg_element_raw =
10182 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10183 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10184 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10185 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10186 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10187 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10188 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10190 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10192 int action_arg_direction =
10193 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10194 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10195 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10196 change->actual_trigger_side :
10197 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10198 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10201 int action_arg_number_min =
10202 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10205 int action_arg_number_max =
10206 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10207 action_type == CA_SET_LEVEL_GEMS ? 999 :
10208 action_type == CA_SET_LEVEL_TIME ? 9999 :
10209 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10210 action_type == CA_SET_CE_VALUE ? 9999 :
10211 action_type == CA_SET_CE_SCORE ? 9999 :
10214 int action_arg_number_reset =
10215 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10216 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10217 action_type == CA_SET_LEVEL_TIME ? level.time :
10218 action_type == CA_SET_LEVEL_SCORE ? 0 :
10219 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10220 action_type == CA_SET_CE_SCORE ? 0 :
10223 int action_arg_number =
10224 (action_arg <= CA_ARG_MAX ? action_arg :
10225 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10226 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10227 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10228 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10229 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10230 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10231 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10232 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10233 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10234 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10235 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10236 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10237 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10238 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10239 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10240 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10241 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10242 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10243 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10244 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10245 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10248 int action_arg_number_old =
10249 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10250 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10251 action_type == CA_SET_LEVEL_SCORE ? game.score :
10252 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10253 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10256 int action_arg_number_new =
10257 getModifiedActionNumber(action_arg_number_old,
10258 action_mode, action_arg_number,
10259 action_arg_number_min, action_arg_number_max);
10261 int trigger_player_bits =
10262 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10263 change->actual_trigger_player_bits : change->trigger_player);
10265 int action_arg_player_bits =
10266 (action_arg >= CA_ARG_PLAYER_1 &&
10267 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10268 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10269 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10272 // ---------- execute action -----------------------------------------------
10274 switch (action_type)
10281 // ---------- level actions ----------------------------------------------
10283 case CA_RESTART_LEVEL:
10285 game.restart_level = TRUE;
10290 case CA_SHOW_ENVELOPE:
10292 int element = getSpecialActionElement(action_arg_element,
10293 action_arg_number, EL_ENVELOPE_1);
10295 if (IS_ENVELOPE(element))
10296 local_player->show_envelope = element;
10301 case CA_SET_LEVEL_TIME:
10303 if (level.time > 0) // only modify limited time value
10305 TimeLeft = action_arg_number_new;
10307 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10309 DisplayGameControlValues();
10311 if (!TimeLeft && game.time_limit)
10312 for (i = 0; i < MAX_PLAYERS; i++)
10313 KillPlayer(&stored_player[i]);
10319 case CA_SET_LEVEL_SCORE:
10321 game.score = action_arg_number_new;
10323 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10325 DisplayGameControlValues();
10330 case CA_SET_LEVEL_GEMS:
10332 game.gems_still_needed = action_arg_number_new;
10334 game.snapshot.collected_item = TRUE;
10336 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10338 DisplayGameControlValues();
10343 case CA_SET_LEVEL_WIND:
10345 game.wind_direction = action_arg_direction;
10350 case CA_SET_LEVEL_RANDOM_SEED:
10352 // ensure that setting a new random seed while playing is predictable
10353 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10358 // ---------- player actions ---------------------------------------------
10360 case CA_MOVE_PLAYER:
10361 case CA_MOVE_PLAYER_NEW:
10363 // automatically move to the next field in specified direction
10364 for (i = 0; i < MAX_PLAYERS; i++)
10365 if (trigger_player_bits & (1 << i))
10366 if (action_type == CA_MOVE_PLAYER ||
10367 stored_player[i].MovPos == 0)
10368 stored_player[i].programmed_action = action_arg_direction;
10373 case CA_EXIT_PLAYER:
10375 for (i = 0; i < MAX_PLAYERS; i++)
10376 if (action_arg_player_bits & (1 << i))
10377 ExitPlayer(&stored_player[i]);
10379 if (game.players_still_needed == 0)
10385 case CA_KILL_PLAYER:
10387 for (i = 0; i < MAX_PLAYERS; i++)
10388 if (action_arg_player_bits & (1 << i))
10389 KillPlayer(&stored_player[i]);
10394 case CA_SET_PLAYER_KEYS:
10396 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10397 int element = getSpecialActionElement(action_arg_element,
10398 action_arg_number, EL_KEY_1);
10400 if (IS_KEY(element))
10402 for (i = 0; i < MAX_PLAYERS; i++)
10404 if (trigger_player_bits & (1 << i))
10406 stored_player[i].key[KEY_NR(element)] = key_state;
10408 DrawGameDoorValues();
10416 case CA_SET_PLAYER_SPEED:
10418 for (i = 0; i < MAX_PLAYERS; i++)
10420 if (trigger_player_bits & (1 << i))
10422 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10424 if (action_arg == CA_ARG_SPEED_FASTER &&
10425 stored_player[i].cannot_move)
10427 action_arg_number = STEPSIZE_VERY_SLOW;
10429 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10430 action_arg == CA_ARG_SPEED_FASTER)
10432 action_arg_number = 2;
10433 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10436 else if (action_arg == CA_ARG_NUMBER_RESET)
10438 action_arg_number = level.initial_player_stepsize[i];
10442 getModifiedActionNumber(move_stepsize,
10445 action_arg_number_min,
10446 action_arg_number_max);
10448 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10455 case CA_SET_PLAYER_SHIELD:
10457 for (i = 0; i < MAX_PLAYERS; i++)
10459 if (trigger_player_bits & (1 << i))
10461 if (action_arg == CA_ARG_SHIELD_OFF)
10463 stored_player[i].shield_normal_time_left = 0;
10464 stored_player[i].shield_deadly_time_left = 0;
10466 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10468 stored_player[i].shield_normal_time_left = 999999;
10470 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10472 stored_player[i].shield_normal_time_left = 999999;
10473 stored_player[i].shield_deadly_time_left = 999999;
10481 case CA_SET_PLAYER_GRAVITY:
10483 for (i = 0; i < MAX_PLAYERS; i++)
10485 if (trigger_player_bits & (1 << i))
10487 stored_player[i].gravity =
10488 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10489 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10490 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10491 stored_player[i].gravity);
10498 case CA_SET_PLAYER_ARTWORK:
10500 for (i = 0; i < MAX_PLAYERS; i++)
10502 if (trigger_player_bits & (1 << i))
10504 int artwork_element = action_arg_element;
10506 if (action_arg == CA_ARG_ELEMENT_RESET)
10508 (level.use_artwork_element[i] ? level.artwork_element[i] :
10509 stored_player[i].element_nr);
10511 if (stored_player[i].artwork_element != artwork_element)
10512 stored_player[i].Frame = 0;
10514 stored_player[i].artwork_element = artwork_element;
10516 SetPlayerWaiting(&stored_player[i], FALSE);
10518 // set number of special actions for bored and sleeping animation
10519 stored_player[i].num_special_action_bored =
10520 get_num_special_action(artwork_element,
10521 ACTION_BORING_1, ACTION_BORING_LAST);
10522 stored_player[i].num_special_action_sleeping =
10523 get_num_special_action(artwork_element,
10524 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10531 case CA_SET_PLAYER_INVENTORY:
10533 for (i = 0; i < MAX_PLAYERS; i++)
10535 struct PlayerInfo *player = &stored_player[i];
10538 if (trigger_player_bits & (1 << i))
10540 int inventory_element = action_arg_element;
10542 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10543 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10544 action_arg == CA_ARG_ELEMENT_ACTION)
10546 int element = inventory_element;
10547 int collect_count = element_info[element].collect_count_initial;
10549 if (!IS_CUSTOM_ELEMENT(element))
10552 if (collect_count == 0)
10553 player->inventory_infinite_element = element;
10555 for (k = 0; k < collect_count; k++)
10556 if (player->inventory_size < MAX_INVENTORY_SIZE)
10557 player->inventory_element[player->inventory_size++] =
10560 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10561 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10562 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10564 if (player->inventory_infinite_element != EL_UNDEFINED &&
10565 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10566 action_arg_element_raw))
10567 player->inventory_infinite_element = EL_UNDEFINED;
10569 for (k = 0, j = 0; j < player->inventory_size; j++)
10571 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10572 action_arg_element_raw))
10573 player->inventory_element[k++] = player->inventory_element[j];
10576 player->inventory_size = k;
10578 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10580 if (player->inventory_size > 0)
10582 for (j = 0; j < player->inventory_size - 1; j++)
10583 player->inventory_element[j] = player->inventory_element[j + 1];
10585 player->inventory_size--;
10588 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10590 if (player->inventory_size > 0)
10591 player->inventory_size--;
10593 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10595 player->inventory_infinite_element = EL_UNDEFINED;
10596 player->inventory_size = 0;
10598 else if (action_arg == CA_ARG_INVENTORY_RESET)
10600 player->inventory_infinite_element = EL_UNDEFINED;
10601 player->inventory_size = 0;
10603 if (level.use_initial_inventory[i])
10605 for (j = 0; j < level.initial_inventory_size[i]; j++)
10607 int element = level.initial_inventory_content[i][j];
10608 int collect_count = element_info[element].collect_count_initial;
10610 if (!IS_CUSTOM_ELEMENT(element))
10613 if (collect_count == 0)
10614 player->inventory_infinite_element = element;
10616 for (k = 0; k < collect_count; k++)
10617 if (player->inventory_size < MAX_INVENTORY_SIZE)
10618 player->inventory_element[player->inventory_size++] =
10629 // ---------- CE actions -------------------------------------------------
10631 case CA_SET_CE_VALUE:
10633 int last_ce_value = CustomValue[x][y];
10635 CustomValue[x][y] = action_arg_number_new;
10637 if (CustomValue[x][y] != last_ce_value)
10639 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10640 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10642 if (CustomValue[x][y] == 0)
10644 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10645 ChangeCount[x][y] = 0; // allow at least one more change
10647 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10648 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10655 case CA_SET_CE_SCORE:
10657 int last_ce_score = ei->collect_score;
10659 ei->collect_score = action_arg_number_new;
10661 if (ei->collect_score != last_ce_score)
10663 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10664 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10666 if (ei->collect_score == 0)
10670 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10671 ChangeCount[x][y] = 0; // allow at least one more change
10673 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10674 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10677 This is a very special case that seems to be a mixture between
10678 CheckElementChange() and CheckTriggeredElementChange(): while
10679 the first one only affects single elements that are triggered
10680 directly, the second one affects multiple elements in the playfield
10681 that are triggered indirectly by another element. This is a third
10682 case: Changing the CE score always affects multiple identical CEs,
10683 so every affected CE must be checked, not only the single CE for
10684 which the CE score was changed in the first place (as every instance
10685 of that CE shares the same CE score, and therefore also can change)!
10687 SCAN_PLAYFIELD(xx, yy)
10689 if (Tile[xx][yy] == element)
10690 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10691 CE_SCORE_GETS_ZERO);
10699 case CA_SET_CE_ARTWORK:
10701 int artwork_element = action_arg_element;
10702 boolean reset_frame = FALSE;
10705 if (action_arg == CA_ARG_ELEMENT_RESET)
10706 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10709 if (ei->gfx_element != artwork_element)
10710 reset_frame = TRUE;
10712 ei->gfx_element = artwork_element;
10714 SCAN_PLAYFIELD(xx, yy)
10716 if (Tile[xx][yy] == element)
10720 ResetGfxAnimation(xx, yy);
10721 ResetRandomAnimationValue(xx, yy);
10724 TEST_DrawLevelField(xx, yy);
10731 // ---------- engine actions ---------------------------------------------
10733 case CA_SET_ENGINE_SCAN_MODE:
10735 InitPlayfieldScanMode(action_arg);
10745 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10747 int old_element = Tile[x][y];
10748 int new_element = GetElementFromGroupElement(element);
10749 int previous_move_direction = MovDir[x][y];
10750 int last_ce_value = CustomValue[x][y];
10751 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10752 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10753 boolean add_player_onto_element = (new_element_is_player &&
10754 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10755 IS_WALKABLE(old_element));
10757 if (!add_player_onto_element)
10759 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10760 RemoveMovingField(x, y);
10764 Tile[x][y] = new_element;
10766 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10767 MovDir[x][y] = previous_move_direction;
10769 if (element_info[new_element].use_last_ce_value)
10770 CustomValue[x][y] = last_ce_value;
10772 InitField_WithBug1(x, y, FALSE);
10774 new_element = Tile[x][y]; // element may have changed
10776 ResetGfxAnimation(x, y);
10777 ResetRandomAnimationValue(x, y);
10779 TEST_DrawLevelField(x, y);
10781 if (GFX_CRUMBLED(new_element))
10782 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10784 if (old_element == EL_EXPLOSION)
10786 Store[x][y] = Store2[x][y] = 0;
10788 // check if new element replaces an exploding player, requiring cleanup
10789 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
10790 StorePlayer[x][y] = 0;
10793 // check if element under the player changes from accessible to unaccessible
10794 // (needed for special case of dropping element which then changes)
10795 // (must be checked after creating new element for walkable group elements)
10796 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10797 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10799 KillPlayer(PLAYERINFO(x, y));
10805 // "ChangeCount" not set yet to allow "entered by player" change one time
10806 if (new_element_is_player)
10807 RelocatePlayer(x, y, new_element);
10810 ChangeCount[x][y]++; // count number of changes in the same frame
10812 TestIfBadThingTouchesPlayer(x, y);
10813 TestIfPlayerTouchesCustomElement(x, y);
10814 TestIfElementTouchesCustomElement(x, y);
10817 static void CreateField(int x, int y, int element)
10819 CreateFieldExt(x, y, element, FALSE);
10822 static void CreateElementFromChange(int x, int y, int element)
10824 element = GET_VALID_RUNTIME_ELEMENT(element);
10826 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10828 int old_element = Tile[x][y];
10830 // prevent changed element from moving in same engine frame
10831 // unless both old and new element can either fall or move
10832 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10833 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10837 CreateFieldExt(x, y, element, TRUE);
10840 static boolean ChangeElement(int x, int y, int element, int page)
10842 struct ElementInfo *ei = &element_info[element];
10843 struct ElementChangeInfo *change = &ei->change_page[page];
10844 int ce_value = CustomValue[x][y];
10845 int ce_score = ei->collect_score;
10846 int target_element;
10847 int old_element = Tile[x][y];
10849 // always use default change event to prevent running into a loop
10850 if (ChangeEvent[x][y] == -1)
10851 ChangeEvent[x][y] = CE_DELAY;
10853 if (ChangeEvent[x][y] == CE_DELAY)
10855 // reset actual trigger element, trigger player and action element
10856 change->actual_trigger_element = EL_EMPTY;
10857 change->actual_trigger_player = EL_EMPTY;
10858 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10859 change->actual_trigger_side = CH_SIDE_NONE;
10860 change->actual_trigger_ce_value = 0;
10861 change->actual_trigger_ce_score = 0;
10862 change->actual_trigger_x = -1;
10863 change->actual_trigger_y = -1;
10866 // do not change elements more than a specified maximum number of changes
10867 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10870 ChangeCount[x][y]++; // count number of changes in the same frame
10872 if (ei->has_anim_event)
10873 HandleGlobalAnimEventByElementChange(element, page, x, y,
10874 change->actual_trigger_x,
10875 change->actual_trigger_y);
10877 if (change->explode)
10884 if (change->use_target_content)
10886 boolean complete_replace = TRUE;
10887 boolean can_replace[3][3];
10890 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10893 boolean is_walkable;
10894 boolean is_diggable;
10895 boolean is_collectible;
10896 boolean is_removable;
10897 boolean is_destructible;
10898 int ex = x + xx - 1;
10899 int ey = y + yy - 1;
10900 int content_element = change->target_content.e[xx][yy];
10903 can_replace[xx][yy] = TRUE;
10905 if (ex == x && ey == y) // do not check changing element itself
10908 if (content_element == EL_EMPTY_SPACE)
10910 can_replace[xx][yy] = FALSE; // do not replace border with space
10915 if (!IN_LEV_FIELD(ex, ey))
10917 can_replace[xx][yy] = FALSE;
10918 complete_replace = FALSE;
10925 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10926 e = MovingOrBlocked2Element(ex, ey);
10928 is_empty = (IS_FREE(ex, ey) ||
10929 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10931 is_walkable = (is_empty || IS_WALKABLE(e));
10932 is_diggable = (is_empty || IS_DIGGABLE(e));
10933 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10934 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10935 is_removable = (is_diggable || is_collectible);
10937 can_replace[xx][yy] =
10938 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10939 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10940 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10941 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10942 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10943 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10944 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10946 if (!can_replace[xx][yy])
10947 complete_replace = FALSE;
10950 if (!change->only_if_complete || complete_replace)
10952 boolean something_has_changed = FALSE;
10954 if (change->only_if_complete && change->use_random_replace &&
10955 RND(100) < change->random_percentage)
10958 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10960 int ex = x + xx - 1;
10961 int ey = y + yy - 1;
10962 int content_element;
10964 if (can_replace[xx][yy] && (!change->use_random_replace ||
10965 RND(100) < change->random_percentage))
10967 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10968 RemoveMovingField(ex, ey);
10970 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10972 content_element = change->target_content.e[xx][yy];
10973 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10974 ce_value, ce_score);
10976 CreateElementFromChange(ex, ey, target_element);
10978 something_has_changed = TRUE;
10980 // for symmetry reasons, freeze newly created border elements
10981 if (ex != x || ey != y)
10982 Stop[ex][ey] = TRUE; // no more moving in this frame
10986 if (something_has_changed)
10988 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10989 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10995 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10996 ce_value, ce_score);
10998 if (element == EL_DIAGONAL_GROWING ||
10999 element == EL_DIAGONAL_SHRINKING)
11001 target_element = Store[x][y];
11003 Store[x][y] = EL_EMPTY;
11006 // special case: element changes to player (and may be kept if walkable)
11007 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
11008 CreateElementFromChange(x, y, EL_EMPTY);
11010 CreateElementFromChange(x, y, target_element);
11012 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
11013 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
11016 // this uses direct change before indirect change
11017 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
11022 static void HandleElementChange(int x, int y, int page)
11024 int element = MovingOrBlocked2Element(x, y);
11025 struct ElementInfo *ei = &element_info[element];
11026 struct ElementChangeInfo *change = &ei->change_page[page];
11027 boolean handle_action_before_change = FALSE;
11030 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
11031 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
11033 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
11034 x, y, element, element_info[element].token_name);
11035 Debug("game:playing:HandleElementChange", "This should never happen!");
11039 // this can happen with classic bombs on walkable, changing elements
11040 if (!CAN_CHANGE_OR_HAS_ACTION(element))
11045 if (ChangeDelay[x][y] == 0) // initialize element change
11047 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
11049 if (change->can_change)
11051 // !!! not clear why graphic animation should be reset at all here !!!
11052 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
11053 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
11056 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
11058 When using an animation frame delay of 1 (this only happens with
11059 "sp_zonk.moving.left/right" in the classic graphics), the default
11060 (non-moving) animation shows wrong animation frames (while the
11061 moving animation, like "sp_zonk.moving.left/right", is correct,
11062 so this graphical bug never shows up with the classic graphics).
11063 For an animation with 4 frames, this causes wrong frames 0,0,1,2
11064 be drawn instead of the correct frames 0,1,2,3. This is caused by
11065 "GfxFrame[][]" being reset *twice* (in two successive frames) after
11066 an element change: First when the change delay ("ChangeDelay[][]")
11067 counter has reached zero after decrementing, then a second time in
11068 the next frame (after "GfxFrame[][]" was already incremented) when
11069 "ChangeDelay[][]" is reset to the initial delay value again.
11071 This causes frame 0 to be drawn twice, while the last frame won't
11072 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
11074 As some animations may already be cleverly designed around this bug
11075 (at least the "Snake Bite" snake tail animation does this), it cannot
11076 simply be fixed here without breaking such existing animations.
11077 Unfortunately, it cannot easily be detected if a graphics set was
11078 designed "before" or "after" the bug was fixed. As a workaround,
11079 a new graphics set option "game.graphics_engine_version" was added
11080 to be able to specify the game's major release version for which the
11081 graphics set was designed, which can then be used to decide if the
11082 bugfix should be used (version 4 and above) or not (version 3 or
11083 below, or if no version was specified at all, as with old sets).
11085 (The wrong/fixed animation frames can be tested with the test level set
11086 "test_gfxframe" and level "000", which contains a specially prepared
11087 custom element at level position (x/y) == (11/9) which uses the zonk
11088 animation mentioned above. Using "game.graphics_engine_version: 4"
11089 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
11090 This can also be seen from the debug output for this test element.)
11093 // when a custom element is about to change (for example by change delay),
11094 // do not reset graphic animation when the custom element is moving
11095 if (game.graphics_engine_version < 4 &&
11098 ResetGfxAnimation(x, y);
11099 ResetRandomAnimationValue(x, y);
11102 if (change->pre_change_function)
11103 change->pre_change_function(x, y);
11107 ChangeDelay[x][y]--;
11109 if (ChangeDelay[x][y] != 0) // continue element change
11111 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11113 // also needed if CE can not change, but has CE delay with CE action
11114 if (IS_ANIMATED(graphic))
11115 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11117 if (change->can_change)
11119 if (change->change_function)
11120 change->change_function(x, y);
11123 else // finish element change
11125 if (ChangePage[x][y] != -1) // remember page from delayed change
11127 page = ChangePage[x][y];
11128 ChangePage[x][y] = -1;
11130 change = &ei->change_page[page];
11133 if (IS_MOVING(x, y)) // never change a running system ;-)
11135 ChangeDelay[x][y] = 1; // try change after next move step
11136 ChangePage[x][y] = page; // remember page to use for change
11141 // special case: set new level random seed before changing element
11142 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11143 handle_action_before_change = TRUE;
11145 if (change->has_action && handle_action_before_change)
11146 ExecuteCustomElementAction(x, y, element, page);
11148 if (change->can_change)
11150 if (ChangeElement(x, y, element, page))
11152 if (change->post_change_function)
11153 change->post_change_function(x, y);
11157 if (change->has_action && !handle_action_before_change)
11158 ExecuteCustomElementAction(x, y, element, page);
11162 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11163 int trigger_element,
11165 int trigger_player,
11169 boolean change_done_any = FALSE;
11170 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11173 if (!(trigger_events[trigger_element][trigger_event]))
11176 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11178 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11180 int element = EL_CUSTOM_START + i;
11181 boolean change_done = FALSE;
11184 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11185 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11188 for (p = 0; p < element_info[element].num_change_pages; p++)
11190 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11192 if (change->can_change_or_has_action &&
11193 change->has_event[trigger_event] &&
11194 change->trigger_side & trigger_side &&
11195 change->trigger_player & trigger_player &&
11196 change->trigger_page & trigger_page_bits &&
11197 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11199 change->actual_trigger_element = trigger_element;
11200 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11201 change->actual_trigger_player_bits = trigger_player;
11202 change->actual_trigger_side = trigger_side;
11203 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11204 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11205 change->actual_trigger_x = trigger_x;
11206 change->actual_trigger_y = trigger_y;
11208 if ((change->can_change && !change_done) || change->has_action)
11212 SCAN_PLAYFIELD(x, y)
11214 if (Tile[x][y] == element)
11216 if (change->can_change && !change_done)
11218 // if element already changed in this frame, not only prevent
11219 // another element change (checked in ChangeElement()), but
11220 // also prevent additional element actions for this element
11222 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11223 !level.use_action_after_change_bug)
11226 ChangeDelay[x][y] = 1;
11227 ChangeEvent[x][y] = trigger_event;
11229 HandleElementChange(x, y, p);
11231 else if (change->has_action)
11233 // if element already changed in this frame, not only prevent
11234 // another element change (checked in ChangeElement()), but
11235 // also prevent additional element actions for this element
11237 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11238 !level.use_action_after_change_bug)
11241 ExecuteCustomElementAction(x, y, element, p);
11242 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11247 if (change->can_change)
11249 change_done = TRUE;
11250 change_done_any = TRUE;
11257 RECURSION_LOOP_DETECTION_END();
11259 return change_done_any;
11262 static boolean CheckElementChangeExt(int x, int y,
11264 int trigger_element,
11266 int trigger_player,
11269 boolean change_done = FALSE;
11272 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11273 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11276 if (Tile[x][y] == EL_BLOCKED)
11278 Blocked2Moving(x, y, &x, &y);
11279 element = Tile[x][y];
11282 // check if element has already changed or is about to change after moving
11283 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11284 Tile[x][y] != element) ||
11286 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11287 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11288 ChangePage[x][y] != -1)))
11291 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11293 for (p = 0; p < element_info[element].num_change_pages; p++)
11295 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11297 /* check trigger element for all events where the element that is checked
11298 for changing interacts with a directly adjacent element -- this is
11299 different to element changes that affect other elements to change on the
11300 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11301 boolean check_trigger_element =
11302 (trigger_event == CE_NEXT_TO_X ||
11303 trigger_event == CE_TOUCHING_X ||
11304 trigger_event == CE_HITTING_X ||
11305 trigger_event == CE_HIT_BY_X ||
11306 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11308 if (change->can_change_or_has_action &&
11309 change->has_event[trigger_event] &&
11310 change->trigger_side & trigger_side &&
11311 change->trigger_player & trigger_player &&
11312 (!check_trigger_element ||
11313 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11315 change->actual_trigger_element = trigger_element;
11316 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11317 change->actual_trigger_player_bits = trigger_player;
11318 change->actual_trigger_side = trigger_side;
11319 change->actual_trigger_ce_value = CustomValue[x][y];
11320 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11321 change->actual_trigger_x = x;
11322 change->actual_trigger_y = y;
11324 // special case: trigger element not at (x,y) position for some events
11325 if (check_trigger_element)
11337 { 0, 0 }, { 0, 0 }, { 0, 0 },
11341 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11342 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11344 change->actual_trigger_ce_value = CustomValue[xx][yy];
11345 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11346 change->actual_trigger_x = xx;
11347 change->actual_trigger_y = yy;
11350 if (change->can_change && !change_done)
11352 ChangeDelay[x][y] = 1;
11353 ChangeEvent[x][y] = trigger_event;
11355 HandleElementChange(x, y, p);
11357 change_done = TRUE;
11359 else if (change->has_action)
11361 ExecuteCustomElementAction(x, y, element, p);
11362 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11367 RECURSION_LOOP_DETECTION_END();
11369 return change_done;
11372 static void PlayPlayerSound(struct PlayerInfo *player)
11374 int jx = player->jx, jy = player->jy;
11375 int sound_element = player->artwork_element;
11376 int last_action = player->last_action_waiting;
11377 int action = player->action_waiting;
11379 if (player->is_waiting)
11381 if (action != last_action)
11382 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11384 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11388 if (action != last_action)
11389 StopSound(element_info[sound_element].sound[last_action]);
11391 if (last_action == ACTION_SLEEPING)
11392 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11396 static void PlayAllPlayersSound(void)
11400 for (i = 0; i < MAX_PLAYERS; i++)
11401 if (stored_player[i].active)
11402 PlayPlayerSound(&stored_player[i]);
11405 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11407 boolean last_waiting = player->is_waiting;
11408 int move_dir = player->MovDir;
11410 player->dir_waiting = move_dir;
11411 player->last_action_waiting = player->action_waiting;
11415 if (!last_waiting) // not waiting -> waiting
11417 player->is_waiting = TRUE;
11419 player->frame_counter_bored =
11421 game.player_boring_delay_fixed +
11422 GetSimpleRandom(game.player_boring_delay_random);
11423 player->frame_counter_sleeping =
11425 game.player_sleeping_delay_fixed +
11426 GetSimpleRandom(game.player_sleeping_delay_random);
11428 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11431 if (game.player_sleeping_delay_fixed +
11432 game.player_sleeping_delay_random > 0 &&
11433 player->anim_delay_counter == 0 &&
11434 player->post_delay_counter == 0 &&
11435 FrameCounter >= player->frame_counter_sleeping)
11436 player->is_sleeping = TRUE;
11437 else if (game.player_boring_delay_fixed +
11438 game.player_boring_delay_random > 0 &&
11439 FrameCounter >= player->frame_counter_bored)
11440 player->is_bored = TRUE;
11442 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11443 player->is_bored ? ACTION_BORING :
11446 if (player->is_sleeping && player->use_murphy)
11448 // special case for sleeping Murphy when leaning against non-free tile
11450 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11451 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11452 !IS_MOVING(player->jx - 1, player->jy)))
11453 move_dir = MV_LEFT;
11454 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11455 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11456 !IS_MOVING(player->jx + 1, player->jy)))
11457 move_dir = MV_RIGHT;
11459 player->is_sleeping = FALSE;
11461 player->dir_waiting = move_dir;
11464 if (player->is_sleeping)
11466 if (player->num_special_action_sleeping > 0)
11468 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11470 int last_special_action = player->special_action_sleeping;
11471 int num_special_action = player->num_special_action_sleeping;
11472 int special_action =
11473 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11474 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11475 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11476 last_special_action + 1 : ACTION_SLEEPING);
11477 int special_graphic =
11478 el_act_dir2img(player->artwork_element, special_action, move_dir);
11480 player->anim_delay_counter =
11481 graphic_info[special_graphic].anim_delay_fixed +
11482 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11483 player->post_delay_counter =
11484 graphic_info[special_graphic].post_delay_fixed +
11485 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11487 player->special_action_sleeping = special_action;
11490 if (player->anim_delay_counter > 0)
11492 player->action_waiting = player->special_action_sleeping;
11493 player->anim_delay_counter--;
11495 else if (player->post_delay_counter > 0)
11497 player->post_delay_counter--;
11501 else if (player->is_bored)
11503 if (player->num_special_action_bored > 0)
11505 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11507 int special_action =
11508 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11509 int special_graphic =
11510 el_act_dir2img(player->artwork_element, special_action, move_dir);
11512 player->anim_delay_counter =
11513 graphic_info[special_graphic].anim_delay_fixed +
11514 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11515 player->post_delay_counter =
11516 graphic_info[special_graphic].post_delay_fixed +
11517 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11519 player->special_action_bored = special_action;
11522 if (player->anim_delay_counter > 0)
11524 player->action_waiting = player->special_action_bored;
11525 player->anim_delay_counter--;
11527 else if (player->post_delay_counter > 0)
11529 player->post_delay_counter--;
11534 else if (last_waiting) // waiting -> not waiting
11536 player->is_waiting = FALSE;
11537 player->is_bored = FALSE;
11538 player->is_sleeping = FALSE;
11540 player->frame_counter_bored = -1;
11541 player->frame_counter_sleeping = -1;
11543 player->anim_delay_counter = 0;
11544 player->post_delay_counter = 0;
11546 player->dir_waiting = player->MovDir;
11547 player->action_waiting = ACTION_DEFAULT;
11549 player->special_action_bored = ACTION_DEFAULT;
11550 player->special_action_sleeping = ACTION_DEFAULT;
11554 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11556 if ((!player->is_moving && player->was_moving) ||
11557 (player->MovPos == 0 && player->was_moving) ||
11558 (player->is_snapping && !player->was_snapping) ||
11559 (player->is_dropping && !player->was_dropping))
11561 if (!CheckSaveEngineSnapshotToList())
11564 player->was_moving = FALSE;
11565 player->was_snapping = TRUE;
11566 player->was_dropping = TRUE;
11570 if (player->is_moving)
11571 player->was_moving = TRUE;
11573 if (!player->is_snapping)
11574 player->was_snapping = FALSE;
11576 if (!player->is_dropping)
11577 player->was_dropping = FALSE;
11580 static struct MouseActionInfo mouse_action_last = { 0 };
11581 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11582 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11585 CheckSaveEngineSnapshotToList();
11587 mouse_action_last = mouse_action;
11590 static void CheckSingleStepMode(struct PlayerInfo *player)
11592 if (tape.single_step && tape.recording && !tape.pausing)
11594 // as it is called "single step mode", just return to pause mode when the
11595 // player stopped moving after one tile (or never starts moving at all)
11596 // (reverse logic needed here in case single step mode used in team mode)
11597 if (player->is_moving ||
11598 player->is_pushing ||
11599 player->is_dropping_pressed ||
11600 player->effective_mouse_action.button)
11601 game.enter_single_step_mode = FALSE;
11604 CheckSaveEngineSnapshot(player);
11607 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11609 int left = player_action & JOY_LEFT;
11610 int right = player_action & JOY_RIGHT;
11611 int up = player_action & JOY_UP;
11612 int down = player_action & JOY_DOWN;
11613 int button1 = player_action & JOY_BUTTON_1;
11614 int button2 = player_action & JOY_BUTTON_2;
11615 int dx = (left ? -1 : right ? 1 : 0);
11616 int dy = (up ? -1 : down ? 1 : 0);
11618 if (!player->active || tape.pausing)
11624 SnapField(player, dx, dy);
11628 DropElement(player);
11630 MovePlayer(player, dx, dy);
11633 CheckSingleStepMode(player);
11635 SetPlayerWaiting(player, FALSE);
11637 return player_action;
11641 // no actions for this player (no input at player's configured device)
11643 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11644 SnapField(player, 0, 0);
11645 CheckGravityMovementWhenNotMoving(player);
11647 if (player->MovPos == 0)
11648 SetPlayerWaiting(player, TRUE);
11650 if (player->MovPos == 0) // needed for tape.playing
11651 player->is_moving = FALSE;
11653 player->is_dropping = FALSE;
11654 player->is_dropping_pressed = FALSE;
11655 player->drop_pressed_delay = 0;
11657 CheckSingleStepMode(player);
11663 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11666 if (!tape.use_mouse_actions)
11669 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11670 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11671 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11674 static void SetTapeActionFromMouseAction(byte *tape_action,
11675 struct MouseActionInfo *mouse_action)
11677 if (!tape.use_mouse_actions)
11680 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11681 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11682 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11685 static void CheckLevelSolved(void)
11687 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11689 if (game_bd.level_solved &&
11690 !game_bd.game_over) // game won
11694 game_bd.game_over = TRUE;
11696 game.all_players_gone = TRUE;
11699 if (game_bd.game_over) // game lost
11700 game.all_players_gone = TRUE;
11702 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11704 if (game_em.level_solved &&
11705 !game_em.game_over) // game won
11709 game_em.game_over = TRUE;
11711 game.all_players_gone = TRUE;
11714 if (game_em.game_over) // game lost
11715 game.all_players_gone = TRUE;
11717 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11719 if (game_sp.level_solved &&
11720 !game_sp.game_over) // game won
11724 game_sp.game_over = TRUE;
11726 game.all_players_gone = TRUE;
11729 if (game_sp.game_over) // game lost
11730 game.all_players_gone = TRUE;
11732 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11734 if (game_mm.level_solved &&
11735 !game_mm.game_over) // game won
11739 game_mm.game_over = TRUE;
11741 game.all_players_gone = TRUE;
11744 if (game_mm.game_over) // game lost
11745 game.all_players_gone = TRUE;
11749 static void PlayTimeoutSound(int seconds_left)
11751 // will be played directly by BD engine (for classic bonus time sounds)
11752 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && checkBonusTime_BD())
11755 // try to use individual "running out of time" sound for each second left
11756 int sound = SND_GAME_RUNNING_OUT_OF_TIME_0 - seconds_left;
11758 // if special sound per second not defined, use default sound
11759 if (getSoundInfoEntryFilename(sound) == NULL)
11760 sound = SND_GAME_RUNNING_OUT_OF_TIME;
11762 // if out of time, but player still alive, play special "timeout" sound, if defined
11763 if (seconds_left == 0 && !checkGameFailed())
11764 if (getSoundInfoEntryFilename(SND_GAME_TIMEOUT) != NULL)
11765 sound = SND_GAME_TIMEOUT;
11770 static void CheckLevelTime_StepCounter(void)
11780 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11781 PlayTimeoutSound(TimeLeft);
11783 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11785 DisplayGameControlValues();
11787 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11788 for (i = 0; i < MAX_PLAYERS; i++)
11789 KillPlayer(&stored_player[i]);
11791 else if (game.no_level_time_limit && !game.all_players_gone)
11793 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11795 DisplayGameControlValues();
11799 static void CheckLevelTime(void)
11801 int frames_per_second = FRAMES_PER_SECOND;
11804 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11806 // level time may be running slower in native BD engine
11807 frames_per_second = getFramesPerSecond_BD();
11809 // if native engine time changed, force main engine time change
11810 if (getTimeLeft_BD() < TimeLeft)
11811 TimeFrames = frames_per_second;
11813 // if last second running, wait for native engine time to exactly reach zero
11814 if (getTimeLeft_BD() == 1 && TimeLeft == 1)
11815 TimeFrames = frames_per_second - 1;
11818 if (TimeFrames >= frames_per_second)
11822 for (i = 0; i < MAX_PLAYERS; i++)
11824 struct PlayerInfo *player = &stored_player[i];
11826 if (SHIELD_ON(player))
11828 player->shield_normal_time_left--;
11830 if (player->shield_deadly_time_left > 0)
11831 player->shield_deadly_time_left--;
11835 if (!game.LevelSolved && !level.use_step_counter)
11843 if (TimeLeft <= 10 && game.time_limit)
11844 PlayTimeoutSound(TimeLeft);
11846 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11847 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11849 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11851 if (!TimeLeft && game.time_limit)
11853 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11855 if (game_bd.game->cave->player_state == GD_PL_LIVING)
11856 game_bd.game->cave->player_state = GD_PL_TIMEOUT;
11858 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11860 game_em.lev->killed_out_of_time = TRUE;
11864 for (i = 0; i < MAX_PLAYERS; i++)
11865 KillPlayer(&stored_player[i]);
11869 else if (game.no_level_time_limit && !game.all_players_gone)
11871 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11874 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11878 if (TapeTimeFrames >= FRAMES_PER_SECOND)
11880 TapeTimeFrames = 0;
11883 if (tape.recording || tape.playing)
11884 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11887 if (tape.recording || tape.playing)
11888 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11890 UpdateAndDisplayGameControlValues();
11893 void AdvanceFrameAndPlayerCounters(int player_nr)
11897 // handle game and tape time differently for native BD game engine
11899 // tape time is running in native BD engine even if player is not hatched yet
11900 if (!checkGameRunning())
11903 // advance frame counters (global frame counter and tape time frame counter)
11907 // level time is running in native BD engine after player is being hatched
11908 if (!checkGamePlaying())
11911 // advance time frame counter (used to control available time to solve level)
11914 // advance player counters (counters for move delay, move animation etc.)
11915 for (i = 0; i < MAX_PLAYERS; i++)
11917 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11918 int move_delay_value = stored_player[i].move_delay_value;
11919 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11921 if (!advance_player_counters) // not all players may be affected
11924 if (move_frames == 0) // less than one move per game frame
11926 int stepsize = TILEX / move_delay_value;
11927 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11928 int count = (stored_player[i].is_moving ?
11929 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11931 if (count % delay == 0)
11935 stored_player[i].Frame += move_frames;
11937 if (stored_player[i].MovPos != 0)
11938 stored_player[i].StepFrame += move_frames;
11940 if (stored_player[i].move_delay > 0)
11941 stored_player[i].move_delay--;
11943 // due to bugs in previous versions, counter must count up, not down
11944 if (stored_player[i].push_delay != -1)
11945 stored_player[i].push_delay++;
11947 if (stored_player[i].drop_delay > 0)
11948 stored_player[i].drop_delay--;
11950 if (stored_player[i].is_dropping_pressed)
11951 stored_player[i].drop_pressed_delay++;
11955 void AdvanceFrameCounter(void)
11960 void AdvanceGfxFrame(void)
11964 SCAN_PLAYFIELD(x, y)
11970 static void HandleMouseAction(struct MouseActionInfo *mouse_action,
11971 struct MouseActionInfo *mouse_action_last)
11973 if (mouse_action->button)
11975 int new_button = (mouse_action->button && mouse_action_last->button == 0);
11976 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button);
11977 int x = mouse_action->lx;
11978 int y = mouse_action->ly;
11979 int element = Tile[x][y];
11983 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
11984 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
11988 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
11989 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
11992 if (level.use_step_counter)
11994 boolean counted_click = FALSE;
11996 // element clicked that can change when clicked/pressed
11997 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
11998 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
11999 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
12000 counted_click = TRUE;
12002 // element clicked that can trigger change when clicked/pressed
12003 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
12004 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
12005 counted_click = TRUE;
12007 if (new_button && counted_click)
12008 CheckLevelTime_StepCounter();
12013 void StartGameActions(boolean init_network_game, boolean record_tape,
12016 unsigned int new_random_seed = InitRND(random_seed);
12019 TapeStartRecording(new_random_seed);
12021 if (setup.auto_pause_on_start && !tape.pausing)
12022 TapeTogglePause(TAPE_TOGGLE_MANUAL);
12024 if (init_network_game)
12026 SendToServer_LevelFile();
12027 SendToServer_StartPlaying();
12035 static void GameActionsExt(void)
12038 static unsigned int game_frame_delay = 0;
12040 unsigned int game_frame_delay_value;
12041 byte *recorded_player_action;
12042 byte summarized_player_action = 0;
12043 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
12046 // detect endless loops, caused by custom element programming
12047 if (recursion_loop_detected && recursion_loop_depth == 0)
12049 char *message = getStringCat3("Internal Error! Element ",
12050 EL_NAME(recursion_loop_element),
12051 " caused endless loop! Quit the game?");
12053 Warn("element '%s' caused endless loop in game engine",
12054 EL_NAME(recursion_loop_element));
12056 RequestQuitGameExt(program.headless, level_editor_test_game, message);
12058 recursion_loop_detected = FALSE; // if game should be continued
12065 if (game.restart_level)
12066 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
12068 CheckLevelSolved();
12070 if (game.LevelSolved && !game.LevelSolved_GameEnd)
12073 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
12076 if (game_status != GAME_MODE_PLAYING) // status might have changed
12079 game_frame_delay_value =
12080 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
12082 if (tape.playing && tape.warp_forward && !tape.pausing)
12083 game_frame_delay_value = 0;
12085 SetVideoFrameDelay(game_frame_delay_value);
12087 // (de)activate virtual buttons depending on current game status
12088 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
12090 if (game.all_players_gone) // if no players there to be controlled anymore
12091 SetOverlayActive(FALSE);
12092 else if (!tape.playing) // if game continues after tape stopped playing
12093 SetOverlayActive(TRUE);
12098 // ---------- main game synchronization point ----------
12100 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12102 Debug("game:playing:skip", "skip == %d", skip);
12105 // ---------- main game synchronization point ----------
12107 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12111 if (network_playing && !network_player_action_received)
12113 // try to get network player actions in time
12115 // last chance to get network player actions without main loop delay
12116 HandleNetworking();
12118 // game was quit by network peer
12119 if (game_status != GAME_MODE_PLAYING)
12122 // check if network player actions still missing and game still running
12123 if (!network_player_action_received && !checkGameEnded())
12124 return; // failed to get network player actions in time
12126 // do not yet reset "network_player_action_received" (for tape.pausing)
12132 // at this point we know that we really continue executing the game
12134 network_player_action_received = FALSE;
12136 // when playing tape, read previously recorded player input from tape data
12137 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
12139 local_player->effective_mouse_action = local_player->mouse_action;
12141 if (recorded_player_action != NULL)
12142 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
12143 recorded_player_action);
12145 // TapePlayAction() may return NULL when toggling to "pause before death"
12149 if (tape.set_centered_player)
12151 game.centered_player_nr_next = tape.centered_player_nr_next;
12152 game.set_centered_player = TRUE;
12155 for (i = 0; i < MAX_PLAYERS; i++)
12157 summarized_player_action |= stored_player[i].action;
12159 if (!network_playing && (game.team_mode || tape.playing))
12160 stored_player[i].effective_action = stored_player[i].action;
12163 if (network_playing && !checkGameEnded())
12164 SendToServer_MovePlayer(summarized_player_action);
12166 // summarize all actions at local players mapped input device position
12167 // (this allows using different input devices in single player mode)
12168 if (!network.enabled && !game.team_mode)
12169 stored_player[map_player_action[local_player->index_nr]].effective_action =
12170 summarized_player_action;
12172 // summarize all actions at centered player in local team mode
12173 if (tape.recording &&
12174 setup.team_mode && !network.enabled &&
12175 setup.input_on_focus &&
12176 game.centered_player_nr != -1)
12178 for (i = 0; i < MAX_PLAYERS; i++)
12179 stored_player[map_player_action[i]].effective_action =
12180 (i == game.centered_player_nr ? summarized_player_action : 0);
12183 if (recorded_player_action != NULL)
12184 for (i = 0; i < MAX_PLAYERS; i++)
12185 stored_player[i].effective_action = recorded_player_action[i];
12187 for (i = 0; i < MAX_PLAYERS; i++)
12189 tape_action[i] = stored_player[i].effective_action;
12191 /* (this may happen in the RND game engine if a player was not present on
12192 the playfield on level start, but appeared later from a custom element */
12193 if (setup.team_mode &&
12196 !tape.player_participates[i])
12197 tape.player_participates[i] = TRUE;
12200 SetTapeActionFromMouseAction(tape_action,
12201 &local_player->effective_mouse_action);
12203 // only record actions from input devices, but not programmed actions
12204 if (tape.recording)
12205 TapeRecordAction(tape_action);
12207 // remember if game was played (especially after tape stopped playing)
12208 if (!tape.playing && summarized_player_action && !checkGameFailed())
12209 game.GamePlayed = TRUE;
12211 #if USE_NEW_PLAYER_ASSIGNMENTS
12212 // !!! also map player actions in single player mode !!!
12213 // if (game.team_mode)
12216 byte mapped_action[MAX_PLAYERS];
12218 #if DEBUG_PLAYER_ACTIONS
12219 for (i = 0; i < MAX_PLAYERS; i++)
12220 DebugContinued("", "%d, ", stored_player[i].effective_action);
12223 for (i = 0; i < MAX_PLAYERS; i++)
12224 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
12226 for (i = 0; i < MAX_PLAYERS; i++)
12227 stored_player[i].effective_action = mapped_action[i];
12229 #if DEBUG_PLAYER_ACTIONS
12230 DebugContinued("", "=> ");
12231 for (i = 0; i < MAX_PLAYERS; i++)
12232 DebugContinued("", "%d, ", stored_player[i].effective_action);
12233 DebugContinued("game:playing:player", "\n");
12236 #if DEBUG_PLAYER_ACTIONS
12239 for (i = 0; i < MAX_PLAYERS; i++)
12240 DebugContinued("", "%d, ", stored_player[i].effective_action);
12241 DebugContinued("game:playing:player", "\n");
12246 for (i = 0; i < MAX_PLAYERS; i++)
12248 // allow engine snapshot in case of changed movement attempt
12249 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12250 (stored_player[i].effective_action & KEY_MOTION))
12251 game.snapshot.changed_action = TRUE;
12253 // allow engine snapshot in case of snapping/dropping attempt
12254 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12255 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12256 game.snapshot.changed_action = TRUE;
12258 game.snapshot.last_action[i] = stored_player[i].effective_action;
12261 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
12263 GameActions_BD_Main();
12265 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12267 GameActions_EM_Main();
12269 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12271 GameActions_SP_Main();
12273 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12275 GameActions_MM_Main();
12279 GameActions_RND_Main();
12282 BlitScreenToBitmap(backbuffer);
12284 CheckLevelSolved();
12287 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12289 if (global.show_frames_per_second)
12291 static unsigned int fps_counter = 0;
12292 static int fps_frames = 0;
12293 unsigned int fps_delay_ms = Counter() - fps_counter;
12297 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12299 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12302 fps_counter = Counter();
12304 // always draw FPS to screen after FPS value was updated
12305 redraw_mask |= REDRAW_FPS;
12308 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12309 if (GetDrawDeactivationMask() == REDRAW_NONE)
12310 redraw_mask |= REDRAW_FPS;
12314 static void GameActions_CheckSaveEngineSnapshot(void)
12316 if (!game.snapshot.save_snapshot)
12319 // clear flag for saving snapshot _before_ saving snapshot
12320 game.snapshot.save_snapshot = FALSE;
12322 SaveEngineSnapshotToList();
12325 void GameActions(void)
12329 GameActions_CheckSaveEngineSnapshot();
12332 void GameActions_BD_Main(void)
12334 byte effective_action[MAX_PLAYERS];
12337 for (i = 0; i < MAX_PLAYERS; i++)
12338 effective_action[i] = stored_player[i].effective_action;
12340 GameActions_BD(effective_action);
12343 void GameActions_EM_Main(void)
12345 byte effective_action[MAX_PLAYERS];
12348 for (i = 0; i < MAX_PLAYERS; i++)
12349 effective_action[i] = stored_player[i].effective_action;
12351 GameActions_EM(effective_action);
12354 void GameActions_SP_Main(void)
12356 byte effective_action[MAX_PLAYERS];
12359 for (i = 0; i < MAX_PLAYERS; i++)
12360 effective_action[i] = stored_player[i].effective_action;
12362 GameActions_SP(effective_action);
12364 for (i = 0; i < MAX_PLAYERS; i++)
12366 if (stored_player[i].force_dropping)
12367 stored_player[i].action |= KEY_BUTTON_DROP;
12369 stored_player[i].force_dropping = FALSE;
12373 void GameActions_MM_Main(void)
12377 GameActions_MM(local_player->effective_mouse_action);
12380 void GameActions_RND_Main(void)
12385 void GameActions_RND(void)
12387 static struct MouseActionInfo mouse_action_last = { 0 };
12388 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12389 int magic_wall_x = 0, magic_wall_y = 0;
12390 int i, x, y, element, graphic, last_gfx_frame;
12392 InitPlayfieldScanModeVars();
12394 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12396 SCAN_PLAYFIELD(x, y)
12398 ChangeCount[x][y] = 0;
12399 ChangeEvent[x][y] = -1;
12403 if (game.set_centered_player)
12405 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12407 // switching to "all players" only possible if all players fit to screen
12408 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12410 game.centered_player_nr_next = game.centered_player_nr;
12411 game.set_centered_player = FALSE;
12414 // do not switch focus to non-existing (or non-active) player
12415 if (game.centered_player_nr_next >= 0 &&
12416 !stored_player[game.centered_player_nr_next].active)
12418 game.centered_player_nr_next = game.centered_player_nr;
12419 game.set_centered_player = FALSE;
12423 if (game.set_centered_player &&
12424 ScreenMovPos == 0) // screen currently aligned at tile position
12428 if (game.centered_player_nr_next == -1)
12430 setScreenCenteredToAllPlayers(&sx, &sy);
12434 sx = stored_player[game.centered_player_nr_next].jx;
12435 sy = stored_player[game.centered_player_nr_next].jy;
12438 game.centered_player_nr = game.centered_player_nr_next;
12439 game.set_centered_player = FALSE;
12441 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12442 DrawGameDoorValues();
12445 // check single step mode (set flag and clear again if any player is active)
12446 game.enter_single_step_mode =
12447 (tape.single_step && tape.recording && !tape.pausing);
12449 for (i = 0; i < MAX_PLAYERS; i++)
12451 int actual_player_action = stored_player[i].effective_action;
12454 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12455 - rnd_equinox_tetrachloride 048
12456 - rnd_equinox_tetrachloride_ii 096
12457 - rnd_emanuel_schmieg 002
12458 - doctor_sloan_ww 001, 020
12460 if (stored_player[i].MovPos == 0)
12461 CheckGravityMovement(&stored_player[i]);
12464 // overwrite programmed action with tape action
12465 if (stored_player[i].programmed_action)
12466 actual_player_action = stored_player[i].programmed_action;
12468 PlayerActions(&stored_player[i], actual_player_action);
12470 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12473 // single step pause mode may already have been toggled by "ScrollPlayer()"
12474 if (game.enter_single_step_mode && !tape.pausing)
12475 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12477 ScrollScreen(NULL, SCROLL_GO_ON);
12479 /* for backwards compatibility, the following code emulates a fixed bug that
12480 occured when pushing elements (causing elements that just made their last
12481 pushing step to already (if possible) make their first falling step in the
12482 same game frame, which is bad); this code is also needed to use the famous
12483 "spring push bug" which is used in older levels and might be wanted to be
12484 used also in newer levels, but in this case the buggy pushing code is only
12485 affecting the "spring" element and no other elements */
12487 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12489 for (i = 0; i < MAX_PLAYERS; i++)
12491 struct PlayerInfo *player = &stored_player[i];
12492 int x = player->jx;
12493 int y = player->jy;
12495 if (player->active && player->is_pushing && player->is_moving &&
12497 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12498 Tile[x][y] == EL_SPRING))
12500 ContinueMoving(x, y);
12502 // continue moving after pushing (this is actually a bug)
12503 if (!IS_MOVING(x, y))
12504 Stop[x][y] = FALSE;
12509 SCAN_PLAYFIELD(x, y)
12511 Last[x][y] = Tile[x][y];
12513 ChangeCount[x][y] = 0;
12514 ChangeEvent[x][y] = -1;
12516 // this must be handled before main playfield loop
12517 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12520 if (MovDelay[x][y] <= 0)
12524 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12527 if (MovDelay[x][y] <= 0)
12529 int element = Store[x][y];
12530 int move_direction = MovDir[x][y];
12531 int player_index_bit = Store2[x][y];
12537 TEST_DrawLevelField(x, y);
12539 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12541 if (IS_ENVELOPE(element))
12542 local_player->show_envelope = element;
12547 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12549 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12551 Debug("game:playing:GameActions_RND", "This should never happen!");
12553 ChangePage[x][y] = -1;
12557 Stop[x][y] = FALSE;
12558 if (WasJustMoving[x][y] > 0)
12559 WasJustMoving[x][y]--;
12560 if (WasJustFalling[x][y] > 0)
12561 WasJustFalling[x][y]--;
12562 if (CheckCollision[x][y] > 0)
12563 CheckCollision[x][y]--;
12564 if (CheckImpact[x][y] > 0)
12565 CheckImpact[x][y]--;
12569 /* reset finished pushing action (not done in ContinueMoving() to allow
12570 continuous pushing animation for elements with zero push delay) */
12571 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12573 ResetGfxAnimation(x, y);
12574 TEST_DrawLevelField(x, y);
12578 if (IS_BLOCKED(x, y))
12582 Blocked2Moving(x, y, &oldx, &oldy);
12583 if (!IS_MOVING(oldx, oldy))
12585 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12586 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12587 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12588 Debug("game:playing:GameActions_RND", "This should never happen!");
12594 HandleMouseAction(&mouse_action, &mouse_action_last);
12596 SCAN_PLAYFIELD(x, y)
12598 element = Tile[x][y];
12599 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12600 last_gfx_frame = GfxFrame[x][y];
12602 if (element == EL_EMPTY)
12603 graphic = el2img(GfxElementEmpty[x][y]);
12605 ResetGfxFrame(x, y);
12607 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12608 DrawLevelGraphicAnimation(x, y, graphic);
12610 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12611 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12612 ResetRandomAnimationValue(x, y);
12614 SetRandomAnimationValue(x, y);
12616 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12618 if (IS_INACTIVE(element))
12620 if (IS_ANIMATED(graphic))
12621 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12626 // this may take place after moving, so 'element' may have changed
12627 if (IS_CHANGING(x, y) &&
12628 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12630 int page = element_info[element].event_page_nr[CE_DELAY];
12632 HandleElementChange(x, y, page);
12634 element = Tile[x][y];
12635 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12638 CheckNextToConditions(x, y);
12640 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12644 element = Tile[x][y];
12645 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12647 if (IS_ANIMATED(graphic) &&
12648 !IS_MOVING(x, y) &&
12650 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12652 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12653 TEST_DrawTwinkleOnField(x, y);
12655 else if (element == EL_ACID)
12658 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12660 else if ((element == EL_EXIT_OPEN ||
12661 element == EL_EM_EXIT_OPEN ||
12662 element == EL_SP_EXIT_OPEN ||
12663 element == EL_STEEL_EXIT_OPEN ||
12664 element == EL_EM_STEEL_EXIT_OPEN ||
12665 element == EL_SP_TERMINAL ||
12666 element == EL_SP_TERMINAL_ACTIVE ||
12667 element == EL_EXTRA_TIME ||
12668 element == EL_SHIELD_NORMAL ||
12669 element == EL_SHIELD_DEADLY) &&
12670 IS_ANIMATED(graphic))
12671 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12672 else if (IS_MOVING(x, y))
12673 ContinueMoving(x, y);
12674 else if (IS_ACTIVE_BOMB(element))
12675 CheckDynamite(x, y);
12676 else if (element == EL_AMOEBA_GROWING)
12677 AmoebaGrowing(x, y);
12678 else if (element == EL_AMOEBA_SHRINKING)
12679 AmoebaShrinking(x, y);
12681 #if !USE_NEW_AMOEBA_CODE
12682 else if (IS_AMOEBALIVE(element))
12683 AmoebaReproduce(x, y);
12686 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12688 else if (element == EL_EXIT_CLOSED)
12690 else if (element == EL_EM_EXIT_CLOSED)
12692 else if (element == EL_STEEL_EXIT_CLOSED)
12693 CheckExitSteel(x, y);
12694 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12695 CheckExitSteelEM(x, y);
12696 else if (element == EL_SP_EXIT_CLOSED)
12698 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12699 element == EL_EXPANDABLE_STEELWALL_GROWING)
12701 else if (element == EL_EXPANDABLE_WALL ||
12702 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12703 element == EL_EXPANDABLE_WALL_VERTICAL ||
12704 element == EL_EXPANDABLE_WALL_ANY ||
12705 element == EL_BD_EXPANDABLE_WALL ||
12706 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12707 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12708 element == EL_EXPANDABLE_STEELWALL_ANY)
12709 CheckWallGrowing(x, y);
12710 else if (element == EL_FLAMES)
12711 CheckForDragon(x, y);
12712 else if (element == EL_EXPLOSION)
12713 ; // drawing of correct explosion animation is handled separately
12714 else if (element == EL_ELEMENT_SNAPPING ||
12715 element == EL_DIAGONAL_SHRINKING ||
12716 element == EL_DIAGONAL_GROWING)
12718 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]);
12720 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12722 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12723 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12725 if (IS_BELT_ACTIVE(element))
12726 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12728 if (game.magic_wall_active)
12730 int jx = local_player->jx, jy = local_player->jy;
12732 // play the element sound at the position nearest to the player
12733 if ((element == EL_MAGIC_WALL_FULL ||
12734 element == EL_MAGIC_WALL_ACTIVE ||
12735 element == EL_MAGIC_WALL_EMPTYING ||
12736 element == EL_BD_MAGIC_WALL_FULL ||
12737 element == EL_BD_MAGIC_WALL_ACTIVE ||
12738 element == EL_BD_MAGIC_WALL_EMPTYING ||
12739 element == EL_DC_MAGIC_WALL_FULL ||
12740 element == EL_DC_MAGIC_WALL_ACTIVE ||
12741 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12742 ABS(x - jx) + ABS(y - jy) <
12743 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12751 #if USE_NEW_AMOEBA_CODE
12752 // new experimental amoeba growth stuff
12753 if (!(FrameCounter % 8))
12755 static unsigned int random = 1684108901;
12757 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12759 x = RND(lev_fieldx);
12760 y = RND(lev_fieldy);
12761 element = Tile[x][y];
12763 if (!IS_PLAYER(x, y) &&
12764 (element == EL_EMPTY ||
12765 CAN_GROW_INTO(element) ||
12766 element == EL_QUICKSAND_EMPTY ||
12767 element == EL_QUICKSAND_FAST_EMPTY ||
12768 element == EL_ACID_SPLASH_LEFT ||
12769 element == EL_ACID_SPLASH_RIGHT))
12771 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12772 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12773 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12774 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12775 Tile[x][y] = EL_AMOEBA_DROP;
12778 random = random * 129 + 1;
12783 game.explosions_delayed = FALSE;
12785 SCAN_PLAYFIELD(x, y)
12787 element = Tile[x][y];
12789 if (ExplodeField[x][y])
12790 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12791 else if (element == EL_EXPLOSION)
12792 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12794 ExplodeField[x][y] = EX_TYPE_NONE;
12797 game.explosions_delayed = TRUE;
12799 if (game.magic_wall_active)
12801 if (!(game.magic_wall_time_left % 4))
12803 int element = Tile[magic_wall_x][magic_wall_y];
12805 if (element == EL_BD_MAGIC_WALL_FULL ||
12806 element == EL_BD_MAGIC_WALL_ACTIVE ||
12807 element == EL_BD_MAGIC_WALL_EMPTYING)
12808 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12809 else if (element == EL_DC_MAGIC_WALL_FULL ||
12810 element == EL_DC_MAGIC_WALL_ACTIVE ||
12811 element == EL_DC_MAGIC_WALL_EMPTYING)
12812 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12814 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12817 if (game.magic_wall_time_left > 0)
12819 game.magic_wall_time_left--;
12821 if (!game.magic_wall_time_left)
12823 SCAN_PLAYFIELD(x, y)
12825 element = Tile[x][y];
12827 if (element == EL_MAGIC_WALL_ACTIVE ||
12828 element == EL_MAGIC_WALL_FULL)
12830 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12831 TEST_DrawLevelField(x, y);
12833 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12834 element == EL_BD_MAGIC_WALL_FULL)
12836 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12837 TEST_DrawLevelField(x, y);
12839 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12840 element == EL_DC_MAGIC_WALL_FULL)
12842 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12843 TEST_DrawLevelField(x, y);
12847 game.magic_wall_active = FALSE;
12852 if (game.light_time_left > 0)
12854 game.light_time_left--;
12856 if (game.light_time_left == 0)
12857 RedrawAllLightSwitchesAndInvisibleElements();
12860 if (game.timegate_time_left > 0)
12862 game.timegate_time_left--;
12864 if (game.timegate_time_left == 0)
12865 CloseAllOpenTimegates();
12868 if (game.lenses_time_left > 0)
12870 game.lenses_time_left--;
12872 if (game.lenses_time_left == 0)
12873 RedrawAllInvisibleElementsForLenses();
12876 if (game.magnify_time_left > 0)
12878 game.magnify_time_left--;
12880 if (game.magnify_time_left == 0)
12881 RedrawAllInvisibleElementsForMagnifier();
12884 for (i = 0; i < MAX_PLAYERS; i++)
12886 struct PlayerInfo *player = &stored_player[i];
12888 if (SHIELD_ON(player))
12890 if (player->shield_deadly_time_left)
12891 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12892 else if (player->shield_normal_time_left)
12893 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12897 #if USE_DELAYED_GFX_REDRAW
12898 SCAN_PLAYFIELD(x, y)
12900 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12902 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12903 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12905 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12906 DrawLevelField(x, y);
12908 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12909 DrawLevelFieldCrumbled(x, y);
12911 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12912 DrawLevelFieldCrumbledNeighbours(x, y);
12914 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12915 DrawTwinkleOnField(x, y);
12918 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12923 PlayAllPlayersSound();
12925 for (i = 0; i < MAX_PLAYERS; i++)
12927 struct PlayerInfo *player = &stored_player[i];
12929 if (player->show_envelope != 0 && (!player->active ||
12930 player->MovPos == 0))
12932 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12934 player->show_envelope = 0;
12938 // use random number generator in every frame to make it less predictable
12939 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12942 mouse_action_last = mouse_action;
12945 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12947 int min_x = x, min_y = y, max_x = x, max_y = y;
12948 int scr_fieldx = getScreenFieldSizeX();
12949 int scr_fieldy = getScreenFieldSizeY();
12952 for (i = 0; i < MAX_PLAYERS; i++)
12954 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12956 if (!stored_player[i].active || &stored_player[i] == player)
12959 min_x = MIN(min_x, jx);
12960 min_y = MIN(min_y, jy);
12961 max_x = MAX(max_x, jx);
12962 max_y = MAX(max_y, jy);
12965 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12968 static boolean AllPlayersInVisibleScreen(void)
12972 for (i = 0; i < MAX_PLAYERS; i++)
12974 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12976 if (!stored_player[i].active)
12979 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12986 void ScrollLevel(int dx, int dy)
12988 int scroll_offset = 2 * TILEX_VAR;
12991 BlitBitmap(drawto_field, drawto_field,
12992 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12993 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12994 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12995 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12996 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12997 FY + TILEY_VAR * (dy == 1) - scroll_offset);
13001 x = (dx == 1 ? BX1 : BX2);
13002 for (y = BY1; y <= BY2; y++)
13003 DrawScreenField(x, y);
13008 y = (dy == 1 ? BY1 : BY2);
13009 for (x = BX1; x <= BX2; x++)
13010 DrawScreenField(x, y);
13013 redraw_mask |= REDRAW_FIELD;
13016 static boolean canFallDown(struct PlayerInfo *player)
13018 int jx = player->jx, jy = player->jy;
13020 return (IN_LEV_FIELD(jx, jy + 1) &&
13021 (IS_FREE(jx, jy + 1) ||
13022 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
13023 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
13024 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
13027 static boolean canPassField(int x, int y, int move_dir)
13029 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13030 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13031 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13032 int nextx = x + dx;
13033 int nexty = y + dy;
13034 int element = Tile[x][y];
13036 return (IS_PASSABLE_FROM(element, opposite_dir) &&
13037 !CAN_MOVE(element) &&
13038 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
13039 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
13040 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
13043 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
13045 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13046 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13047 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13051 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
13052 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
13053 (IS_DIGGABLE(Tile[newx][newy]) ||
13054 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
13055 canPassField(newx, newy, move_dir)));
13058 static void CheckGravityMovement(struct PlayerInfo *player)
13060 if (player->gravity && !player->programmed_action)
13062 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
13063 int move_dir_vertical = player->effective_action & MV_VERTICAL;
13064 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
13065 int jx = player->jx, jy = player->jy;
13066 boolean player_is_moving_to_valid_field =
13067 (!player_is_snapping &&
13068 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
13069 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
13070 boolean player_can_fall_down = canFallDown(player);
13072 if (player_can_fall_down &&
13073 !player_is_moving_to_valid_field)
13074 player->programmed_action = MV_DOWN;
13078 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
13080 return CheckGravityMovement(player);
13082 if (player->gravity && !player->programmed_action)
13084 int jx = player->jx, jy = player->jy;
13085 boolean field_under_player_is_free =
13086 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
13087 boolean player_is_standing_on_valid_field =
13088 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
13089 (IS_WALKABLE(Tile[jx][jy]) &&
13090 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
13092 if (field_under_player_is_free && !player_is_standing_on_valid_field)
13093 player->programmed_action = MV_DOWN;
13098 MovePlayerOneStep()
13099 -----------------------------------------------------------------------------
13100 dx, dy: direction (non-diagonal) to try to move the player to
13101 real_dx, real_dy: direction as read from input device (can be diagonal)
13104 boolean MovePlayerOneStep(struct PlayerInfo *player,
13105 int dx, int dy, int real_dx, int real_dy)
13107 int jx = player->jx, jy = player->jy;
13108 int new_jx = jx + dx, new_jy = jy + dy;
13110 boolean player_can_move = !player->cannot_move;
13112 if (!player->active || (!dx && !dy))
13113 return MP_NO_ACTION;
13115 player->MovDir = (dx < 0 ? MV_LEFT :
13116 dx > 0 ? MV_RIGHT :
13118 dy > 0 ? MV_DOWN : MV_NONE);
13120 if (!IN_LEV_FIELD(new_jx, new_jy))
13121 return MP_NO_ACTION;
13123 if (!player_can_move)
13125 if (player->MovPos == 0)
13127 player->is_moving = FALSE;
13128 player->is_digging = FALSE;
13129 player->is_collecting = FALSE;
13130 player->is_snapping = FALSE;
13131 player->is_pushing = FALSE;
13135 if (!network.enabled && game.centered_player_nr == -1 &&
13136 !AllPlayersInSight(player, new_jx, new_jy))
13137 return MP_NO_ACTION;
13139 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG);
13140 if (can_move != MP_MOVING)
13143 // check if DigField() has caused relocation of the player
13144 if (player->jx != jx || player->jy != jy)
13145 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
13147 StorePlayer[jx][jy] = 0;
13148 player->last_jx = jx;
13149 player->last_jy = jy;
13150 player->jx = new_jx;
13151 player->jy = new_jy;
13152 StorePlayer[new_jx][new_jy] = player->element_nr;
13154 if (player->move_delay_value_next != -1)
13156 player->move_delay_value = player->move_delay_value_next;
13157 player->move_delay_value_next = -1;
13161 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
13163 player->step_counter++;
13165 PlayerVisit[jx][jy] = FrameCounter;
13167 player->is_moving = TRUE;
13170 // should better be called in MovePlayer(), but this breaks some tapes
13171 ScrollPlayer(player, SCROLL_INIT);
13177 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
13179 int jx = player->jx, jy = player->jy;
13180 int old_jx = jx, old_jy = jy;
13181 int moved = MP_NO_ACTION;
13183 if (!player->active)
13188 if (player->MovPos == 0)
13190 player->is_moving = FALSE;
13191 player->is_digging = FALSE;
13192 player->is_collecting = FALSE;
13193 player->is_snapping = FALSE;
13194 player->is_pushing = FALSE;
13200 if (player->move_delay > 0)
13203 player->move_delay = -1; // set to "uninitialized" value
13205 // store if player is automatically moved to next field
13206 player->is_auto_moving = (player->programmed_action != MV_NONE);
13208 // remove the last programmed player action
13209 player->programmed_action = 0;
13211 if (player->MovPos)
13213 // should only happen if pre-1.2 tape recordings are played
13214 // this is only for backward compatibility
13216 int original_move_delay_value = player->move_delay_value;
13219 Debug("game:playing:MovePlayer",
13220 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13224 // scroll remaining steps with finest movement resolution
13225 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13227 while (player->MovPos)
13229 ScrollPlayer(player, SCROLL_GO_ON);
13230 ScrollScreen(NULL, SCROLL_GO_ON);
13232 AdvanceFrameAndPlayerCounters(player->index_nr);
13235 BackToFront_WithFrameDelay(0);
13238 player->move_delay_value = original_move_delay_value;
13241 player->is_active = FALSE;
13243 if (player->last_move_dir & MV_HORIZONTAL)
13245 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13246 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13250 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13251 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13254 if (!moved && !player->is_active)
13256 player->is_moving = FALSE;
13257 player->is_digging = FALSE;
13258 player->is_collecting = FALSE;
13259 player->is_snapping = FALSE;
13260 player->is_pushing = FALSE;
13266 if (moved & MP_MOVING && !ScreenMovPos &&
13267 (player->index_nr == game.centered_player_nr ||
13268 game.centered_player_nr == -1))
13270 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13272 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13274 // actual player has left the screen -- scroll in that direction
13275 if (jx != old_jx) // player has moved horizontally
13276 scroll_x += (jx - old_jx);
13277 else // player has moved vertically
13278 scroll_y += (jy - old_jy);
13282 int offset_raw = game.scroll_delay_value;
13284 if (jx != old_jx) // player has moved horizontally
13286 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13287 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13288 int new_scroll_x = jx - MIDPOSX + offset_x;
13290 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13291 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13292 scroll_x = new_scroll_x;
13294 // don't scroll over playfield boundaries
13295 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13297 // don't scroll more than one field at a time
13298 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13300 // don't scroll against the player's moving direction
13301 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13302 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13303 scroll_x = old_scroll_x;
13305 else // player has moved vertically
13307 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13308 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13309 int new_scroll_y = jy - MIDPOSY + offset_y;
13311 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13312 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13313 scroll_y = new_scroll_y;
13315 // don't scroll over playfield boundaries
13316 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13318 // don't scroll more than one field at a time
13319 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13321 // don't scroll against the player's moving direction
13322 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13323 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13324 scroll_y = old_scroll_y;
13328 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13330 if (!network.enabled && game.centered_player_nr == -1 &&
13331 !AllPlayersInVisibleScreen())
13333 scroll_x = old_scroll_x;
13334 scroll_y = old_scroll_y;
13338 ScrollScreen(player, SCROLL_INIT);
13339 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13344 player->StepFrame = 0;
13346 if (moved & MP_MOVING)
13348 if (old_jx != jx && old_jy == jy)
13349 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13350 else if (old_jx == jx && old_jy != jy)
13351 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13353 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13355 player->last_move_dir = player->MovDir;
13356 player->is_moving = TRUE;
13357 player->is_snapping = FALSE;
13358 player->is_switching = FALSE;
13359 player->is_dropping = FALSE;
13360 player->is_dropping_pressed = FALSE;
13361 player->drop_pressed_delay = 0;
13364 // should better be called here than above, but this breaks some tapes
13365 ScrollPlayer(player, SCROLL_INIT);
13370 CheckGravityMovementWhenNotMoving(player);
13372 player->is_moving = FALSE;
13374 /* at this point, the player is allowed to move, but cannot move right now
13375 (e.g. because of something blocking the way) -- ensure that the player
13376 is also allowed to move in the next frame (in old versions before 3.1.1,
13377 the player was forced to wait again for eight frames before next try) */
13379 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13380 player->move_delay = 0; // allow direct movement in the next frame
13383 if (player->move_delay == -1) // not yet initialized by DigField()
13384 player->move_delay = player->move_delay_value;
13386 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13388 TestIfPlayerTouchesBadThing(jx, jy);
13389 TestIfPlayerTouchesCustomElement(jx, jy);
13392 if (!player->active)
13393 RemovePlayer(player);
13398 void ScrollPlayer(struct PlayerInfo *player, int mode)
13400 int jx = player->jx, jy = player->jy;
13401 int last_jx = player->last_jx, last_jy = player->last_jy;
13402 int move_stepsize = TILEX / player->move_delay_value;
13404 if (!player->active)
13407 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13410 if (mode == SCROLL_INIT)
13412 player->actual_frame_counter.count = FrameCounter;
13413 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13415 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13416 Tile[last_jx][last_jy] == EL_EMPTY)
13418 int last_field_block_delay = 0; // start with no blocking at all
13419 int block_delay_adjustment = player->block_delay_adjustment;
13421 // if player blocks last field, add delay for exactly one move
13422 if (player->block_last_field)
13424 last_field_block_delay += player->move_delay_value;
13426 // when blocking enabled, prevent moving up despite gravity
13427 if (player->gravity && player->MovDir == MV_UP)
13428 block_delay_adjustment = -1;
13431 // add block delay adjustment (also possible when not blocking)
13432 last_field_block_delay += block_delay_adjustment;
13434 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13435 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13438 if (player->MovPos != 0) // player has not yet reached destination
13441 else if (!FrameReached(&player->actual_frame_counter))
13444 if (player->MovPos != 0)
13446 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13447 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13449 // before DrawPlayer() to draw correct player graphic for this case
13450 if (player->MovPos == 0)
13451 CheckGravityMovement(player);
13454 if (player->MovPos == 0) // player reached destination field
13456 if (player->move_delay_reset_counter > 0)
13458 player->move_delay_reset_counter--;
13460 if (player->move_delay_reset_counter == 0)
13462 // continue with normal speed after quickly moving through gate
13463 HALVE_PLAYER_SPEED(player);
13465 // be able to make the next move without delay
13466 player->move_delay = 0;
13470 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13471 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13472 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13473 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13474 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13475 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13476 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13477 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13479 ExitPlayer(player);
13481 if (game.players_still_needed == 0 &&
13482 (game.friends_still_needed == 0 ||
13483 IS_SP_ELEMENT(Tile[jx][jy])))
13487 player->last_jx = jx;
13488 player->last_jy = jy;
13490 // this breaks one level: "machine", level 000
13492 int move_direction = player->MovDir;
13493 int enter_side = MV_DIR_OPPOSITE(move_direction);
13494 int leave_side = move_direction;
13495 int old_jx = last_jx;
13496 int old_jy = last_jy;
13497 int old_element = Tile[old_jx][old_jy];
13498 int new_element = Tile[jx][jy];
13500 if (IS_CUSTOM_ELEMENT(old_element))
13501 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13503 player->index_bit, leave_side);
13505 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13506 CE_PLAYER_LEAVES_X,
13507 player->index_bit, leave_side);
13509 // needed because pushed element has not yet reached its destination,
13510 // so it would trigger a change event at its previous field location
13511 if (!player->is_pushing)
13513 if (IS_CUSTOM_ELEMENT(new_element))
13514 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13515 player->index_bit, enter_side);
13517 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13518 CE_PLAYER_ENTERS_X,
13519 player->index_bit, enter_side);
13522 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13523 CE_MOVE_OF_X, move_direction);
13526 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13528 TestIfPlayerTouchesBadThing(jx, jy);
13529 TestIfPlayerTouchesCustomElement(jx, jy);
13531 // needed because pushed element has not yet reached its destination,
13532 // so it would trigger a change event at its previous field location
13533 if (!player->is_pushing)
13534 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13536 if (level.finish_dig_collect &&
13537 (player->is_digging || player->is_collecting))
13539 int last_element = player->last_removed_element;
13540 int move_direction = player->MovDir;
13541 int enter_side = MV_DIR_OPPOSITE(move_direction);
13542 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13543 CE_PLAYER_COLLECTS_X);
13545 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13546 player->index_bit, enter_side);
13548 player->last_removed_element = EL_UNDEFINED;
13551 if (!player->active)
13552 RemovePlayer(player);
13555 if (level.use_step_counter)
13556 CheckLevelTime_StepCounter();
13558 if (tape.single_step && tape.recording && !tape.pausing &&
13559 !player->programmed_action)
13560 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13562 if (!player->programmed_action)
13563 CheckSaveEngineSnapshot(player);
13567 void ScrollScreen(struct PlayerInfo *player, int mode)
13569 static DelayCounter screen_frame_counter = { 0 };
13571 if (mode == SCROLL_INIT)
13573 // set scrolling step size according to actual player's moving speed
13574 ScrollStepSize = TILEX / player->move_delay_value;
13576 screen_frame_counter.count = FrameCounter;
13577 screen_frame_counter.value = 1;
13579 ScreenMovDir = player->MovDir;
13580 ScreenMovPos = player->MovPos;
13581 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13584 else if (!FrameReached(&screen_frame_counter))
13589 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13590 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13591 redraw_mask |= REDRAW_FIELD;
13594 ScreenMovDir = MV_NONE;
13597 void CheckNextToConditions(int x, int y)
13599 int element = Tile[x][y];
13601 if (IS_PLAYER(x, y))
13602 TestIfPlayerNextToCustomElement(x, y);
13604 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13605 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13606 TestIfElementNextToCustomElement(x, y);
13609 void TestIfPlayerNextToCustomElement(int x, int y)
13611 struct XY *xy = xy_topdown;
13612 static int trigger_sides[4][2] =
13614 // center side border side
13615 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13616 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13617 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13618 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13622 if (!IS_PLAYER(x, y))
13625 struct PlayerInfo *player = PLAYERINFO(x, y);
13627 if (player->is_moving)
13630 for (i = 0; i < NUM_DIRECTIONS; i++)
13632 int xx = x + xy[i].x;
13633 int yy = y + xy[i].y;
13634 int border_side = trigger_sides[i][1];
13635 int border_element;
13637 if (!IN_LEV_FIELD(xx, yy))
13640 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13641 continue; // center and border element not connected
13643 border_element = Tile[xx][yy];
13645 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13646 player->index_bit, border_side);
13647 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13648 CE_PLAYER_NEXT_TO_X,
13649 player->index_bit, border_side);
13651 /* use player element that is initially defined in the level playfield,
13652 not the player element that corresponds to the runtime player number
13653 (example: a level that contains EL_PLAYER_3 as the only player would
13654 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13656 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13657 CE_NEXT_TO_X, border_side);
13661 void TestIfPlayerTouchesCustomElement(int x, int y)
13663 struct XY *xy = xy_topdown;
13664 static int trigger_sides[4][2] =
13666 // center side border side
13667 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13668 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13669 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13670 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13672 static int touch_dir[4] =
13674 MV_LEFT | MV_RIGHT,
13679 int center_element = Tile[x][y]; // should always be non-moving!
13682 for (i = 0; i < NUM_DIRECTIONS; i++)
13684 int xx = x + xy[i].x;
13685 int yy = y + xy[i].y;
13686 int center_side = trigger_sides[i][0];
13687 int border_side = trigger_sides[i][1];
13688 int border_element;
13690 if (!IN_LEV_FIELD(xx, yy))
13693 if (IS_PLAYER(x, y)) // player found at center element
13695 struct PlayerInfo *player = PLAYERINFO(x, y);
13697 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13698 border_element = Tile[xx][yy]; // may be moving!
13699 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13700 border_element = Tile[xx][yy];
13701 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13702 border_element = MovingOrBlocked2Element(xx, yy);
13704 continue; // center and border element do not touch
13706 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13707 player->index_bit, border_side);
13708 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13709 CE_PLAYER_TOUCHES_X,
13710 player->index_bit, border_side);
13713 /* use player element that is initially defined in the level playfield,
13714 not the player element that corresponds to the runtime player number
13715 (example: a level that contains EL_PLAYER_3 as the only player would
13716 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13717 int player_element = PLAYERINFO(x, y)->initial_element;
13719 // as element "X" is the player here, check opposite (center) side
13720 CheckElementChangeBySide(xx, yy, border_element, player_element,
13721 CE_TOUCHING_X, center_side);
13724 else if (IS_PLAYER(xx, yy)) // player found at border element
13726 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13728 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13730 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13731 continue; // center and border element do not touch
13734 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13735 player->index_bit, center_side);
13736 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13737 CE_PLAYER_TOUCHES_X,
13738 player->index_bit, center_side);
13741 /* use player element that is initially defined in the level playfield,
13742 not the player element that corresponds to the runtime player number
13743 (example: a level that contains EL_PLAYER_3 as the only player would
13744 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13745 int player_element = PLAYERINFO(xx, yy)->initial_element;
13747 // as element "X" is the player here, check opposite (border) side
13748 CheckElementChangeBySide(x, y, center_element, player_element,
13749 CE_TOUCHING_X, border_side);
13757 void TestIfElementNextToCustomElement(int x, int y)
13759 struct XY *xy = xy_topdown;
13760 static int trigger_sides[4][2] =
13762 // center side border side
13763 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13764 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13765 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13766 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13768 int center_element = Tile[x][y]; // should always be non-moving!
13771 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13774 for (i = 0; i < NUM_DIRECTIONS; i++)
13776 int xx = x + xy[i].x;
13777 int yy = y + xy[i].y;
13778 int border_side = trigger_sides[i][1];
13779 int border_element;
13781 if (!IN_LEV_FIELD(xx, yy))
13784 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13785 continue; // center and border element not connected
13787 border_element = Tile[xx][yy];
13789 // check for change of center element (but change it only once)
13790 if (CheckElementChangeBySide(x, y, center_element, border_element,
13791 CE_NEXT_TO_X, border_side))
13796 void TestIfElementTouchesCustomElement(int x, int y)
13798 struct XY *xy = xy_topdown;
13799 static int trigger_sides[4][2] =
13801 // center side border side
13802 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13803 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13804 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13805 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13807 static int touch_dir[4] =
13809 MV_LEFT | MV_RIGHT,
13814 boolean change_center_element = FALSE;
13815 int center_element = Tile[x][y]; // should always be non-moving!
13816 int border_element_old[NUM_DIRECTIONS];
13819 for (i = 0; i < NUM_DIRECTIONS; i++)
13821 int xx = x + xy[i].x;
13822 int yy = y + xy[i].y;
13823 int border_element;
13825 border_element_old[i] = -1;
13827 if (!IN_LEV_FIELD(xx, yy))
13830 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13831 border_element = Tile[xx][yy]; // may be moving!
13832 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13833 border_element = Tile[xx][yy];
13834 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13835 border_element = MovingOrBlocked2Element(xx, yy);
13837 continue; // center and border element do not touch
13839 border_element_old[i] = border_element;
13842 for (i = 0; i < NUM_DIRECTIONS; i++)
13844 int xx = x + xy[i].x;
13845 int yy = y + xy[i].y;
13846 int center_side = trigger_sides[i][0];
13847 int border_element = border_element_old[i];
13849 if (border_element == -1)
13852 // check for change of border element
13853 CheckElementChangeBySide(xx, yy, border_element, center_element,
13854 CE_TOUCHING_X, center_side);
13856 // (center element cannot be player, so we don't have to check this here)
13859 for (i = 0; i < NUM_DIRECTIONS; i++)
13861 int xx = x + xy[i].x;
13862 int yy = y + xy[i].y;
13863 int border_side = trigger_sides[i][1];
13864 int border_element = border_element_old[i];
13866 if (border_element == -1)
13869 // check for change of center element (but change it only once)
13870 if (!change_center_element)
13871 change_center_element =
13872 CheckElementChangeBySide(x, y, center_element, border_element,
13873 CE_TOUCHING_X, border_side);
13875 if (IS_PLAYER(xx, yy))
13877 /* use player element that is initially defined in the level playfield,
13878 not the player element that corresponds to the runtime player number
13879 (example: a level that contains EL_PLAYER_3 as the only player would
13880 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13881 int player_element = PLAYERINFO(xx, yy)->initial_element;
13883 // as element "X" is the player here, check opposite (border) side
13884 CheckElementChangeBySide(x, y, center_element, player_element,
13885 CE_TOUCHING_X, border_side);
13890 void TestIfElementHitsCustomElement(int x, int y, int direction)
13892 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13893 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13894 int hitx = x + dx, hity = y + dy;
13895 int hitting_element = Tile[x][y];
13896 int touched_element;
13898 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13901 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13902 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13904 if (IN_LEV_FIELD(hitx, hity))
13906 int opposite_direction = MV_DIR_OPPOSITE(direction);
13907 int hitting_side = direction;
13908 int touched_side = opposite_direction;
13909 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13910 MovDir[hitx][hity] != direction ||
13911 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13917 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13918 CE_HITTING_X, touched_side);
13920 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13921 CE_HIT_BY_X, hitting_side);
13923 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13924 CE_HIT_BY_SOMETHING, opposite_direction);
13926 if (IS_PLAYER(hitx, hity))
13928 /* use player element that is initially defined in the level playfield,
13929 not the player element that corresponds to the runtime player number
13930 (example: a level that contains EL_PLAYER_3 as the only player would
13931 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13932 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13934 CheckElementChangeBySide(x, y, hitting_element, player_element,
13935 CE_HITTING_X, touched_side);
13940 // "hitting something" is also true when hitting the playfield border
13941 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13942 CE_HITTING_SOMETHING, direction);
13945 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13947 int i, kill_x = -1, kill_y = -1;
13949 int bad_element = -1;
13950 struct XY *test_xy = xy_topdown;
13951 static int test_dir[4] =
13959 for (i = 0; i < NUM_DIRECTIONS; i++)
13961 int test_x, test_y, test_move_dir, test_element;
13963 test_x = good_x + test_xy[i].x;
13964 test_y = good_y + test_xy[i].y;
13966 if (!IN_LEV_FIELD(test_x, test_y))
13970 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13972 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13974 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13975 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13977 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13978 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13982 bad_element = test_element;
13988 if (kill_x != -1 || kill_y != -1)
13990 if (IS_PLAYER(good_x, good_y))
13992 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13994 if (player->shield_deadly_time_left > 0 &&
13995 !IS_INDESTRUCTIBLE(bad_element))
13996 Bang(kill_x, kill_y);
13997 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13998 KillPlayer(player);
14001 Bang(good_x, good_y);
14005 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
14007 int i, kill_x = -1, kill_y = -1;
14008 int bad_element = Tile[bad_x][bad_y];
14009 struct XY *test_xy = xy_topdown;
14010 static int touch_dir[4] =
14012 MV_LEFT | MV_RIGHT,
14017 static int test_dir[4] =
14025 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
14028 for (i = 0; i < NUM_DIRECTIONS; i++)
14030 int test_x, test_y, test_move_dir, test_element;
14032 test_x = bad_x + test_xy[i].x;
14033 test_y = bad_y + test_xy[i].y;
14035 if (!IN_LEV_FIELD(test_x, test_y))
14039 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14041 test_element = Tile[test_x][test_y];
14043 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
14044 2nd case: DONT_TOUCH style bad thing does not move away from good thing
14046 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
14047 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
14049 // good thing is player or penguin that does not move away
14050 if (IS_PLAYER(test_x, test_y))
14052 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14054 if (bad_element == EL_ROBOT && player->is_moving)
14055 continue; // robot does not kill player if he is moving
14057 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
14059 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
14060 continue; // center and border element do not touch
14068 else if (test_element == EL_PENGUIN)
14078 if (kill_x != -1 || kill_y != -1)
14080 if (IS_PLAYER(kill_x, kill_y))
14082 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14084 if (player->shield_deadly_time_left > 0 &&
14085 !IS_INDESTRUCTIBLE(bad_element))
14086 Bang(bad_x, bad_y);
14087 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14088 KillPlayer(player);
14091 Bang(kill_x, kill_y);
14095 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
14097 int bad_element = Tile[bad_x][bad_y];
14098 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
14099 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
14100 int test_x = bad_x + dx, test_y = bad_y + dy;
14101 int test_move_dir, test_element;
14102 int kill_x = -1, kill_y = -1;
14104 if (!IN_LEV_FIELD(test_x, test_y))
14108 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14110 test_element = Tile[test_x][test_y];
14112 if (test_move_dir != bad_move_dir)
14114 // good thing can be player or penguin that does not move away
14115 if (IS_PLAYER(test_x, test_y))
14117 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14119 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
14120 player as being hit when he is moving towards the bad thing, because
14121 the "get hit by" condition would be lost after the player stops) */
14122 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
14123 return; // player moves away from bad thing
14128 else if (test_element == EL_PENGUIN)
14135 if (kill_x != -1 || kill_y != -1)
14137 if (IS_PLAYER(kill_x, kill_y))
14139 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14141 if (player->shield_deadly_time_left > 0 &&
14142 !IS_INDESTRUCTIBLE(bad_element))
14143 Bang(bad_x, bad_y);
14144 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14145 KillPlayer(player);
14148 Bang(kill_x, kill_y);
14152 void TestIfPlayerTouchesBadThing(int x, int y)
14154 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14157 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
14159 TestIfGoodThingHitsBadThing(x, y, move_dir);
14162 void TestIfBadThingTouchesPlayer(int x, int y)
14164 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14167 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
14169 TestIfBadThingHitsGoodThing(x, y, move_dir);
14172 void TestIfFriendTouchesBadThing(int x, int y)
14174 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14177 void TestIfBadThingTouchesFriend(int x, int y)
14179 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14182 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
14184 int i, kill_x = bad_x, kill_y = bad_y;
14185 struct XY *xy = xy_topdown;
14187 for (i = 0; i < NUM_DIRECTIONS; i++)
14191 x = bad_x + xy[i].x;
14192 y = bad_y + xy[i].y;
14193 if (!IN_LEV_FIELD(x, y))
14196 element = Tile[x][y];
14197 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14198 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14206 if (kill_x != bad_x || kill_y != bad_y)
14207 Bang(bad_x, bad_y);
14210 void KillPlayer(struct PlayerInfo *player)
14212 int jx = player->jx, jy = player->jy;
14214 if (!player->active)
14218 Debug("game:playing:KillPlayer",
14219 "0: killed == %d, active == %d, reanimated == %d",
14220 player->killed, player->active, player->reanimated);
14223 /* the following code was introduced to prevent an infinite loop when calling
14225 -> CheckTriggeredElementChangeExt()
14226 -> ExecuteCustomElementAction()
14228 -> (infinitely repeating the above sequence of function calls)
14229 which occurs when killing the player while having a CE with the setting
14230 "kill player X when explosion of <player X>"; the solution using a new
14231 field "player->killed" was chosen for backwards compatibility, although
14232 clever use of the fields "player->active" etc. would probably also work */
14234 if (player->killed)
14238 player->killed = TRUE;
14240 // remove accessible field at the player's position
14241 RemoveField(jx, jy);
14243 // deactivate shield (else Bang()/Explode() would not work right)
14244 player->shield_normal_time_left = 0;
14245 player->shield_deadly_time_left = 0;
14248 Debug("game:playing:KillPlayer",
14249 "1: killed == %d, active == %d, reanimated == %d",
14250 player->killed, player->active, player->reanimated);
14256 Debug("game:playing:KillPlayer",
14257 "2: killed == %d, active == %d, reanimated == %d",
14258 player->killed, player->active, player->reanimated);
14261 if (player->reanimated) // killed player may have been reanimated
14262 player->killed = player->reanimated = FALSE;
14264 BuryPlayer(player);
14267 static void KillPlayerUnlessEnemyProtected(int x, int y)
14269 if (!PLAYER_ENEMY_PROTECTED(x, y))
14270 KillPlayer(PLAYERINFO(x, y));
14273 static void KillPlayerUnlessExplosionProtected(int x, int y)
14275 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14276 KillPlayer(PLAYERINFO(x, y));
14279 void BuryPlayer(struct PlayerInfo *player)
14281 int jx = player->jx, jy = player->jy;
14283 if (!player->active)
14286 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14288 RemovePlayer(player);
14290 player->buried = TRUE;
14292 if (game.all_players_gone)
14293 game.GameOver = TRUE;
14296 void RemovePlayer(struct PlayerInfo *player)
14298 int jx = player->jx, jy = player->jy;
14299 int i, found = FALSE;
14301 player->present = FALSE;
14302 player->active = FALSE;
14304 // required for some CE actions (even if the player is not active anymore)
14305 player->MovPos = 0;
14307 if (!ExplodeField[jx][jy])
14308 StorePlayer[jx][jy] = 0;
14310 if (player->is_moving)
14311 TEST_DrawLevelField(player->last_jx, player->last_jy);
14313 for (i = 0; i < MAX_PLAYERS; i++)
14314 if (stored_player[i].active)
14319 game.all_players_gone = TRUE;
14320 game.GameOver = TRUE;
14323 game.exit_x = game.robot_wheel_x = jx;
14324 game.exit_y = game.robot_wheel_y = jy;
14327 void ExitPlayer(struct PlayerInfo *player)
14329 DrawPlayer(player); // needed here only to cleanup last field
14330 RemovePlayer(player);
14332 if (game.players_still_needed > 0)
14333 game.players_still_needed--;
14336 static void SetFieldForSnapping(int x, int y, int element, int direction,
14337 int player_index_bit)
14339 struct ElementInfo *ei = &element_info[element];
14340 int direction_bit = MV_DIR_TO_BIT(direction);
14341 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14342 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14343 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14345 Tile[x][y] = EL_ELEMENT_SNAPPING;
14346 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14347 MovDir[x][y] = direction;
14348 Store[x][y] = element;
14349 Store2[x][y] = player_index_bit;
14351 ResetGfxAnimation(x, y);
14353 GfxElement[x][y] = element;
14354 GfxAction[x][y] = action;
14355 GfxDir[x][y] = direction;
14356 GfxFrame[x][y] = -1;
14359 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14360 int player_index_bit)
14362 TestIfElementTouchesCustomElement(x, y); // for empty space
14364 if (level.finish_dig_collect)
14366 int dig_side = MV_DIR_OPPOSITE(direction);
14367 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14368 CE_PLAYER_COLLECTS_X);
14370 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14371 player_index_bit, dig_side);
14372 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14373 player_index_bit, dig_side);
14378 =============================================================================
14379 checkDiagonalPushing()
14380 -----------------------------------------------------------------------------
14381 check if diagonal input device direction results in pushing of object
14382 (by checking if the alternative direction is walkable, diggable, ...)
14383 =============================================================================
14386 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14387 int x, int y, int real_dx, int real_dy)
14389 int jx, jy, dx, dy, xx, yy;
14391 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14394 // diagonal direction: check alternative direction
14399 xx = jx + (dx == 0 ? real_dx : 0);
14400 yy = jy + (dy == 0 ? real_dy : 0);
14402 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14406 =============================================================================
14408 -----------------------------------------------------------------------------
14409 x, y: field next to player (non-diagonal) to try to dig to
14410 real_dx, real_dy: direction as read from input device (can be diagonal)
14411 =============================================================================
14414 static int DigField(struct PlayerInfo *player,
14415 int oldx, int oldy, int x, int y,
14416 int real_dx, int real_dy, int mode)
14418 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14419 boolean player_was_pushing = player->is_pushing;
14420 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14421 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14422 int jx = oldx, jy = oldy;
14423 int dx = x - jx, dy = y - jy;
14424 int nextx = x + dx, nexty = y + dy;
14425 int move_direction = (dx == -1 ? MV_LEFT :
14426 dx == +1 ? MV_RIGHT :
14428 dy == +1 ? MV_DOWN : MV_NONE);
14429 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14430 int dig_side = MV_DIR_OPPOSITE(move_direction);
14431 int old_element = Tile[jx][jy];
14432 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14435 if (is_player) // function can also be called by EL_PENGUIN
14437 if (player->MovPos == 0)
14439 player->is_digging = FALSE;
14440 player->is_collecting = FALSE;
14443 if (player->MovPos == 0) // last pushing move finished
14444 player->is_pushing = FALSE;
14446 if (mode == DF_NO_PUSH) // player just stopped pushing
14448 player->is_switching = FALSE;
14449 player->push_delay = -1;
14451 return MP_NO_ACTION;
14454 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14455 old_element = Back[jx][jy];
14457 // in case of element dropped at player position, check background
14458 else if (Back[jx][jy] != EL_EMPTY &&
14459 game.engine_version >= VERSION_IDENT(2,2,0,0))
14460 old_element = Back[jx][jy];
14462 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14463 return MP_NO_ACTION; // field has no opening in this direction
14465 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction))
14466 return MP_NO_ACTION; // field has no opening in this direction
14468 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14472 Tile[jx][jy] = player->artwork_element;
14473 InitMovingField(jx, jy, MV_DOWN);
14474 Store[jx][jy] = EL_ACID;
14475 ContinueMoving(jx, jy);
14476 BuryPlayer(player);
14478 return MP_DONT_RUN_INTO;
14481 if (player_can_move && DONT_RUN_INTO(element))
14483 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14485 return MP_DONT_RUN_INTO;
14488 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14489 return MP_NO_ACTION;
14491 collect_count = element_info[element].collect_count_initial;
14493 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14494 return MP_NO_ACTION;
14496 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14497 player_can_move = player_can_move_or_snap;
14499 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14500 game.engine_version >= VERSION_IDENT(2,2,0,0))
14502 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14503 player->index_bit, dig_side);
14504 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14505 player->index_bit, dig_side);
14507 if (element == EL_DC_LANDMINE)
14510 if (Tile[x][y] != element) // field changed by snapping
14513 return MP_NO_ACTION;
14516 if (player->gravity && is_player && !player->is_auto_moving &&
14517 canFallDown(player) && move_direction != MV_DOWN &&
14518 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14519 return MP_NO_ACTION; // player cannot walk here due to gravity
14521 if (player_can_move &&
14522 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14524 int sound_element = SND_ELEMENT(element);
14525 int sound_action = ACTION_WALKING;
14527 if (IS_RND_GATE(element))
14529 if (!player->key[RND_GATE_NR(element)])
14530 return MP_NO_ACTION;
14532 else if (IS_RND_GATE_GRAY(element))
14534 if (!player->key[RND_GATE_GRAY_NR(element)])
14535 return MP_NO_ACTION;
14537 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14539 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14540 return MP_NO_ACTION;
14542 else if (element == EL_EXIT_OPEN ||
14543 element == EL_EM_EXIT_OPEN ||
14544 element == EL_EM_EXIT_OPENING ||
14545 element == EL_STEEL_EXIT_OPEN ||
14546 element == EL_EM_STEEL_EXIT_OPEN ||
14547 element == EL_EM_STEEL_EXIT_OPENING ||
14548 element == EL_SP_EXIT_OPEN ||
14549 element == EL_SP_EXIT_OPENING)
14551 sound_action = ACTION_PASSING; // player is passing exit
14553 else if (element == EL_EMPTY)
14555 sound_action = ACTION_MOVING; // nothing to walk on
14558 // play sound from background or player, whatever is available
14559 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14560 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14562 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14564 else if (player_can_move &&
14565 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14567 if (!ACCESS_FROM(element, opposite_direction))
14568 return MP_NO_ACTION; // field not accessible from this direction
14570 if (CAN_MOVE(element)) // only fixed elements can be passed!
14571 return MP_NO_ACTION;
14573 if (IS_EM_GATE(element))
14575 if (!player->key[EM_GATE_NR(element)])
14576 return MP_NO_ACTION;
14578 else if (IS_EM_GATE_GRAY(element))
14580 if (!player->key[EM_GATE_GRAY_NR(element)])
14581 return MP_NO_ACTION;
14583 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14585 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14586 return MP_NO_ACTION;
14588 else if (IS_EMC_GATE(element))
14590 if (!player->key[EMC_GATE_NR(element)])
14591 return MP_NO_ACTION;
14593 else if (IS_EMC_GATE_GRAY(element))
14595 if (!player->key[EMC_GATE_GRAY_NR(element)])
14596 return MP_NO_ACTION;
14598 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14600 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14601 return MP_NO_ACTION;
14603 else if (element == EL_DC_GATE_WHITE ||
14604 element == EL_DC_GATE_WHITE_GRAY ||
14605 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14607 if (player->num_white_keys == 0)
14608 return MP_NO_ACTION;
14610 player->num_white_keys--;
14612 else if (IS_SP_PORT(element))
14614 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14615 element == EL_SP_GRAVITY_PORT_RIGHT ||
14616 element == EL_SP_GRAVITY_PORT_UP ||
14617 element == EL_SP_GRAVITY_PORT_DOWN)
14618 player->gravity = !player->gravity;
14619 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14620 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14621 element == EL_SP_GRAVITY_ON_PORT_UP ||
14622 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14623 player->gravity = TRUE;
14624 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14625 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14626 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14627 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14628 player->gravity = FALSE;
14631 // automatically move to the next field with double speed
14632 player->programmed_action = move_direction;
14634 if (player->move_delay_reset_counter == 0)
14636 player->move_delay_reset_counter = 2; // two double speed steps
14638 DOUBLE_PLAYER_SPEED(player);
14641 PlayLevelSoundAction(x, y, ACTION_PASSING);
14643 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14647 if (mode != DF_SNAP)
14649 GfxElement[x][y] = GFX_ELEMENT(element);
14650 player->is_digging = TRUE;
14653 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14655 // use old behaviour for old levels (digging)
14656 if (!level.finish_dig_collect)
14658 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14659 player->index_bit, dig_side);
14661 // if digging triggered player relocation, finish digging tile
14662 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14663 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14666 if (mode == DF_SNAP)
14668 if (level.block_snap_field)
14669 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14671 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14673 // use old behaviour for old levels (snapping)
14674 if (!level.finish_dig_collect)
14675 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14676 player->index_bit, dig_side);
14679 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14683 if (is_player && mode != DF_SNAP)
14685 GfxElement[x][y] = element;
14686 player->is_collecting = TRUE;
14689 if (element == EL_SPEED_PILL)
14691 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14693 else if (element == EL_EXTRA_TIME && level.time > 0)
14695 TimeLeft += level.extra_time;
14697 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14699 DisplayGameControlValues();
14701 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14703 int shield_time = (element == EL_SHIELD_DEADLY ?
14704 level.shield_deadly_time :
14705 level.shield_normal_time);
14707 player->shield_normal_time_left += shield_time;
14708 if (element == EL_SHIELD_DEADLY)
14709 player->shield_deadly_time_left += shield_time;
14711 else if (element == EL_DYNAMITE ||
14712 element == EL_EM_DYNAMITE ||
14713 element == EL_SP_DISK_RED)
14715 if (player->inventory_size < MAX_INVENTORY_SIZE)
14716 player->inventory_element[player->inventory_size++] = element;
14718 DrawGameDoorValues();
14720 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14722 player->dynabomb_count++;
14723 player->dynabombs_left++;
14725 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14727 player->dynabomb_size++;
14729 else if (element == EL_DYNABOMB_INCREASE_POWER)
14731 player->dynabomb_xl = TRUE;
14733 else if (IS_KEY(element))
14735 player->key[KEY_NR(element)] = TRUE;
14737 DrawGameDoorValues();
14739 else if (element == EL_DC_KEY_WHITE)
14741 player->num_white_keys++;
14743 // display white keys?
14744 // DrawGameDoorValues();
14746 else if (IS_ENVELOPE(element))
14748 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14750 if (!wait_for_snapping)
14751 player->show_envelope = element;
14753 else if (element == EL_EMC_LENSES)
14755 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14757 RedrawAllInvisibleElementsForLenses();
14759 else if (element == EL_EMC_MAGNIFIER)
14761 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14763 RedrawAllInvisibleElementsForMagnifier();
14765 else if (IS_DROPPABLE(element) ||
14766 IS_THROWABLE(element)) // can be collected and dropped
14770 if (collect_count == 0)
14771 player->inventory_infinite_element = element;
14773 for (i = 0; i < collect_count; i++)
14774 if (player->inventory_size < MAX_INVENTORY_SIZE)
14775 player->inventory_element[player->inventory_size++] = element;
14777 DrawGameDoorValues();
14779 else if (collect_count > 0)
14781 game.gems_still_needed -= collect_count;
14782 if (game.gems_still_needed < 0)
14783 game.gems_still_needed = 0;
14785 game.snapshot.collected_item = TRUE;
14787 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14789 DisplayGameControlValues();
14792 RaiseScoreElement(element);
14793 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14795 // use old behaviour for old levels (collecting)
14796 if (!level.finish_dig_collect && is_player)
14798 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14799 player->index_bit, dig_side);
14801 // if collecting triggered player relocation, finish collecting tile
14802 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14803 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14806 if (mode == DF_SNAP)
14808 if (level.block_snap_field)
14809 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14811 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14813 // use old behaviour for old levels (snapping)
14814 if (!level.finish_dig_collect)
14815 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14816 player->index_bit, dig_side);
14819 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14821 if (mode == DF_SNAP && element != EL_BD_ROCK)
14822 return MP_NO_ACTION;
14824 if (CAN_FALL(element) && dy)
14825 return MP_NO_ACTION;
14827 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14828 !(element == EL_SPRING && level.use_spring_bug))
14829 return MP_NO_ACTION;
14831 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14832 ((move_direction & MV_VERTICAL &&
14833 ((element_info[element].move_pattern & MV_LEFT &&
14834 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14835 (element_info[element].move_pattern & MV_RIGHT &&
14836 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14837 (move_direction & MV_HORIZONTAL &&
14838 ((element_info[element].move_pattern & MV_UP &&
14839 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14840 (element_info[element].move_pattern & MV_DOWN &&
14841 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14842 return MP_NO_ACTION;
14844 // do not push elements already moving away faster than player
14845 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14846 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14847 return MP_NO_ACTION;
14849 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14851 if (player->push_delay_value == -1 || !player_was_pushing)
14852 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14854 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14856 if (player->push_delay_value == -1)
14857 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14859 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14861 if (!player->is_pushing)
14862 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14865 player->is_pushing = TRUE;
14866 player->is_active = TRUE;
14868 if (!(IN_LEV_FIELD(nextx, nexty) &&
14869 (IS_FREE(nextx, nexty) ||
14870 (IS_SB_ELEMENT(element) &&
14871 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14872 (IS_CUSTOM_ELEMENT(element) &&
14873 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14874 return MP_NO_ACTION;
14876 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14877 return MP_NO_ACTION;
14879 if (player->push_delay == -1) // new pushing; restart delay
14880 player->push_delay = 0;
14882 if (player->push_delay < player->push_delay_value &&
14883 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14884 element != EL_SPRING && element != EL_BALLOON)
14886 // make sure that there is no move delay before next try to push
14887 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14888 player->move_delay = 0;
14890 return MP_NO_ACTION;
14893 if (IS_CUSTOM_ELEMENT(element) &&
14894 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14896 if (!DigFieldByCE(nextx, nexty, element))
14897 return MP_NO_ACTION;
14900 if (IS_SB_ELEMENT(element))
14902 boolean sokoban_task_solved = FALSE;
14904 if (element == EL_SOKOBAN_FIELD_FULL)
14906 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14908 IncrementSokobanFieldsNeeded();
14909 IncrementSokobanObjectsNeeded();
14912 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14914 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14916 DecrementSokobanFieldsNeeded();
14917 DecrementSokobanObjectsNeeded();
14919 // sokoban object was pushed from empty field to sokoban field
14920 if (Back[x][y] == EL_EMPTY)
14921 sokoban_task_solved = TRUE;
14924 Tile[x][y] = EL_SOKOBAN_OBJECT;
14926 if (Back[x][y] == Back[nextx][nexty])
14927 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14928 else if (Back[x][y] != 0)
14929 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14932 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14935 if (sokoban_task_solved &&
14936 game.sokoban_fields_still_needed == 0 &&
14937 game.sokoban_objects_still_needed == 0 &&
14938 level.auto_exit_sokoban)
14940 game.players_still_needed = 0;
14944 PlaySound(SND_GAME_SOKOBAN_SOLVING);
14948 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14950 InitMovingField(x, y, move_direction);
14951 GfxAction[x][y] = ACTION_PUSHING;
14953 if (mode == DF_SNAP)
14954 ContinueMoving(x, y);
14956 MovPos[x][y] = (dx != 0 ? dx : dy);
14958 Pushed[x][y] = TRUE;
14959 Pushed[nextx][nexty] = TRUE;
14961 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14962 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14964 player->push_delay_value = -1; // get new value later
14966 // check for element change _after_ element has been pushed
14967 if (game.use_change_when_pushing_bug)
14969 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14970 player->index_bit, dig_side);
14971 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14972 player->index_bit, dig_side);
14975 else if (IS_SWITCHABLE(element))
14977 if (PLAYER_SWITCHING(player, x, y))
14979 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14980 player->index_bit, dig_side);
14985 player->is_switching = TRUE;
14986 player->switch_x = x;
14987 player->switch_y = y;
14989 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14991 if (element == EL_ROBOT_WHEEL)
14993 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14995 game.robot_wheel_x = x;
14996 game.robot_wheel_y = y;
14997 game.robot_wheel_active = TRUE;
14999 TEST_DrawLevelField(x, y);
15001 else if (element == EL_SP_TERMINAL)
15005 SCAN_PLAYFIELD(xx, yy)
15007 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
15011 else if (Tile[xx][yy] == EL_SP_TERMINAL)
15013 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
15015 ResetGfxAnimation(xx, yy);
15016 TEST_DrawLevelField(xx, yy);
15020 else if (IS_BELT_SWITCH(element))
15022 ToggleBeltSwitch(x, y);
15024 else if (element == EL_SWITCHGATE_SWITCH_UP ||
15025 element == EL_SWITCHGATE_SWITCH_DOWN ||
15026 element == EL_DC_SWITCHGATE_SWITCH_UP ||
15027 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
15029 ToggleSwitchgateSwitch();
15031 else if (element == EL_LIGHT_SWITCH ||
15032 element == EL_LIGHT_SWITCH_ACTIVE)
15034 ToggleLightSwitch(x, y);
15036 else if (element == EL_TIMEGATE_SWITCH ||
15037 element == EL_DC_TIMEGATE_SWITCH)
15039 ActivateTimegateSwitch(x, y);
15041 else if (element == EL_BALLOON_SWITCH_LEFT ||
15042 element == EL_BALLOON_SWITCH_RIGHT ||
15043 element == EL_BALLOON_SWITCH_UP ||
15044 element == EL_BALLOON_SWITCH_DOWN ||
15045 element == EL_BALLOON_SWITCH_NONE ||
15046 element == EL_BALLOON_SWITCH_ANY)
15048 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
15049 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
15050 element == EL_BALLOON_SWITCH_UP ? MV_UP :
15051 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
15052 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
15055 else if (element == EL_LAMP)
15057 Tile[x][y] = EL_LAMP_ACTIVE;
15058 game.lights_still_needed--;
15060 ResetGfxAnimation(x, y);
15061 TEST_DrawLevelField(x, y);
15063 else if (element == EL_TIME_ORB_FULL)
15065 Tile[x][y] = EL_TIME_ORB_EMPTY;
15067 if (level.time > 0 || level.use_time_orb_bug)
15069 TimeLeft += level.time_orb_time;
15070 game.no_level_time_limit = FALSE;
15072 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
15074 DisplayGameControlValues();
15077 ResetGfxAnimation(x, y);
15078 TEST_DrawLevelField(x, y);
15080 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
15081 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15085 game.ball_active = !game.ball_active;
15087 SCAN_PLAYFIELD(xx, yy)
15089 int e = Tile[xx][yy];
15091 if (game.ball_active)
15093 if (e == EL_EMC_MAGIC_BALL)
15094 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
15095 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
15096 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
15100 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
15101 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
15102 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15103 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
15108 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15109 player->index_bit, dig_side);
15111 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15112 player->index_bit, dig_side);
15114 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15115 player->index_bit, dig_side);
15121 if (!PLAYER_SWITCHING(player, x, y))
15123 player->is_switching = TRUE;
15124 player->switch_x = x;
15125 player->switch_y = y;
15127 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
15128 player->index_bit, dig_side);
15129 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15130 player->index_bit, dig_side);
15132 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
15133 player->index_bit, dig_side);
15134 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15135 player->index_bit, dig_side);
15138 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
15139 player->index_bit, dig_side);
15140 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15141 player->index_bit, dig_side);
15143 return MP_NO_ACTION;
15146 player->push_delay = -1;
15148 if (is_player) // function can also be called by EL_PENGUIN
15150 if (Tile[x][y] != element) // really digged/collected something
15152 player->is_collecting = !player->is_digging;
15153 player->is_active = TRUE;
15155 player->last_removed_element = element;
15162 static boolean DigFieldByCE(int x, int y, int digging_element)
15164 int element = Tile[x][y];
15166 if (!IS_FREE(x, y))
15168 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
15169 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
15172 // no element can dig solid indestructible elements
15173 if (IS_INDESTRUCTIBLE(element) &&
15174 !IS_DIGGABLE(element) &&
15175 !IS_COLLECTIBLE(element))
15178 if (AmoebaNr[x][y] &&
15179 (element == EL_AMOEBA_FULL ||
15180 element == EL_BD_AMOEBA ||
15181 element == EL_AMOEBA_GROWING))
15183 AmoebaCnt[AmoebaNr[x][y]]--;
15184 AmoebaCnt2[AmoebaNr[x][y]]--;
15187 if (IS_MOVING(x, y))
15188 RemoveMovingField(x, y);
15192 TEST_DrawLevelField(x, y);
15195 // if digged element was about to explode, prevent the explosion
15196 ExplodeField[x][y] = EX_TYPE_NONE;
15198 PlayLevelSoundAction(x, y, action);
15201 Store[x][y] = EL_EMPTY;
15203 // this makes it possible to leave the removed element again
15204 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15205 Store[x][y] = element;
15210 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15212 int jx = player->jx, jy = player->jy;
15213 int x = jx + dx, y = jy + dy;
15214 int snap_direction = (dx == -1 ? MV_LEFT :
15215 dx == +1 ? MV_RIGHT :
15217 dy == +1 ? MV_DOWN : MV_NONE);
15218 boolean can_continue_snapping = (level.continuous_snapping &&
15219 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15221 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15224 if (!player->active || !IN_LEV_FIELD(x, y))
15232 if (player->MovPos == 0)
15233 player->is_pushing = FALSE;
15235 player->is_snapping = FALSE;
15237 if (player->MovPos == 0)
15239 player->is_moving = FALSE;
15240 player->is_digging = FALSE;
15241 player->is_collecting = FALSE;
15247 // prevent snapping with already pressed snap key when not allowed
15248 if (player->is_snapping && !can_continue_snapping)
15251 player->MovDir = snap_direction;
15253 if (player->MovPos == 0)
15255 player->is_moving = FALSE;
15256 player->is_digging = FALSE;
15257 player->is_collecting = FALSE;
15260 player->is_dropping = FALSE;
15261 player->is_dropping_pressed = FALSE;
15262 player->drop_pressed_delay = 0;
15264 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15267 player->is_snapping = TRUE;
15268 player->is_active = TRUE;
15270 if (player->MovPos == 0)
15272 player->is_moving = FALSE;
15273 player->is_digging = FALSE;
15274 player->is_collecting = FALSE;
15277 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15278 TEST_DrawLevelField(player->last_jx, player->last_jy);
15280 TEST_DrawLevelField(x, y);
15285 static boolean DropElement(struct PlayerInfo *player)
15287 int old_element, new_element;
15288 int dropx = player->jx, dropy = player->jy;
15289 int drop_direction = player->MovDir;
15290 int drop_side = drop_direction;
15291 int drop_element = get_next_dropped_element(player);
15293 /* do not drop an element on top of another element; when holding drop key
15294 pressed without moving, dropped element must move away before the next
15295 element can be dropped (this is especially important if the next element
15296 is dynamite, which can be placed on background for historical reasons) */
15297 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15300 if (IS_THROWABLE(drop_element))
15302 dropx += GET_DX_FROM_DIR(drop_direction);
15303 dropy += GET_DY_FROM_DIR(drop_direction);
15305 if (!IN_LEV_FIELD(dropx, dropy))
15309 old_element = Tile[dropx][dropy]; // old element at dropping position
15310 new_element = drop_element; // default: no change when dropping
15312 // check if player is active, not moving and ready to drop
15313 if (!player->active || player->MovPos || player->drop_delay > 0)
15316 // check if player has anything that can be dropped
15317 if (new_element == EL_UNDEFINED)
15320 // only set if player has anything that can be dropped
15321 player->is_dropping_pressed = TRUE;
15323 // check if drop key was pressed long enough for EM style dynamite
15324 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15327 // check if anything can be dropped at the current position
15328 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15331 // collected custom elements can only be dropped on empty fields
15332 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15335 if (old_element != EL_EMPTY)
15336 Back[dropx][dropy] = old_element; // store old element on this field
15338 ResetGfxAnimation(dropx, dropy);
15339 ResetRandomAnimationValue(dropx, dropy);
15341 if (player->inventory_size > 0 ||
15342 player->inventory_infinite_element != EL_UNDEFINED)
15344 if (player->inventory_size > 0)
15346 player->inventory_size--;
15348 DrawGameDoorValues();
15350 if (new_element == EL_DYNAMITE)
15351 new_element = EL_DYNAMITE_ACTIVE;
15352 else if (new_element == EL_EM_DYNAMITE)
15353 new_element = EL_EM_DYNAMITE_ACTIVE;
15354 else if (new_element == EL_SP_DISK_RED)
15355 new_element = EL_SP_DISK_RED_ACTIVE;
15358 Tile[dropx][dropy] = new_element;
15360 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15361 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15362 el2img(Tile[dropx][dropy]), 0);
15364 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15366 // needed if previous element just changed to "empty" in the last frame
15367 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15369 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15370 player->index_bit, drop_side);
15371 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15373 player->index_bit, drop_side);
15375 TestIfElementTouchesCustomElement(dropx, dropy);
15377 else // player is dropping a dyna bomb
15379 player->dynabombs_left--;
15381 Tile[dropx][dropy] = new_element;
15383 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15384 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15385 el2img(Tile[dropx][dropy]), 0);
15387 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15390 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15391 InitField_WithBug1(dropx, dropy, FALSE);
15393 new_element = Tile[dropx][dropy]; // element might have changed
15395 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15396 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15398 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15399 MovDir[dropx][dropy] = drop_direction;
15401 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15403 // do not cause impact style collision by dropping elements that can fall
15404 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15407 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15408 player->is_dropping = TRUE;
15410 player->drop_pressed_delay = 0;
15411 player->is_dropping_pressed = FALSE;
15413 player->drop_x = dropx;
15414 player->drop_y = dropy;
15419 // ----------------------------------------------------------------------------
15420 // game sound playing functions
15421 // ----------------------------------------------------------------------------
15423 static int *loop_sound_frame = NULL;
15424 static int *loop_sound_volume = NULL;
15426 void InitPlayLevelSound(void)
15428 int num_sounds = getSoundListSize();
15430 checked_free(loop_sound_frame);
15431 checked_free(loop_sound_volume);
15433 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15434 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15437 static void PlayLevelSoundExt(int x, int y, int nr, boolean is_loop_sound)
15439 int sx = SCREENX(x), sy = SCREENY(y);
15440 int volume, stereo_position;
15441 int max_distance = 8;
15442 int type = (is_loop_sound ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15444 if ((!setup.sound_simple && !is_loop_sound) ||
15445 (!setup.sound_loops && is_loop_sound))
15448 if (!IN_LEV_FIELD(x, y) ||
15449 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15450 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15453 volume = SOUND_MAX_VOLUME;
15455 if (!IN_SCR_FIELD(sx, sy))
15457 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15458 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15460 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15463 stereo_position = (SOUND_MAX_LEFT +
15464 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15465 (SCR_FIELDX + 2 * max_distance));
15469 /* This assures that quieter loop sounds do not overwrite louder ones,
15470 while restarting sound volume comparison with each new game frame. */
15472 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15475 loop_sound_volume[nr] = volume;
15476 loop_sound_frame[nr] = FrameCounter;
15479 PlaySoundExt(nr, volume, stereo_position, type);
15482 static void PlayLevelSound(int x, int y, int nr)
15484 PlayLevelSoundExt(x, y, nr, IS_LOOP_SOUND(nr));
15487 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15489 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15490 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15491 y < LEVELY(BY1) ? LEVELY(BY1) :
15492 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15496 static void PlayLevelSoundAction(int x, int y, int action)
15498 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15501 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15503 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15505 if (sound_effect != SND_UNDEFINED)
15506 PlayLevelSound(x, y, sound_effect);
15509 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15512 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15514 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15515 PlayLevelSound(x, y, sound_effect);
15518 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15520 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15522 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15523 PlayLevelSound(x, y, sound_effect);
15526 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15528 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15530 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15531 StopSound(sound_effect);
15534 static int getLevelMusicNr(void)
15536 int level_pos = level_nr - leveldir_current->first_level;
15538 if (levelset.music[level_nr] != MUS_UNDEFINED)
15539 return levelset.music[level_nr]; // from config file
15541 return MAP_NOCONF_MUSIC(level_pos); // from music dir
15544 static void FadeLevelSounds(void)
15549 static void FadeLevelMusic(void)
15551 int music_nr = getLevelMusicNr();
15552 char *curr_music = getCurrentlyPlayingMusicFilename();
15553 char *next_music = getMusicInfoEntryFilename(music_nr);
15555 if (!strEqual(curr_music, next_music))
15559 void FadeLevelSoundsAndMusic(void)
15565 static void PlayLevelMusic(void)
15567 int music_nr = getLevelMusicNr();
15568 char *curr_music = getCurrentlyPlayingMusicFilename();
15569 char *next_music = getMusicInfoEntryFilename(music_nr);
15571 if (!strEqual(curr_music, next_music))
15572 PlayMusicLoop(music_nr);
15575 static int getSoundAction_BD(int sample)
15579 case GD_S_STONE_PUSHING:
15580 case GD_S_MEGA_STONE_PUSHING:
15581 case GD_S_FLYING_STONE_PUSHING:
15582 case GD_S_WAITING_STONE_PUSHING:
15583 case GD_S_CHASING_STONE_PUSHING:
15584 case GD_S_NUT_PUSHING:
15585 case GD_S_NITRO_PACK_PUSHING:
15586 case GD_S_BLADDER_PUSHING:
15587 case GD_S_BOX_PUSHING:
15588 return ACTION_PUSHING;
15590 case GD_S_STONE_FALLING:
15591 case GD_S_MEGA_STONE_FALLING:
15592 case GD_S_FLYING_STONE_FALLING:
15593 case GD_S_NUT_FALLING:
15594 case GD_S_DIRT_BALL_FALLING:
15595 case GD_S_DIRT_LOOSE_FALLING:
15596 case GD_S_NITRO_PACK_FALLING:
15597 case GD_S_FALLING_WALL_FALLING:
15598 return ACTION_FALLING;
15600 case GD_S_STONE_IMPACT:
15601 case GD_S_MEGA_STONE_IMPACT:
15602 case GD_S_FLYING_STONE_IMPACT:
15603 case GD_S_NUT_IMPACT:
15604 case GD_S_DIRT_BALL_IMPACT:
15605 case GD_S_DIRT_LOOSE_IMPACT:
15606 case GD_S_NITRO_PACK_IMPACT:
15607 case GD_S_FALLING_WALL_IMPACT:
15608 return ACTION_IMPACT;
15610 case GD_S_NUT_CRACKING:
15611 return ACTION_BREAKING;
15613 case GD_S_EXPANDING_WALL:
15614 case GD_S_WALL_REAPPEARING:
15617 case GD_S_ACID_SPREADING:
15618 return ACTION_GROWING;
15620 case GD_S_DIAMOND_COLLECTING:
15621 case GD_S_FLYING_DIAMOND_COLLECTING:
15622 case GD_S_SKELETON_COLLECTING:
15623 case GD_S_PNEUMATIC_COLLECTING:
15624 case GD_S_BOMB_COLLECTING:
15625 case GD_S_CLOCK_COLLECTING:
15626 case GD_S_SWEET_COLLECTING:
15627 case GD_S_KEY_COLLECTING:
15628 case GD_S_DIAMOND_KEY_COLLECTING:
15629 return ACTION_COLLECTING;
15631 case GD_S_BOMB_PLACING:
15632 case GD_S_REPLICATOR:
15633 return ACTION_DROPPING;
15635 case GD_S_BLADDER_MOVING:
15636 return ACTION_MOVING;
15638 case GD_S_BLADDER_SPENDER:
15639 case GD_S_BLADDER_CONVERTING:
15640 case GD_S_GRAVITY_CHANGING:
15641 return ACTION_CHANGING;
15643 case GD_S_BITER_EATING:
15644 return ACTION_EATING;
15646 case GD_S_DOOR_OPENING:
15647 case GD_S_CRACKING:
15648 return ACTION_OPENING;
15650 case GD_S_DIRT_WALKING:
15651 return ACTION_DIGGING;
15653 case GD_S_EMPTY_WALKING:
15654 return ACTION_WALKING;
15656 case GD_S_SWITCH_BITER:
15657 case GD_S_SWITCH_CREATURES:
15658 case GD_S_SWITCH_GRAVITY:
15659 case GD_S_SWITCH_EXPANDING:
15660 case GD_S_SWITCH_CONVEYOR:
15661 case GD_S_SWITCH_REPLICATOR:
15662 case GD_S_STIRRING:
15663 return ACTION_ACTIVATING;
15665 case GD_S_TELEPORTER:
15666 return ACTION_PASSING;
15668 case GD_S_EXPLODING:
15669 case GD_S_BOMB_EXPLODING:
15670 case GD_S_GHOST_EXPLODING:
15671 case GD_S_VOODOO_EXPLODING:
15672 case GD_S_NITRO_PACK_EXPLODING:
15673 return ACTION_EXPLODING;
15675 case GD_S_COVERING:
15677 case GD_S_MAGIC_WALL:
15678 case GD_S_PNEUMATIC_HAMMER:
15680 return ACTION_ACTIVE;
15682 case GD_S_DIAMOND_FALLING_RANDOM:
15683 case GD_S_DIAMOND_FALLING_1:
15684 case GD_S_DIAMOND_FALLING_2:
15685 case GD_S_DIAMOND_FALLING_3:
15686 case GD_S_DIAMOND_FALLING_4:
15687 case GD_S_DIAMOND_FALLING_5:
15688 case GD_S_DIAMOND_FALLING_6:
15689 case GD_S_DIAMOND_FALLING_7:
15690 case GD_S_DIAMOND_FALLING_8:
15691 case GD_S_DIAMOND_IMPACT_RANDOM:
15692 case GD_S_DIAMOND_IMPACT_1:
15693 case GD_S_DIAMOND_IMPACT_2:
15694 case GD_S_DIAMOND_IMPACT_3:
15695 case GD_S_DIAMOND_IMPACT_4:
15696 case GD_S_DIAMOND_IMPACT_5:
15697 case GD_S_DIAMOND_IMPACT_6:
15698 case GD_S_DIAMOND_IMPACT_7:
15699 case GD_S_DIAMOND_IMPACT_8:
15700 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15701 case GD_S_FLYING_DIAMOND_FALLING_1:
15702 case GD_S_FLYING_DIAMOND_FALLING_2:
15703 case GD_S_FLYING_DIAMOND_FALLING_3:
15704 case GD_S_FLYING_DIAMOND_FALLING_4:
15705 case GD_S_FLYING_DIAMOND_FALLING_5:
15706 case GD_S_FLYING_DIAMOND_FALLING_6:
15707 case GD_S_FLYING_DIAMOND_FALLING_7:
15708 case GD_S_FLYING_DIAMOND_FALLING_8:
15709 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15710 case GD_S_FLYING_DIAMOND_IMPACT_1:
15711 case GD_S_FLYING_DIAMOND_IMPACT_2:
15712 case GD_S_FLYING_DIAMOND_IMPACT_3:
15713 case GD_S_FLYING_DIAMOND_IMPACT_4:
15714 case GD_S_FLYING_DIAMOND_IMPACT_5:
15715 case GD_S_FLYING_DIAMOND_IMPACT_6:
15716 case GD_S_FLYING_DIAMOND_IMPACT_7:
15717 case GD_S_FLYING_DIAMOND_IMPACT_8:
15718 case GD_S_TIMEOUT_0:
15719 case GD_S_TIMEOUT_1:
15720 case GD_S_TIMEOUT_2:
15721 case GD_S_TIMEOUT_3:
15722 case GD_S_TIMEOUT_4:
15723 case GD_S_TIMEOUT_5:
15724 case GD_S_TIMEOUT_6:
15725 case GD_S_TIMEOUT_7:
15726 case GD_S_TIMEOUT_8:
15727 case GD_S_TIMEOUT_9:
15728 case GD_S_TIMEOUT_10:
15729 case GD_S_BONUS_LIFE:
15730 // trigger special post-processing (and force sound to be non-looping)
15731 return ACTION_OTHER;
15733 case GD_S_AMOEBA_MAGIC:
15734 case GD_S_FINISHED:
15735 // trigger special post-processing (and force sound to be looping)
15736 return ACTION_DEFAULT;
15739 return ACTION_DEFAULT;
15743 static int getSoundEffect_BD(int element_bd, int sample)
15745 int sound_action = getSoundAction_BD(sample);
15746 int sound_effect = element_info[SND_ELEMENT(element_bd)].sound[sound_action];
15750 if (sound_action != ACTION_OTHER &&
15751 sound_action != ACTION_DEFAULT)
15752 return sound_effect;
15754 // special post-processing for some sounds
15757 case GD_S_DIAMOND_FALLING_RANDOM:
15758 case GD_S_DIAMOND_FALLING_1:
15759 case GD_S_DIAMOND_FALLING_2:
15760 case GD_S_DIAMOND_FALLING_3:
15761 case GD_S_DIAMOND_FALLING_4:
15762 case GD_S_DIAMOND_FALLING_5:
15763 case GD_S_DIAMOND_FALLING_6:
15764 case GD_S_DIAMOND_FALLING_7:
15765 case GD_S_DIAMOND_FALLING_8:
15766 nr = (sample == GD_S_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15767 sample - GD_S_DIAMOND_FALLING_1);
15768 sound_effect = SND_BD_DIAMOND_FALLING_RANDOM_1 + nr;
15770 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15771 sound_effect = SND_BD_DIAMOND_FALLING;
15774 case GD_S_DIAMOND_IMPACT_RANDOM:
15775 case GD_S_DIAMOND_IMPACT_1:
15776 case GD_S_DIAMOND_IMPACT_2:
15777 case GD_S_DIAMOND_IMPACT_3:
15778 case GD_S_DIAMOND_IMPACT_4:
15779 case GD_S_DIAMOND_IMPACT_5:
15780 case GD_S_DIAMOND_IMPACT_6:
15781 case GD_S_DIAMOND_IMPACT_7:
15782 case GD_S_DIAMOND_IMPACT_8:
15783 nr = (sample == GD_S_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15784 sample - GD_S_DIAMOND_IMPACT_1);
15785 sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr;
15787 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15788 sound_effect = SND_BD_DIAMOND_IMPACT;
15791 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15792 case GD_S_FLYING_DIAMOND_FALLING_1:
15793 case GD_S_FLYING_DIAMOND_FALLING_2:
15794 case GD_S_FLYING_DIAMOND_FALLING_3:
15795 case GD_S_FLYING_DIAMOND_FALLING_4:
15796 case GD_S_FLYING_DIAMOND_FALLING_5:
15797 case GD_S_FLYING_DIAMOND_FALLING_6:
15798 case GD_S_FLYING_DIAMOND_FALLING_7:
15799 case GD_S_FLYING_DIAMOND_FALLING_8:
15800 nr = (sample == GD_S_FLYING_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15801 sample - GD_S_FLYING_DIAMOND_FALLING_1);
15802 sound_effect = SND_BD_FLYING_DIAMOND_FALLING_RANDOM_1 + nr;
15804 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15805 sound_effect = SND_BD_FLYING_DIAMOND_FALLING;
15808 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15809 case GD_S_FLYING_DIAMOND_IMPACT_1:
15810 case GD_S_FLYING_DIAMOND_IMPACT_2:
15811 case GD_S_FLYING_DIAMOND_IMPACT_3:
15812 case GD_S_FLYING_DIAMOND_IMPACT_4:
15813 case GD_S_FLYING_DIAMOND_IMPACT_5:
15814 case GD_S_FLYING_DIAMOND_IMPACT_6:
15815 case GD_S_FLYING_DIAMOND_IMPACT_7:
15816 case GD_S_FLYING_DIAMOND_IMPACT_8:
15817 nr = (sample == GD_S_FLYING_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15818 sample - GD_S_FLYING_DIAMOND_IMPACT_1);
15819 sound_effect = SND_BD_FLYING_DIAMOND_IMPACT_RANDOM_1 + nr;
15821 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15822 sound_effect = SND_BD_FLYING_DIAMOND_IMPACT;
15825 case GD_S_TIMEOUT_0:
15826 case GD_S_TIMEOUT_1:
15827 case GD_S_TIMEOUT_2:
15828 case GD_S_TIMEOUT_3:
15829 case GD_S_TIMEOUT_4:
15830 case GD_S_TIMEOUT_5:
15831 case GD_S_TIMEOUT_6:
15832 case GD_S_TIMEOUT_7:
15833 case GD_S_TIMEOUT_8:
15834 case GD_S_TIMEOUT_9:
15835 case GD_S_TIMEOUT_10:
15836 nr = sample - GD_S_TIMEOUT_0;
15837 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME_0 + nr;
15839 if (getSoundInfoEntryFilename(sound_effect) == NULL && sample != GD_S_TIMEOUT_0)
15840 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME;
15843 case GD_S_BONUS_LIFE:
15844 sound_effect = SND_GAME_HEALTH_BONUS;
15847 case GD_S_AMOEBA_MAGIC:
15848 sound_effect = SND_BD_AMOEBA_OTHER;
15851 case GD_S_FINISHED:
15852 sound_effect = SND_GAME_LEVELTIME_BONUS;
15856 sound_effect = SND_UNDEFINED;
15860 return sound_effect;
15863 void PlayLevelSound_BD(int xx, int yy, int element_bd, int sample)
15865 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15866 int sound_effect = getSoundEffect_BD(element, sample);
15867 int sound_action = getSoundAction_BD(sample);
15868 boolean is_loop_sound = IS_LOOP_SOUND(sound_effect);
15870 int x = xx - offset;
15871 int y = yy - offset;
15873 // some sound actions are always looping in native BD game engine
15874 if (sound_action == ACTION_DEFAULT)
15875 is_loop_sound = TRUE;
15877 // some sound actions are always non-looping in native BD game engine
15878 if (sound_action == ACTION_FALLING ||
15879 sound_action == ACTION_MOVING ||
15880 sound_action == ACTION_OTHER)
15881 is_loop_sound = FALSE;
15883 if (sound_effect != SND_UNDEFINED)
15884 PlayLevelSoundExt(x, y, sound_effect, is_loop_sound);
15887 void StopSound_BD(int element_bd, int sample)
15889 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15890 int sound_effect = getSoundEffect_BD(element, sample);
15892 if (sound_effect != SND_UNDEFINED)
15893 StopSound(sound_effect);
15896 boolean isSoundPlaying_BD(int element_bd, int sample)
15898 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15899 int sound_effect = getSoundEffect_BD(element, sample);
15901 if (sound_effect != SND_UNDEFINED)
15902 return isSoundPlaying(sound_effect);
15907 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15909 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15911 int x = xx - offset;
15912 int y = yy - offset;
15917 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15921 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15925 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15929 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15933 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15937 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15941 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15944 case SOUND_android_clone:
15945 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15948 case SOUND_android_move:
15949 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15953 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15957 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15961 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15964 case SOUND_eater_eat:
15965 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15969 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15972 case SOUND_collect:
15973 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15976 case SOUND_diamond:
15977 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15981 // !!! CHECK THIS !!!
15983 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15985 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15989 case SOUND_wonderfall:
15990 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15994 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15998 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
16002 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
16006 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
16010 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16014 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
16018 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16022 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16025 case SOUND_exit_open:
16026 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
16029 case SOUND_exit_leave:
16030 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16033 case SOUND_dynamite:
16034 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16038 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16042 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
16046 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16050 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
16054 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
16058 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
16062 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
16067 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
16069 int element = map_element_SP_to_RND(element_sp);
16070 int action = map_action_SP_to_RND(action_sp);
16071 int offset = (setup.sp_show_border_elements ? 0 : 1);
16072 int x = xx - offset;
16073 int y = yy - offset;
16075 PlayLevelSoundElementAction(x, y, element, action);
16078 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
16080 int element = map_element_MM_to_RND(element_mm);
16081 int action = map_action_MM_to_RND(action_mm);
16083 int x = xx - offset;
16084 int y = yy - offset;
16086 if (!IS_MM_ELEMENT(element))
16087 element = EL_MM_DEFAULT;
16089 PlayLevelSoundElementAction(x, y, element, action);
16092 void PlaySound_MM(int sound_mm)
16094 int sound = map_sound_MM_to_RND(sound_mm);
16096 if (sound == SND_UNDEFINED)
16102 void PlaySoundLoop_MM(int sound_mm)
16104 int sound = map_sound_MM_to_RND(sound_mm);
16106 if (sound == SND_UNDEFINED)
16109 PlaySoundLoop(sound);
16112 void StopSound_MM(int sound_mm)
16114 int sound = map_sound_MM_to_RND(sound_mm);
16116 if (sound == SND_UNDEFINED)
16122 void RaiseScore(int value)
16124 game.score += value;
16126 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
16128 DisplayGameControlValues();
16131 void RaiseScoreElement(int element)
16136 case EL_BD_DIAMOND:
16137 case EL_EMERALD_YELLOW:
16138 case EL_EMERALD_RED:
16139 case EL_EMERALD_PURPLE:
16140 case EL_SP_INFOTRON:
16141 RaiseScore(level.score[SC_EMERALD]);
16144 RaiseScore(level.score[SC_DIAMOND]);
16147 RaiseScore(level.score[SC_CRYSTAL]);
16150 RaiseScore(level.score[SC_PEARL]);
16153 case EL_BD_BUTTERFLY:
16154 case EL_SP_ELECTRON:
16155 RaiseScore(level.score[SC_BUG]);
16158 case EL_BD_FIREFLY:
16159 case EL_SP_SNIKSNAK:
16160 RaiseScore(level.score[SC_SPACESHIP]);
16163 case EL_DARK_YAMYAM:
16164 RaiseScore(level.score[SC_YAMYAM]);
16167 RaiseScore(level.score[SC_ROBOT]);
16170 RaiseScore(level.score[SC_PACMAN]);
16173 RaiseScore(level.score[SC_NUT]);
16176 case EL_EM_DYNAMITE:
16177 case EL_SP_DISK_RED:
16178 case EL_DYNABOMB_INCREASE_NUMBER:
16179 case EL_DYNABOMB_INCREASE_SIZE:
16180 case EL_DYNABOMB_INCREASE_POWER:
16181 RaiseScore(level.score[SC_DYNAMITE]);
16183 case EL_SHIELD_NORMAL:
16184 case EL_SHIELD_DEADLY:
16185 RaiseScore(level.score[SC_SHIELD]);
16187 case EL_EXTRA_TIME:
16188 RaiseScore(level.extra_time_score);
16202 case EL_DC_KEY_WHITE:
16203 RaiseScore(level.score[SC_KEY]);
16206 RaiseScore(element_info[element].collect_score);
16211 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
16213 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
16217 // prevent short reactivation of overlay buttons while closing door
16218 SetOverlayActive(FALSE);
16219 UnmapGameButtons();
16221 // door may still be open due to skipped or envelope style request
16222 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
16225 if (network.enabled)
16227 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
16231 // when using BD game engine, cover screen before fading out
16232 if (!quick_quit && level.game_engine_type == GAME_ENGINE_TYPE_BD)
16233 game_bd.cover_screen = TRUE;
16236 FadeSkipNextFadeIn();
16238 SetGameStatus(GAME_MODE_MAIN);
16243 else // continue playing the game
16245 if (tape.playing && tape.deactivate_display)
16246 TapeDeactivateDisplayOff(TRUE);
16248 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
16250 if (tape.playing && tape.deactivate_display)
16251 TapeDeactivateDisplayOn();
16255 void RequestQuitGame(boolean escape_key_pressed)
16257 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
16258 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
16259 level_editor_test_game);
16260 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
16261 quick_quit || score_info_tape_play);
16263 RequestQuitGameExt(skip_request, quick_quit,
16264 "Do you really want to quit the game?");
16267 static char *getRestartGameMessage(void)
16269 boolean play_again = hasStartedNetworkGame();
16270 static char message[MAX_OUTPUT_LINESIZE];
16271 char *game_over_text = "Game over!";
16272 char *play_again_text = " Play it again?";
16274 if (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
16275 game_mm.game_over_message != NULL)
16276 game_over_text = game_mm.game_over_message;
16278 snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text,
16279 (play_again ? play_again_text : ""));
16284 static void RequestRestartGame(void)
16286 char *message = getRestartGameMessage();
16287 boolean has_started_game = hasStartedNetworkGame();
16288 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
16289 int door_state = DOOR_CLOSE_1;
16291 boolean restart_wanted = (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game);
16293 // if no restart wanted, continue with next level for BD style intermission levels
16294 if (!restart_wanted && !level_editor_test_game && level.bd_intermission)
16296 boolean success = AdvanceToNextLevel();
16298 restart_wanted = (success && setup.auto_play_next_level);
16301 if (restart_wanted)
16303 CloseDoor(door_state);
16305 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16309 // if game was invoked from level editor, also close tape recorder door
16310 if (level_editor_test_game)
16311 door_state = DOOR_CLOSE_ALL;
16313 CloseDoor(door_state);
16315 SetGameStatus(GAME_MODE_MAIN);
16321 boolean CheckRestartGame(void)
16323 static int game_over_delay = 0;
16324 int game_over_delay_value = 50;
16325 boolean game_over = checkGameFailed();
16329 game_over_delay = game_over_delay_value;
16334 if (game_over_delay > 0)
16336 if (game_over_delay == game_over_delay_value / 2)
16337 PlaySound(SND_GAME_LOSING);
16344 // do not ask to play again if request dialog is already active
16345 if (game.request_active)
16348 // do not ask to play again if request dialog already handled
16349 if (game.RestartGameRequested)
16352 // do not ask to play again if game was never actually played
16353 if (!game.GamePlayed)
16356 // do not ask to play again if this was disabled in setup menu
16357 if (!setup.ask_on_game_over)
16360 game.RestartGameRequested = TRUE;
16362 RequestRestartGame();
16367 boolean checkGameRunning(void)
16369 if (game_status != GAME_MODE_PLAYING)
16372 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGameRunning_BD())
16378 boolean checkGamePlaying(void)
16380 if (game_status != GAME_MODE_PLAYING)
16383 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGamePlaying_BD())
16389 boolean checkGameSolved(void)
16391 // set for all game engines if level was solved
16392 return game.LevelSolved_GameEnd;
16395 boolean checkGameFailed(void)
16397 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
16398 return (game_bd.game_over && !game_bd.level_solved);
16399 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16400 return (game_em.game_over && !game_em.level_solved);
16401 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16402 return (game_sp.game_over && !game_sp.level_solved);
16403 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16404 return (game_mm.game_over && !game_mm.level_solved);
16405 else // GAME_ENGINE_TYPE_RND
16406 return (game.GameOver && !game.LevelSolved);
16409 boolean checkGameEnded(void)
16411 return (checkGameSolved() || checkGameFailed());
16415 // ----------------------------------------------------------------------------
16416 // random generator functions
16417 // ----------------------------------------------------------------------------
16419 unsigned int InitEngineRandom_RND(int seed)
16421 game.num_random_calls = 0;
16423 return InitEngineRandom(seed);
16426 unsigned int RND(int max)
16430 game.num_random_calls++;
16432 return GetEngineRandom(max);
16439 // ----------------------------------------------------------------------------
16440 // game engine snapshot handling functions
16441 // ----------------------------------------------------------------------------
16443 struct EngineSnapshotInfo
16445 // runtime values for custom element collect score
16446 int collect_score[NUM_CUSTOM_ELEMENTS];
16448 // runtime values for group element choice position
16449 int choice_pos[NUM_GROUP_ELEMENTS];
16451 // runtime values for belt position animations
16452 int belt_graphic[4][NUM_BELT_PARTS];
16453 int belt_anim_mode[4][NUM_BELT_PARTS];
16456 static struct EngineSnapshotInfo engine_snapshot_rnd;
16457 static char *snapshot_level_identifier = NULL;
16458 static int snapshot_level_nr = -1;
16460 static void SaveEngineSnapshotValues_RND(void)
16462 static int belt_base_active_element[4] =
16464 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
16465 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
16466 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
16467 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
16471 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16473 int element = EL_CUSTOM_START + i;
16475 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
16478 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16480 int element = EL_GROUP_START + i;
16482 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
16485 for (i = 0; i < 4; i++)
16487 for (j = 0; j < NUM_BELT_PARTS; j++)
16489 int element = belt_base_active_element[i] + j;
16490 int graphic = el2img(element);
16491 int anim_mode = graphic_info[graphic].anim_mode;
16493 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
16494 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
16499 static void LoadEngineSnapshotValues_RND(void)
16501 unsigned int num_random_calls = game.num_random_calls;
16504 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16506 int element = EL_CUSTOM_START + i;
16508 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
16511 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16513 int element = EL_GROUP_START + i;
16515 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
16518 for (i = 0; i < 4; i++)
16520 for (j = 0; j < NUM_BELT_PARTS; j++)
16522 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
16523 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
16525 graphic_info[graphic].anim_mode = anim_mode;
16529 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16531 InitRND(tape.random_seed);
16532 for (i = 0; i < num_random_calls; i++)
16536 if (game.num_random_calls != num_random_calls)
16538 Error("number of random calls out of sync");
16539 Error("number of random calls should be %d", num_random_calls);
16540 Error("number of random calls is %d", game.num_random_calls);
16542 Fail("this should not happen -- please debug");
16546 void FreeEngineSnapshotSingle(void)
16548 FreeSnapshotSingle();
16550 setString(&snapshot_level_identifier, NULL);
16551 snapshot_level_nr = -1;
16554 void FreeEngineSnapshotList(void)
16556 FreeSnapshotList();
16559 static ListNode *SaveEngineSnapshotBuffers(void)
16561 ListNode *buffers = NULL;
16563 // copy some special values to a structure better suited for the snapshot
16565 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16566 SaveEngineSnapshotValues_RND();
16567 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16568 SaveEngineSnapshotValues_EM();
16569 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16570 SaveEngineSnapshotValues_SP(&buffers);
16571 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16572 SaveEngineSnapshotValues_MM();
16574 // save values stored in special snapshot structure
16576 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16577 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
16578 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16579 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
16580 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16581 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
16582 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16583 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
16585 // save further RND engine values
16587 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
16588 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
16589 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
16591 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
16592 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
16593 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
16594 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16595 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTimeFrames));
16596 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16598 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16599 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16600 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16602 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16604 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16605 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16607 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16608 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16609 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16610 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16611 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16612 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16613 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16614 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16615 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16616 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16617 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16618 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16619 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16620 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16621 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16622 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16623 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16624 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16626 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16627 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16629 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16630 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16631 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16633 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16634 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16636 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16637 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16638 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16639 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16640 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16641 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16643 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16644 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16647 ListNode *node = engine_snapshot_list_rnd;
16650 while (node != NULL)
16652 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16657 Debug("game:playing:SaveEngineSnapshotBuffers",
16658 "size of engine snapshot: %d bytes", num_bytes);
16664 void SaveEngineSnapshotSingle(void)
16666 ListNode *buffers = SaveEngineSnapshotBuffers();
16668 // finally save all snapshot buffers to single snapshot
16669 SaveSnapshotSingle(buffers);
16671 // save level identification information
16672 setString(&snapshot_level_identifier, leveldir_current->identifier);
16673 snapshot_level_nr = level_nr;
16676 boolean CheckSaveEngineSnapshotToList(void)
16678 boolean save_snapshot =
16679 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16680 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16681 game.snapshot.changed_action) ||
16682 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16683 game.snapshot.collected_item));
16685 game.snapshot.changed_action = FALSE;
16686 game.snapshot.collected_item = FALSE;
16687 game.snapshot.save_snapshot = save_snapshot;
16689 return save_snapshot;
16692 void SaveEngineSnapshotToList(void)
16694 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16698 ListNode *buffers = SaveEngineSnapshotBuffers();
16700 // finally save all snapshot buffers to snapshot list
16701 SaveSnapshotToList(buffers);
16704 void SaveEngineSnapshotToListInitial(void)
16706 FreeEngineSnapshotList();
16708 SaveEngineSnapshotToList();
16711 static void LoadEngineSnapshotValues(void)
16713 // restore special values from snapshot structure
16715 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16716 LoadEngineSnapshotValues_RND();
16717 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16718 LoadEngineSnapshotValues_EM();
16719 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16720 LoadEngineSnapshotValues_SP();
16721 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16722 LoadEngineSnapshotValues_MM();
16725 void LoadEngineSnapshotSingle(void)
16727 LoadSnapshotSingle();
16729 LoadEngineSnapshotValues();
16732 static void LoadEngineSnapshot_Undo(int steps)
16734 LoadSnapshotFromList_Older(steps);
16736 LoadEngineSnapshotValues();
16739 static void LoadEngineSnapshot_Redo(int steps)
16741 LoadSnapshotFromList_Newer(steps);
16743 LoadEngineSnapshotValues();
16746 boolean CheckEngineSnapshotSingle(void)
16748 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16749 snapshot_level_nr == level_nr);
16752 boolean CheckEngineSnapshotList(void)
16754 return CheckSnapshotList();
16758 // ---------- new game button stuff -------------------------------------------
16765 boolean *setup_value;
16766 boolean allowed_on_tape;
16767 boolean is_touch_button;
16769 } gamebutton_info[NUM_GAME_BUTTONS] =
16772 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16773 GAME_CTRL_ID_STOP, NULL,
16774 TRUE, FALSE, "stop game"
16777 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16778 GAME_CTRL_ID_PAUSE, NULL,
16779 TRUE, FALSE, "pause game"
16782 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16783 GAME_CTRL_ID_PLAY, NULL,
16784 TRUE, FALSE, "play game"
16787 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16788 GAME_CTRL_ID_UNDO, NULL,
16789 TRUE, FALSE, "undo step"
16792 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16793 GAME_CTRL_ID_REDO, NULL,
16794 TRUE, FALSE, "redo step"
16797 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16798 GAME_CTRL_ID_SAVE, NULL,
16799 TRUE, FALSE, "save game"
16802 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16803 GAME_CTRL_ID_PAUSE2, NULL,
16804 TRUE, FALSE, "pause game"
16807 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16808 GAME_CTRL_ID_LOAD, NULL,
16809 TRUE, FALSE, "load game"
16812 IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart,
16813 GAME_CTRL_ID_RESTART, NULL,
16814 TRUE, FALSE, "restart game"
16817 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16818 GAME_CTRL_ID_PANEL_STOP, NULL,
16819 FALSE, FALSE, "stop game"
16822 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16823 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16824 FALSE, FALSE, "pause game"
16827 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16828 GAME_CTRL_ID_PANEL_PLAY, NULL,
16829 FALSE, FALSE, "play game"
16832 IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart,
16833 GAME_CTRL_ID_PANEL_RESTART, NULL,
16834 FALSE, FALSE, "restart game"
16837 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16838 GAME_CTRL_ID_TOUCH_STOP, NULL,
16839 FALSE, TRUE, "stop game"
16842 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16843 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16844 FALSE, TRUE, "pause game"
16847 IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart,
16848 GAME_CTRL_ID_TOUCH_RESTART, NULL,
16849 FALSE, TRUE, "restart game"
16852 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16853 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16854 TRUE, FALSE, "background music on/off"
16857 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16858 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16859 TRUE, FALSE, "sound loops on/off"
16862 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16863 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16864 TRUE, FALSE, "normal sounds on/off"
16867 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16868 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16869 FALSE, FALSE, "background music on/off"
16872 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16873 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16874 FALSE, FALSE, "sound loops on/off"
16877 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16878 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16879 FALSE, FALSE, "normal sounds on/off"
16883 void CreateGameButtons(void)
16887 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16889 int graphic = gamebutton_info[i].graphic;
16890 struct GraphicInfo *gfx = &graphic_info[graphic];
16891 struct XY *pos = gamebutton_info[i].pos;
16892 struct GadgetInfo *gi;
16895 unsigned int event_mask;
16896 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16897 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16898 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16899 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16900 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16901 int gd_x = gfx->src_x;
16902 int gd_y = gfx->src_y;
16903 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16904 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16905 int gd_xa = gfx->src_x + gfx->active_xoffset;
16906 int gd_ya = gfx->src_y + gfx->active_yoffset;
16907 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16908 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16909 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16910 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16913 // do not use touch buttons if overlay touch buttons are disabled
16914 if (is_touch_button && !setup.touch.overlay_buttons)
16917 if (gfx->bitmap == NULL)
16919 game_gadget[id] = NULL;
16924 if (id == GAME_CTRL_ID_STOP ||
16925 id == GAME_CTRL_ID_PANEL_STOP ||
16926 id == GAME_CTRL_ID_TOUCH_STOP ||
16927 id == GAME_CTRL_ID_PLAY ||
16928 id == GAME_CTRL_ID_PANEL_PLAY ||
16929 id == GAME_CTRL_ID_SAVE ||
16930 id == GAME_CTRL_ID_LOAD ||
16931 id == GAME_CTRL_ID_RESTART ||
16932 id == GAME_CTRL_ID_PANEL_RESTART ||
16933 id == GAME_CTRL_ID_TOUCH_RESTART)
16935 button_type = GD_TYPE_NORMAL_BUTTON;
16937 event_mask = GD_EVENT_RELEASED;
16939 else if (id == GAME_CTRL_ID_UNDO ||
16940 id == GAME_CTRL_ID_REDO)
16942 button_type = GD_TYPE_NORMAL_BUTTON;
16944 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16948 button_type = GD_TYPE_CHECK_BUTTON;
16949 checked = (gamebutton_info[i].setup_value != NULL ?
16950 *gamebutton_info[i].setup_value : FALSE);
16951 event_mask = GD_EVENT_PRESSED;
16954 gi = CreateGadget(GDI_CUSTOM_ID, id,
16955 GDI_IMAGE_ID, graphic,
16956 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16959 GDI_WIDTH, gfx->width,
16960 GDI_HEIGHT, gfx->height,
16961 GDI_TYPE, button_type,
16962 GDI_STATE, GD_BUTTON_UNPRESSED,
16963 GDI_CHECKED, checked,
16964 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16965 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16966 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16967 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16968 GDI_DIRECT_DRAW, FALSE,
16969 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16970 GDI_EVENT_MASK, event_mask,
16971 GDI_CALLBACK_ACTION, HandleGameButtons,
16975 Fail("cannot create gadget");
16977 game_gadget[id] = gi;
16981 void FreeGameButtons(void)
16985 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16986 FreeGadget(game_gadget[i]);
16989 static void UnmapGameButtonsAtSamePosition(int id)
16993 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16995 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16996 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16997 UnmapGadget(game_gadget[i]);
17000 static void UnmapGameButtonsAtSamePosition_All(void)
17002 if (setup.show_load_save_buttons)
17004 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
17005 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
17006 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
17008 else if (setup.show_undo_redo_buttons)
17010 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
17011 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
17012 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
17016 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
17017 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
17018 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
17020 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
17021 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
17022 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
17026 void MapLoadSaveButtons(void)
17028 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
17029 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
17031 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
17032 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
17035 void MapUndoRedoButtons(void)
17037 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
17038 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
17040 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
17041 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
17044 void ModifyPauseButtons(void)
17048 GAME_CTRL_ID_PAUSE,
17049 GAME_CTRL_ID_PAUSE2,
17050 GAME_CTRL_ID_PANEL_PAUSE,
17051 GAME_CTRL_ID_TOUCH_PAUSE,
17056 // do not redraw pause button on closed door (may happen when restarting game)
17057 if (!(GetDoorState() & DOOR_OPEN_1))
17060 for (i = 0; ids[i] > -1; i++)
17061 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
17064 static void MapGameButtonsExt(boolean on_tape)
17068 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17070 if ((i == GAME_CTRL_ID_UNDO ||
17071 i == GAME_CTRL_ID_REDO) &&
17072 game_status != GAME_MODE_PLAYING)
17075 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17076 MapGadget(game_gadget[i]);
17079 UnmapGameButtonsAtSamePosition_All();
17081 RedrawGameButtons();
17084 static void UnmapGameButtonsExt(boolean on_tape)
17088 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17089 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17090 UnmapGadget(game_gadget[i]);
17093 static void RedrawGameButtonsExt(boolean on_tape)
17097 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17098 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17099 RedrawGadget(game_gadget[i]);
17102 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
17107 gi->checked = state;
17110 static void RedrawSoundButtonGadget(int id)
17112 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
17113 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
17114 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
17115 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
17116 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
17117 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
17120 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
17121 RedrawGadget(game_gadget[id2]);
17124 void MapGameButtons(void)
17126 MapGameButtonsExt(FALSE);
17129 void UnmapGameButtons(void)
17131 UnmapGameButtonsExt(FALSE);
17134 void RedrawGameButtons(void)
17136 RedrawGameButtonsExt(FALSE);
17139 void MapGameButtonsOnTape(void)
17141 MapGameButtonsExt(TRUE);
17144 void UnmapGameButtonsOnTape(void)
17146 UnmapGameButtonsExt(TRUE);
17149 void RedrawGameButtonsOnTape(void)
17151 RedrawGameButtonsExt(TRUE);
17154 static void GameUndoRedoExt(void)
17156 ClearPlayerAction();
17158 tape.pausing = TRUE;
17161 UpdateAndDisplayGameControlValues();
17163 DrawCompleteVideoDisplay();
17164 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
17165 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
17166 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
17168 ModifyPauseButtons();
17173 static void GameUndo(int steps)
17175 if (!CheckEngineSnapshotList())
17178 int tape_property_bits = tape.property_bits;
17180 LoadEngineSnapshot_Undo(steps);
17182 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17187 static void GameRedo(int steps)
17189 if (!CheckEngineSnapshotList())
17192 int tape_property_bits = tape.property_bits;
17194 LoadEngineSnapshot_Redo(steps);
17196 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17201 static void HandleGameButtonsExt(int id, int button)
17203 static boolean game_undo_executed = FALSE;
17204 int steps = BUTTON_STEPSIZE(button);
17205 boolean handle_game_buttons =
17206 (game_status == GAME_MODE_PLAYING ||
17207 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
17209 if (!handle_game_buttons)
17214 case GAME_CTRL_ID_STOP:
17215 case GAME_CTRL_ID_PANEL_STOP:
17216 case GAME_CTRL_ID_TOUCH_STOP:
17221 case GAME_CTRL_ID_PAUSE:
17222 case GAME_CTRL_ID_PAUSE2:
17223 case GAME_CTRL_ID_PANEL_PAUSE:
17224 case GAME_CTRL_ID_TOUCH_PAUSE:
17225 if (network.enabled && game_status == GAME_MODE_PLAYING)
17228 SendToServer_ContinuePlaying();
17230 SendToServer_PausePlaying();
17233 TapeTogglePause(TAPE_TOGGLE_MANUAL);
17235 game_undo_executed = FALSE;
17239 case GAME_CTRL_ID_PLAY:
17240 case GAME_CTRL_ID_PANEL_PLAY:
17241 if (game_status == GAME_MODE_MAIN)
17243 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
17245 else if (tape.pausing)
17247 if (network.enabled)
17248 SendToServer_ContinuePlaying();
17250 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
17254 case GAME_CTRL_ID_UNDO:
17255 // Important: When using "save snapshot when collecting an item" mode,
17256 // load last (current) snapshot for first "undo" after pressing "pause"
17257 // (else the last-but-one snapshot would be loaded, because the snapshot
17258 // pointer already points to the last snapshot when pressing "pause",
17259 // which is fine for "every step/move" mode, but not for "every collect")
17260 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
17261 !game_undo_executed)
17264 game_undo_executed = TRUE;
17269 case GAME_CTRL_ID_REDO:
17273 case GAME_CTRL_ID_SAVE:
17277 case GAME_CTRL_ID_LOAD:
17281 case GAME_CTRL_ID_RESTART:
17282 case GAME_CTRL_ID_PANEL_RESTART:
17283 case GAME_CTRL_ID_TOUCH_RESTART:
17288 case SOUND_CTRL_ID_MUSIC:
17289 case SOUND_CTRL_ID_PANEL_MUSIC:
17290 if (setup.sound_music)
17292 setup.sound_music = FALSE;
17296 else if (audio.music_available)
17298 setup.sound = setup.sound_music = TRUE;
17300 SetAudioMode(setup.sound);
17302 if (game_status == GAME_MODE_PLAYING)
17306 RedrawSoundButtonGadget(id);
17310 case SOUND_CTRL_ID_LOOPS:
17311 case SOUND_CTRL_ID_PANEL_LOOPS:
17312 if (setup.sound_loops)
17313 setup.sound_loops = FALSE;
17314 else if (audio.loops_available)
17316 setup.sound = setup.sound_loops = TRUE;
17318 SetAudioMode(setup.sound);
17321 RedrawSoundButtonGadget(id);
17325 case SOUND_CTRL_ID_SIMPLE:
17326 case SOUND_CTRL_ID_PANEL_SIMPLE:
17327 if (setup.sound_simple)
17328 setup.sound_simple = FALSE;
17329 else if (audio.sound_available)
17331 setup.sound = setup.sound_simple = TRUE;
17333 SetAudioMode(setup.sound);
17336 RedrawSoundButtonGadget(id);
17345 static void HandleGameButtons(struct GadgetInfo *gi)
17347 HandleGameButtonsExt(gi->custom_id, gi->event.button);
17350 void HandleSoundButtonKeys(Key key)
17352 if (key == setup.shortcut.sound_simple)
17353 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
17354 else if (key == setup.shortcut.sound_loops)
17355 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
17356 else if (key == setup.shortcut.sound_music)
17357 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);