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_BDX_EMPTY ? EL_EMPTY :
1844 element == EL_BDX_PLAYER ? EL_PLAYER_1 :
1845 element == EL_BDX_INBOX ? EL_PLAYER_1 :
1846 element == EL_BDX_SAND_1 ? EL_SAND :
1847 element == EL_BDX_WALL ? EL_BD_WALL :
1848 element == EL_BDX_STEELWALL ? EL_STEELWALL :
1849 element == EL_BDX_ROCK ? EL_BD_ROCK :
1850 element == EL_BDX_DIAMOND ? EL_BD_DIAMOND :
1851 element == EL_BDX_AMOEBA_1 ? EL_BD_AMOEBA :
1852 element == EL_BDX_MAGIC_WALL ? EL_BD_MAGIC_WALL :
1853 element == EL_BDX_BUTTERFLY_1_RIGHT ? EL_BD_BUTTERFLY_RIGHT :
1854 element == EL_BDX_BUTTERFLY_1_UP ? EL_BD_BUTTERFLY_UP :
1855 element == EL_BDX_BUTTERFLY_1_LEFT ? EL_BD_BUTTERFLY_LEFT :
1856 element == EL_BDX_BUTTERFLY_1_DOWN ? EL_BD_BUTTERFLY_DOWN :
1857 element == EL_BDX_BUTTERFLY_1 ? EL_BD_BUTTERFLY :
1858 element == EL_BDX_FIREFLY_1_RIGHT ? EL_BD_FIREFLY_RIGHT :
1859 element == EL_BDX_FIREFLY_1_UP ? EL_BD_FIREFLY_UP :
1860 element == EL_BDX_FIREFLY_1_LEFT ? EL_BD_FIREFLY_LEFT :
1861 element == EL_BDX_FIREFLY_1_DOWN ? EL_BD_FIREFLY_DOWN :
1862 element == EL_BDX_FIREFLY_1 ? EL_BD_FIREFLY :
1863 element == EL_BDX_EXPANDABLE_WALL_HORIZONTAL ? EL_BD_EXPANDABLE_WALL :
1864 element == EL_BDX_WALL_DIAMOND ? EL_WALL_BD_DIAMOND :
1865 element == EL_BDX_EXIT_CLOSED ? EL_EXIT_CLOSED :
1866 element == EL_BDX_EXIT_OPEN ? EL_EXIT_OPEN :
1869 Tile[x][y] = element;
1872 static void InitFieldForEngine(int x, int y)
1874 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
1875 InitFieldForEngine_RND(x, y);
1878 static void InitField(int x, int y, boolean init_game)
1880 int element = Tile[x][y];
1889 InitPlayerField(x, y, element, init_game);
1892 case EL_SOKOBAN_FIELD_PLAYER:
1893 element = Tile[x][y] = EL_PLAYER_1;
1894 InitField(x, y, init_game);
1896 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1897 InitField(x, y, init_game);
1900 case EL_SOKOBAN_FIELD_EMPTY:
1901 IncrementSokobanFieldsNeeded();
1904 case EL_SOKOBAN_OBJECT:
1905 IncrementSokobanObjectsNeeded();
1909 if (x < lev_fieldx - 1 && Tile[x + 1][y] == EL_ACID)
1910 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1911 else if (x > 0 && Tile[x - 1][y] == EL_ACID)
1912 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1913 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPLEFT)
1914 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1915 else if (y > 0 && Tile[x][y - 1] == EL_ACID)
1916 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1917 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPRIGHT)
1918 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1927 case EL_SPACESHIP_RIGHT:
1928 case EL_SPACESHIP_UP:
1929 case EL_SPACESHIP_LEFT:
1930 case EL_SPACESHIP_DOWN:
1931 case EL_BD_BUTTERFLY:
1932 case EL_BD_BUTTERFLY_RIGHT:
1933 case EL_BD_BUTTERFLY_UP:
1934 case EL_BD_BUTTERFLY_LEFT:
1935 case EL_BD_BUTTERFLY_DOWN:
1937 case EL_BD_FIREFLY_RIGHT:
1938 case EL_BD_FIREFLY_UP:
1939 case EL_BD_FIREFLY_LEFT:
1940 case EL_BD_FIREFLY_DOWN:
1941 case EL_PACMAN_RIGHT:
1943 case EL_PACMAN_LEFT:
1944 case EL_PACMAN_DOWN:
1946 case EL_YAMYAM_LEFT:
1947 case EL_YAMYAM_RIGHT:
1949 case EL_YAMYAM_DOWN:
1950 case EL_DARK_YAMYAM:
1953 case EL_SP_SNIKSNAK:
1954 case EL_SP_ELECTRON:
1960 case EL_SPRING_LEFT:
1961 case EL_SPRING_RIGHT:
1965 case EL_AMOEBA_FULL:
1970 case EL_AMOEBA_DROP:
1971 if (y == lev_fieldy - 1)
1973 Tile[x][y] = EL_AMOEBA_GROWING;
1974 Store[x][y] = EL_AMOEBA_WET;
1978 case EL_DYNAMITE_ACTIVE:
1979 case EL_SP_DISK_RED_ACTIVE:
1980 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1981 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1982 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1983 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1984 MovDelay[x][y] = 96;
1987 case EL_EM_DYNAMITE_ACTIVE:
1988 MovDelay[x][y] = 32;
1992 game.lights_still_needed++;
1996 game.friends_still_needed++;
2001 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
2004 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
2005 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
2006 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
2007 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
2008 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
2009 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
2010 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
2011 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
2012 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
2013 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
2014 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
2015 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
2018 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
2019 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
2020 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
2022 if (game.belt_dir_nr[belt_nr] == 3) // initial value
2024 game.belt_dir[belt_nr] = belt_dir;
2025 game.belt_dir_nr[belt_nr] = belt_dir_nr;
2027 else // more than one switch -- set it like the first switch
2029 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
2034 case EL_LIGHT_SWITCH_ACTIVE:
2036 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
2039 case EL_INVISIBLE_STEELWALL:
2040 case EL_INVISIBLE_WALL:
2041 case EL_INVISIBLE_SAND:
2042 if (game.light_time_left > 0 ||
2043 game.lenses_time_left > 0)
2044 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
2047 case EL_EMC_MAGIC_BALL:
2048 if (game.ball_active)
2049 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
2052 case EL_EMC_MAGIC_BALL_SWITCH:
2053 if (game.ball_active)
2054 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
2057 case EL_TRIGGER_PLAYER:
2058 case EL_TRIGGER_ELEMENT:
2059 case EL_TRIGGER_CE_VALUE:
2060 case EL_TRIGGER_CE_SCORE:
2062 case EL_ANY_ELEMENT:
2063 case EL_CURRENT_CE_VALUE:
2064 case EL_CURRENT_CE_SCORE:
2081 // reference elements should not be used on the playfield
2082 Tile[x][y] = EL_EMPTY;
2086 if (IS_CUSTOM_ELEMENT(element))
2088 if (CAN_MOVE(element))
2091 if (!element_info[element].use_last_ce_value || init_game)
2092 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2094 else if (IS_GROUP_ELEMENT(element))
2096 Tile[x][y] = GetElementFromGroupElement(element);
2098 InitField(x, y, init_game);
2100 else if (IS_EMPTY_ELEMENT(element))
2102 GfxElementEmpty[x][y] = element;
2103 Tile[x][y] = EL_EMPTY;
2105 if (element_info[element].use_gfx_element)
2106 game.use_masked_elements = TRUE;
2113 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2116 static void InitField_WithBug1(int x, int y, boolean init_game)
2118 InitField(x, y, init_game);
2120 // not needed to call InitMovDir() -- already done by InitField()!
2121 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2122 CAN_MOVE(Tile[x][y]))
2126 static void InitField_WithBug2(int x, int y, boolean init_game)
2128 int old_element = Tile[x][y];
2130 InitField(x, y, init_game);
2132 // not needed to call InitMovDir() -- already done by InitField()!
2133 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2134 CAN_MOVE(old_element) &&
2135 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2138 /* this case is in fact a combination of not less than three bugs:
2139 first, it calls InitMovDir() for elements that can move, although this is
2140 already done by InitField(); then, it checks the element that was at this
2141 field _before_ the call to InitField() (which can change it); lastly, it
2142 was not called for "mole with direction" elements, which were treated as
2143 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2147 static int get_key_element_from_nr(int key_nr)
2149 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2150 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2151 EL_EM_KEY_1 : EL_KEY_1);
2153 return key_base_element + key_nr;
2156 static int get_next_dropped_element(struct PlayerInfo *player)
2158 return (player->inventory_size > 0 ?
2159 player->inventory_element[player->inventory_size - 1] :
2160 player->inventory_infinite_element != EL_UNDEFINED ?
2161 player->inventory_infinite_element :
2162 player->dynabombs_left > 0 ?
2163 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2167 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2169 // pos >= 0: get element from bottom of the stack;
2170 // pos < 0: get element from top of the stack
2174 int min_inventory_size = -pos;
2175 int inventory_pos = player->inventory_size - min_inventory_size;
2176 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2178 return (player->inventory_size >= min_inventory_size ?
2179 player->inventory_element[inventory_pos] :
2180 player->inventory_infinite_element != EL_UNDEFINED ?
2181 player->inventory_infinite_element :
2182 player->dynabombs_left >= min_dynabombs_left ?
2183 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2188 int min_dynabombs_left = pos + 1;
2189 int min_inventory_size = pos + 1 - player->dynabombs_left;
2190 int inventory_pos = pos - player->dynabombs_left;
2192 return (player->inventory_infinite_element != EL_UNDEFINED ?
2193 player->inventory_infinite_element :
2194 player->dynabombs_left >= min_dynabombs_left ?
2195 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2196 player->inventory_size >= min_inventory_size ?
2197 player->inventory_element[inventory_pos] :
2202 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2204 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2205 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2208 if (gpo1->sort_priority != gpo2->sort_priority)
2209 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2211 compare_result = gpo1->nr - gpo2->nr;
2213 return compare_result;
2216 int getPlayerInventorySize(int player_nr)
2218 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2219 return game_em.ply[player_nr]->dynamite;
2220 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2221 return game_sp.red_disk_count;
2223 return stored_player[player_nr].inventory_size;
2226 static void InitGameControlValues(void)
2230 for (i = 0; game_panel_controls[i].nr != -1; i++)
2232 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2233 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2234 struct TextPosInfo *pos = gpc->pos;
2236 int type = gpc->type;
2240 Error("'game_panel_controls' structure corrupted at %d", i);
2242 Fail("this should not happen -- please debug");
2245 // force update of game controls after initialization
2246 gpc->value = gpc->last_value = -1;
2247 gpc->frame = gpc->last_frame = -1;
2248 gpc->gfx_frame = -1;
2250 // determine panel value width for later calculation of alignment
2251 if (type == TYPE_INTEGER || type == TYPE_STRING)
2253 pos->width = pos->size * getFontWidth(pos->font);
2254 pos->height = getFontHeight(pos->font);
2256 else if (type == TYPE_ELEMENT)
2258 pos->width = pos->size;
2259 pos->height = pos->size;
2262 // fill structure for game panel draw order
2264 gpo->sort_priority = pos->sort_priority;
2267 // sort game panel controls according to sort_priority and control number
2268 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2269 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2272 static void UpdatePlayfieldElementCount(void)
2274 boolean use_element_count = FALSE;
2277 // first check if it is needed at all to calculate playfield element count
2278 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2279 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2280 use_element_count = TRUE;
2282 if (!use_element_count)
2285 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2286 element_info[i].element_count = 0;
2288 SCAN_PLAYFIELD(x, y)
2290 element_info[Tile[x][y]].element_count++;
2293 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2294 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2295 if (IS_IN_GROUP(j, i))
2296 element_info[EL_GROUP_START + i].element_count +=
2297 element_info[j].element_count;
2300 static void UpdateGameControlValues(void)
2303 int time = (game.LevelSolved ?
2304 game.LevelSolved_CountingTime :
2305 level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2307 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2309 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2310 game_sp.time_played :
2311 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2312 game_mm.energy_left :
2313 game.no_level_time_limit ? TimePlayed : TimeLeft);
2314 int score = (game.LevelSolved ?
2315 game.LevelSolved_CountingScore :
2316 level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2318 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2319 game_em.lev->score :
2320 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2322 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2325 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2326 game_bd.gems_still_needed :
2327 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2328 game_em.lev->gems_needed :
2329 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2330 game_sp.infotrons_still_needed :
2331 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2332 game_mm.kettles_still_needed :
2333 game.gems_still_needed);
2334 int gems_needed = level.gems_needed;
2335 int gems_collected = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2336 game_bd.game->cave->diamonds_collected :
2337 gems_needed - gems);
2338 int gems_score = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2339 game_bd.game->cave->diamond_value :
2340 level.score[SC_EMERALD]);
2341 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2342 game_bd.gems_still_needed > 0 :
2343 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2344 game_em.lev->gems_needed > 0 :
2345 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2346 game_sp.infotrons_still_needed > 0 :
2347 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2348 game_mm.kettles_still_needed > 0 ||
2349 game_mm.lights_still_needed > 0 :
2350 game.gems_still_needed > 0 ||
2351 game.sokoban_fields_still_needed > 0 ||
2352 game.sokoban_objects_still_needed > 0 ||
2353 game.lights_still_needed > 0);
2354 int health = (game.LevelSolved ?
2355 game.LevelSolved_CountingHealth :
2356 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2357 MM_HEALTH(game_mm.laser_overload_value) :
2359 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2361 UpdatePlayfieldElementCount();
2363 // update game panel control values
2365 // used instead of "level_nr" (for network games)
2366 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2367 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2368 game_panel_controls[GAME_PANEL_GEMS_NEEDED].value = gems_needed;
2369 game_panel_controls[GAME_PANEL_GEMS_COLLECTED].value = gems_collected;
2370 game_panel_controls[GAME_PANEL_GEMS_SCORE].value = gems_score;
2372 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2373 for (i = 0; i < MAX_NUM_KEYS; i++)
2374 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2375 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2376 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2378 if (game.centered_player_nr == -1)
2380 for (i = 0; i < MAX_PLAYERS; i++)
2382 // only one player in Supaplex game engine
2383 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2386 for (k = 0; k < MAX_NUM_KEYS; k++)
2388 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2390 if (game_em.ply[i]->keys & (1 << k))
2391 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2392 get_key_element_from_nr(k);
2394 else if (stored_player[i].key[k])
2395 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2396 get_key_element_from_nr(k);
2399 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2400 getPlayerInventorySize(i);
2402 if (stored_player[i].num_white_keys > 0)
2403 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2406 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2407 stored_player[i].num_white_keys;
2412 int player_nr = game.centered_player_nr;
2414 for (k = 0; k < MAX_NUM_KEYS; k++)
2416 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2418 if (game_em.ply[player_nr]->keys & (1 << k))
2419 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2420 get_key_element_from_nr(k);
2422 else if (stored_player[player_nr].key[k])
2423 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2424 get_key_element_from_nr(k);
2427 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2428 getPlayerInventorySize(player_nr);
2430 if (stored_player[player_nr].num_white_keys > 0)
2431 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2433 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2434 stored_player[player_nr].num_white_keys;
2437 // re-arrange keys on game panel, if needed or if defined by style settings
2438 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2440 int nr = GAME_PANEL_KEY_1 + i;
2441 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2442 struct TextPosInfo *pos = gpc->pos;
2444 // skip check if key is not in the player's inventory
2445 if (gpc->value == EL_EMPTY)
2448 // check if keys should be arranged on panel from left to right
2449 if (pos->style == STYLE_LEFTMOST_POSITION)
2451 // check previous key positions (left from current key)
2452 for (k = 0; k < i; k++)
2454 int nr_new = GAME_PANEL_KEY_1 + k;
2456 if (game_panel_controls[nr_new].value == EL_EMPTY)
2458 game_panel_controls[nr_new].value = gpc->value;
2459 gpc->value = EL_EMPTY;
2466 // check if "undefined" keys can be placed at some other position
2467 if (pos->x == -1 && pos->y == -1)
2469 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2471 // 1st try: display key at the same position as normal or EM keys
2472 if (game_panel_controls[nr_new].value == EL_EMPTY)
2474 game_panel_controls[nr_new].value = gpc->value;
2478 // 2nd try: display key at the next free position in the key panel
2479 for (k = 0; k < STD_NUM_KEYS; k++)
2481 nr_new = GAME_PANEL_KEY_1 + k;
2483 if (game_panel_controls[nr_new].value == EL_EMPTY)
2485 game_panel_controls[nr_new].value = gpc->value;
2494 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2496 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2497 get_inventory_element_from_pos(local_player, i);
2498 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2499 get_inventory_element_from_pos(local_player, -i - 1);
2502 game_panel_controls[GAME_PANEL_SCORE].value = score;
2503 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2505 game_panel_controls[GAME_PANEL_TIME].value = time;
2507 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2508 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2509 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2511 if (level.time == 0)
2512 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2514 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2516 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2517 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2519 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2521 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2522 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2524 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2525 local_player->shield_normal_time_left;
2526 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2527 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2529 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2530 local_player->shield_deadly_time_left;
2532 game_panel_controls[GAME_PANEL_EXIT].value =
2533 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2535 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2536 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2537 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2538 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2539 EL_EMC_MAGIC_BALL_SWITCH);
2541 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2542 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2543 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2544 game.light_time_left;
2546 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2547 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2548 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2549 game.timegate_time_left;
2551 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2552 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2554 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2555 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2556 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2557 game.lenses_time_left;
2559 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2560 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2561 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2562 game.magnify_time_left;
2564 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2565 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2566 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2567 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2568 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2569 EL_BALLOON_SWITCH_NONE);
2571 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2572 local_player->dynabomb_count;
2573 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2574 local_player->dynabomb_size;
2575 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2576 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2578 game_panel_controls[GAME_PANEL_PENGUINS].value =
2579 game.friends_still_needed;
2581 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2582 game.sokoban_objects_still_needed;
2583 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2584 game.sokoban_fields_still_needed;
2586 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2587 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2589 for (i = 0; i < NUM_BELTS; i++)
2591 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2592 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2593 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2594 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2595 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2598 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2599 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2600 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2601 game.magic_wall_time_left;
2603 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2604 local_player->gravity;
2606 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2607 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2609 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2610 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2611 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2612 game.panel.element[i].id : EL_UNDEFINED);
2614 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2615 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2616 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2617 element_info[game.panel.element_count[i].id].element_count : 0);
2619 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2620 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2621 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2622 element_info[game.panel.ce_score[i].id].collect_score : 0);
2624 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2625 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2626 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2627 element_info[game.panel.ce_score_element[i].id].collect_score :
2630 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2631 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2632 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2634 // update game panel control frames
2636 for (i = 0; game_panel_controls[i].nr != -1; i++)
2638 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2640 if (gpc->type == TYPE_ELEMENT)
2642 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2644 int last_anim_random_frame = gfx.anim_random_frame;
2645 int element = gpc->value;
2646 int graphic = el2panelimg(element);
2647 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2649 graphic_info[graphic].anim_global_anim_sync ?
2650 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2652 if (gpc->value != gpc->last_value)
2655 gpc->gfx_random = init_gfx_random;
2661 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2662 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2663 gpc->gfx_random = init_gfx_random;
2666 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2667 gfx.anim_random_frame = gpc->gfx_random;
2669 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2670 gpc->gfx_frame = element_info[element].collect_score;
2672 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2674 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2675 gfx.anim_random_frame = last_anim_random_frame;
2678 else if (gpc->type == TYPE_GRAPHIC)
2680 if (gpc->graphic != IMG_UNDEFINED)
2682 int last_anim_random_frame = gfx.anim_random_frame;
2683 int graphic = gpc->graphic;
2684 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2686 graphic_info[graphic].anim_global_anim_sync ?
2687 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2689 if (gpc->value != gpc->last_value)
2692 gpc->gfx_random = init_gfx_random;
2698 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2699 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2700 gpc->gfx_random = init_gfx_random;
2703 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2704 gfx.anim_random_frame = gpc->gfx_random;
2706 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2708 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2709 gfx.anim_random_frame = last_anim_random_frame;
2715 static void DisplayGameControlValues(void)
2717 boolean redraw_panel = FALSE;
2720 for (i = 0; game_panel_controls[i].nr != -1; i++)
2722 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2724 if (PANEL_DEACTIVATED(gpc->pos))
2727 if (gpc->value == gpc->last_value &&
2728 gpc->frame == gpc->last_frame)
2731 redraw_panel = TRUE;
2737 // copy default game door content to main double buffer
2739 // !!! CHECK AGAIN !!!
2740 SetPanelBackground();
2741 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2742 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2744 // redraw game control buttons
2745 RedrawGameButtons();
2747 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2749 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2751 int nr = game_panel_order[i].nr;
2752 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2753 struct TextPosInfo *pos = gpc->pos;
2754 int type = gpc->type;
2755 int value = gpc->value;
2756 int frame = gpc->frame;
2757 int size = pos->size;
2758 int font = pos->font;
2759 boolean draw_masked = pos->draw_masked;
2760 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2762 if (PANEL_DEACTIVATED(pos))
2765 if (pos->class == get_hash_from_string("extra_panel_items") &&
2766 !setup.prefer_extra_panel_items)
2769 gpc->last_value = value;
2770 gpc->last_frame = frame;
2772 if (type == TYPE_INTEGER)
2774 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2775 nr == GAME_PANEL_INVENTORY_COUNT ||
2776 nr == GAME_PANEL_SCORE ||
2777 nr == GAME_PANEL_HIGHSCORE ||
2778 nr == GAME_PANEL_TIME)
2780 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2782 if (use_dynamic_size) // use dynamic number of digits
2784 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 :
2785 nr == GAME_PANEL_INVENTORY_COUNT ||
2786 nr == GAME_PANEL_TIME ? 1000 : 100000);
2787 int size_add = (nr == GAME_PANEL_LEVEL_NUMBER ||
2788 nr == GAME_PANEL_INVENTORY_COUNT ||
2789 nr == GAME_PANEL_TIME ? 1 : 2);
2790 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 :
2791 nr == GAME_PANEL_INVENTORY_COUNT ||
2792 nr == GAME_PANEL_TIME ? 3 : 5);
2793 int size2 = size1 + size_add;
2794 int font1 = pos->font;
2795 int font2 = pos->font_alt;
2797 size = (value < value_change ? size1 : size2);
2798 font = (value < value_change ? font1 : font2);
2802 // correct text size if "digits" is zero or less
2804 size = strlen(int2str(value, size));
2806 // dynamically correct text alignment
2807 pos->width = size * getFontWidth(font);
2809 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2810 int2str(value, size), font, mask_mode);
2812 else if (type == TYPE_ELEMENT)
2814 int element, graphic;
2818 int dst_x = PANEL_XPOS(pos);
2819 int dst_y = PANEL_YPOS(pos);
2821 if (value != EL_UNDEFINED && value != EL_EMPTY)
2824 graphic = el2panelimg(value);
2827 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2828 element, EL_NAME(element), size);
2831 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2834 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2837 width = graphic_info[graphic].width * size / TILESIZE;
2838 height = graphic_info[graphic].height * size / TILESIZE;
2841 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2844 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2848 else if (type == TYPE_GRAPHIC)
2850 int graphic = gpc->graphic;
2851 int graphic_active = gpc->graphic_active;
2855 int dst_x = PANEL_XPOS(pos);
2856 int dst_y = PANEL_YPOS(pos);
2857 boolean skip = (pos->class == get_hash_from_string("mm_engine_only") &&
2858 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2860 if (graphic != IMG_UNDEFINED && !skip)
2862 if (pos->style == STYLE_REVERSE)
2863 value = 100 - value;
2865 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2867 if (pos->direction & MV_HORIZONTAL)
2869 width = graphic_info[graphic_active].width * value / 100;
2870 height = graphic_info[graphic_active].height;
2872 if (pos->direction == MV_LEFT)
2874 src_x += graphic_info[graphic_active].width - width;
2875 dst_x += graphic_info[graphic_active].width - width;
2880 width = graphic_info[graphic_active].width;
2881 height = graphic_info[graphic_active].height * value / 100;
2883 if (pos->direction == MV_UP)
2885 src_y += graphic_info[graphic_active].height - height;
2886 dst_y += graphic_info[graphic_active].height - height;
2891 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2894 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2897 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2899 if (pos->direction & MV_HORIZONTAL)
2901 if (pos->direction == MV_RIGHT)
2908 dst_x = PANEL_XPOS(pos);
2911 width = graphic_info[graphic].width - width;
2915 if (pos->direction == MV_DOWN)
2922 dst_y = PANEL_YPOS(pos);
2925 height = graphic_info[graphic].height - height;
2929 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2932 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2936 else if (type == TYPE_STRING)
2938 boolean active = (value != 0);
2939 char *state_normal = "off";
2940 char *state_active = "on";
2941 char *state = (active ? state_active : state_normal);
2942 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2943 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2944 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2945 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2947 if (nr == GAME_PANEL_GRAVITY_STATE)
2949 int font1 = pos->font; // (used for normal state)
2950 int font2 = pos->font_alt; // (used for active state)
2952 font = (active ? font2 : font1);
2961 // don't truncate output if "chars" is zero or less
2964 // dynamically correct text alignment
2965 pos->width = size * getFontWidth(font);
2968 s_cut = getStringCopyN(s, size);
2970 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2971 s_cut, font, mask_mode);
2977 redraw_mask |= REDRAW_DOOR_1;
2980 SetGameStatus(GAME_MODE_PLAYING);
2983 void UpdateAndDisplayGameControlValues(void)
2985 if (tape.deactivate_display)
2988 UpdateGameControlValues();
2989 DisplayGameControlValues();
2992 void UpdateGameDoorValues(void)
2994 UpdateGameControlValues();
2997 void DrawGameDoorValues(void)
2999 DisplayGameControlValues();
3003 // ============================================================================
3005 // ----------------------------------------------------------------------------
3006 // initialize game engine due to level / tape version number
3007 // ============================================================================
3009 static void InitGameEngine(void)
3011 int i, j, k, l, x, y;
3013 // set game engine from tape file when re-playing, else from level file
3014 game.engine_version = (tape.playing ? tape.engine_version :
3015 level.game_version);
3017 // set single or multi-player game mode (needed for re-playing tapes)
3018 game.team_mode = setup.team_mode;
3022 int num_players = 0;
3024 for (i = 0; i < MAX_PLAYERS; i++)
3025 if (tape.player_participates[i])
3028 // multi-player tapes contain input data for more than one player
3029 game.team_mode = (num_players > 1);
3033 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
3034 level.game_version);
3035 Debug("game:init:level", " tape.file_version == %06d",
3037 Debug("game:init:level", " tape.game_version == %06d",
3039 Debug("game:init:level", " tape.engine_version == %06d",
3040 tape.engine_version);
3041 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
3042 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
3045 // --------------------------------------------------------------------------
3046 // set flags for bugs and changes according to active game engine version
3047 // --------------------------------------------------------------------------
3051 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
3053 Bug was introduced in version:
3056 Bug was fixed in version:
3060 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
3061 but the property "can fall" was missing, which caused some levels to be
3062 unsolvable. This was fixed in version 4.2.0.0.
3064 Affected levels/tapes:
3065 An example for a tape that was fixed by this bugfix is tape 029 from the
3066 level set "rnd_sam_bateman".
3067 The wrong behaviour will still be used for all levels or tapes that were
3068 created/recorded with it. An example for this is tape 023 from the level
3069 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
3072 boolean use_amoeba_dropping_cannot_fall_bug =
3073 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
3074 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
3076 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3077 tape.game_version < VERSION_IDENT(4,2,0,0)));
3080 Summary of bugfix/change:
3081 Fixed move speed of elements entering or leaving magic wall.
3083 Fixed/changed in version:
3087 Before 2.0.1, move speed of elements entering or leaving magic wall was
3088 twice as fast as it is now.
3089 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
3091 Affected levels/tapes:
3092 The first condition is generally needed for all levels/tapes before version
3093 2.0.1, which might use the old behaviour before it was changed; known tapes
3094 that are affected: Tape 014 from the level set "rnd_conor_mancone".
3095 The second condition is an exception from the above case and is needed for
3096 the special case of tapes recorded with game (not engine!) version 2.0.1 or
3097 above, but before it was known that this change would break tapes like the
3098 above and was fixed in 4.2.0.0, so that the changed behaviour was active
3099 although the engine version while recording maybe was before 2.0.1. There
3100 are a lot of tapes that are affected by this exception, like tape 006 from
3101 the level set "rnd_conor_mancone".
3104 boolean use_old_move_stepsize_for_magic_wall =
3105 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
3107 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3108 tape.game_version < VERSION_IDENT(4,2,0,0)));
3111 Summary of bugfix/change:
3112 Fixed handling for custom elements that change when pushed by the player.
3114 Fixed/changed in version:
3118 Before 3.1.0, custom elements that "change when pushing" changed directly
3119 after the player started pushing them (until then handled in "DigField()").
3120 Since 3.1.0, these custom elements are not changed until the "pushing"
3121 move of the element is finished (now handled in "ContinueMoving()").
3123 Affected levels/tapes:
3124 The first condition is generally needed for all levels/tapes before version
3125 3.1.0, which might use the old behaviour before it was changed; known tapes
3126 that are affected are some tapes from the level set "Walpurgis Gardens" by
3128 The second condition is an exception from the above case and is needed for
3129 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3130 above (including some development versions of 3.1.0), but before it was
3131 known that this change would break tapes like the above and was fixed in
3132 3.1.1, so that the changed behaviour was active although the engine version
3133 while recording maybe was before 3.1.0. There is at least one tape that is
3134 affected by this exception, which is the tape for the one-level set "Bug
3135 Machine" by Juergen Bonhagen.
3138 game.use_change_when_pushing_bug =
3139 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3141 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3142 tape.game_version < VERSION_IDENT(3,1,1,0)));
3145 Summary of bugfix/change:
3146 Fixed handling for blocking the field the player leaves when moving.
3148 Fixed/changed in version:
3152 Before 3.1.1, when "block last field when moving" was enabled, the field
3153 the player is leaving when moving was blocked for the time of the move,
3154 and was directly unblocked afterwards. This resulted in the last field
3155 being blocked for exactly one less than the number of frames of one player
3156 move. Additionally, even when blocking was disabled, the last field was
3157 blocked for exactly one frame.
3158 Since 3.1.1, due to changes in player movement handling, the last field
3159 is not blocked at all when blocking is disabled. When blocking is enabled,
3160 the last field is blocked for exactly the number of frames of one player
3161 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3162 last field is blocked for exactly one more than the number of frames of
3165 Affected levels/tapes:
3166 (!!! yet to be determined -- probably many !!!)
3169 game.use_block_last_field_bug =
3170 (game.engine_version < VERSION_IDENT(3,1,1,0));
3172 /* various special flags and settings for native Emerald Mine game engine */
3174 game_em.use_single_button =
3175 (game.engine_version > VERSION_IDENT(4,0,0,2));
3177 game_em.use_push_delay =
3178 (game.engine_version > VERSION_IDENT(4,3,7,1));
3180 game_em.use_snap_key_bug =
3181 (game.engine_version < VERSION_IDENT(4,0,1,0));
3183 game_em.use_random_bug =
3184 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3186 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3188 game_em.use_old_explosions = use_old_em_engine;
3189 game_em.use_old_android = use_old_em_engine;
3190 game_em.use_old_push_elements = use_old_em_engine;
3191 game_em.use_old_push_into_acid = use_old_em_engine;
3193 game_em.use_wrap_around = !use_old_em_engine;
3195 // --------------------------------------------------------------------------
3197 // set maximal allowed number of custom element changes per game frame
3198 game.max_num_changes_per_frame = 1;
3200 // default scan direction: scan playfield from top/left to bottom/right
3201 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3203 // dynamically adjust element properties according to game engine version
3204 InitElementPropertiesEngine(game.engine_version);
3206 // ---------- initialize special element properties -------------------------
3208 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3209 if (use_amoeba_dropping_cannot_fall_bug)
3210 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3212 // ---------- initialize player's initial move delay ------------------------
3214 // dynamically adjust player properties according to level information
3215 for (i = 0; i < MAX_PLAYERS; i++)
3216 game.initial_move_delay_value[i] =
3217 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3219 // dynamically adjust player properties according to game engine version
3220 for (i = 0; i < MAX_PLAYERS; i++)
3221 game.initial_move_delay[i] =
3222 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3223 game.initial_move_delay_value[i] : 0);
3225 // ---------- initialize player's initial push delay ------------------------
3227 // dynamically adjust player properties according to game engine version
3228 game.initial_push_delay_value =
3229 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3231 // ---------- initialize changing elements ----------------------------------
3233 // initialize changing elements information
3234 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3236 struct ElementInfo *ei = &element_info[i];
3238 // this pointer might have been changed in the level editor
3239 ei->change = &ei->change_page[0];
3241 if (!IS_CUSTOM_ELEMENT(i))
3243 ei->change->target_element = EL_EMPTY_SPACE;
3244 ei->change->delay_fixed = 0;
3245 ei->change->delay_random = 0;
3246 ei->change->delay_frames = 1;
3249 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3251 ei->has_change_event[j] = FALSE;
3253 ei->event_page_nr[j] = 0;
3254 ei->event_page[j] = &ei->change_page[0];
3258 // add changing elements from pre-defined list
3259 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3261 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3262 struct ElementInfo *ei = &element_info[ch_delay->element];
3264 ei->change->target_element = ch_delay->target_element;
3265 ei->change->delay_fixed = ch_delay->change_delay;
3267 ei->change->pre_change_function = ch_delay->pre_change_function;
3268 ei->change->change_function = ch_delay->change_function;
3269 ei->change->post_change_function = ch_delay->post_change_function;
3271 ei->change->can_change = TRUE;
3272 ei->change->can_change_or_has_action = TRUE;
3274 ei->has_change_event[CE_DELAY] = TRUE;
3276 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3277 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3280 // ---------- initialize if element can trigger global animations -----------
3282 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3284 struct ElementInfo *ei = &element_info[i];
3286 ei->has_anim_event = FALSE;
3289 InitGlobalAnimEventsForCustomElements();
3291 // ---------- initialize internal run-time variables ------------------------
3293 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3295 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3297 for (j = 0; j < ei->num_change_pages; j++)
3299 ei->change_page[j].can_change_or_has_action =
3300 (ei->change_page[j].can_change |
3301 ei->change_page[j].has_action);
3305 // add change events from custom element configuration
3306 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3308 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3310 for (j = 0; j < ei->num_change_pages; j++)
3312 if (!ei->change_page[j].can_change_or_has_action)
3315 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3317 // only add event page for the first page found with this event
3318 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3320 ei->has_change_event[k] = TRUE;
3322 ei->event_page_nr[k] = j;
3323 ei->event_page[k] = &ei->change_page[j];
3329 // ---------- initialize reference elements in change conditions ------------
3331 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3333 int element = EL_CUSTOM_START + i;
3334 struct ElementInfo *ei = &element_info[element];
3336 for (j = 0; j < ei->num_change_pages; j++)
3338 int trigger_element = ei->change_page[j].initial_trigger_element;
3340 if (trigger_element >= EL_PREV_CE_8 &&
3341 trigger_element <= EL_NEXT_CE_8)
3342 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3344 ei->change_page[j].trigger_element = trigger_element;
3348 // ---------- initialize run-time trigger player and element ----------------
3350 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3352 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3354 for (j = 0; j < ei->num_change_pages; j++)
3356 struct ElementChangeInfo *change = &ei->change_page[j];
3358 change->actual_trigger_element = EL_EMPTY;
3359 change->actual_trigger_player = EL_EMPTY;
3360 change->actual_trigger_player_bits = CH_PLAYER_NONE;
3361 change->actual_trigger_side = CH_SIDE_NONE;
3362 change->actual_trigger_ce_value = 0;
3363 change->actual_trigger_ce_score = 0;
3364 change->actual_trigger_x = -1;
3365 change->actual_trigger_y = -1;
3369 // ---------- initialize trigger events -------------------------------------
3371 // initialize trigger events information
3372 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3373 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3374 trigger_events[i][j] = FALSE;
3376 // add trigger events from element change event properties
3377 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3379 struct ElementInfo *ei = &element_info[i];
3381 for (j = 0; j < ei->num_change_pages; j++)
3383 struct ElementChangeInfo *change = &ei->change_page[j];
3385 if (!change->can_change_or_has_action)
3388 if (change->has_event[CE_BY_OTHER_ACTION])
3390 int trigger_element = change->trigger_element;
3392 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3394 if (change->has_event[k])
3396 if (IS_GROUP_ELEMENT(trigger_element))
3398 struct ElementGroupInfo *group =
3399 element_info[trigger_element].group;
3401 for (l = 0; l < group->num_elements_resolved; l++)
3402 trigger_events[group->element_resolved[l]][k] = TRUE;
3404 else if (trigger_element == EL_ANY_ELEMENT)
3405 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3406 trigger_events[l][k] = TRUE;
3408 trigger_events[trigger_element][k] = TRUE;
3415 // ---------- initialize push delay -----------------------------------------
3417 // initialize push delay values to default
3418 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3420 if (!IS_CUSTOM_ELEMENT(i))
3422 // set default push delay values (corrected since version 3.0.7-1)
3423 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3425 element_info[i].push_delay_fixed = 2;
3426 element_info[i].push_delay_random = 8;
3430 element_info[i].push_delay_fixed = 8;
3431 element_info[i].push_delay_random = 8;
3436 // set push delay value for certain elements from pre-defined list
3437 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3439 int e = push_delay_list[i].element;
3441 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3442 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3445 // set push delay value for Supaplex elements for newer engine versions
3446 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3448 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3450 if (IS_SP_ELEMENT(i))
3452 // set SP push delay to just enough to push under a falling zonk
3453 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3455 element_info[i].push_delay_fixed = delay;
3456 element_info[i].push_delay_random = 0;
3461 // ---------- initialize move stepsize --------------------------------------
3463 // initialize move stepsize values to default
3464 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3465 if (!IS_CUSTOM_ELEMENT(i))
3466 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3468 // set move stepsize value for certain elements from pre-defined list
3469 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3471 int e = move_stepsize_list[i].element;
3473 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3475 // set move stepsize value for certain elements for older engine versions
3476 if (use_old_move_stepsize_for_magic_wall)
3478 if (e == EL_MAGIC_WALL_FILLING ||
3479 e == EL_MAGIC_WALL_EMPTYING ||
3480 e == EL_BD_MAGIC_WALL_FILLING ||
3481 e == EL_BD_MAGIC_WALL_EMPTYING)
3482 element_info[e].move_stepsize *= 2;
3486 // ---------- initialize collect score --------------------------------------
3488 // initialize collect score values for custom elements from initial value
3489 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3490 if (IS_CUSTOM_ELEMENT(i))
3491 element_info[i].collect_score = element_info[i].collect_score_initial;
3493 // ---------- initialize collect count --------------------------------------
3495 // initialize collect count values for non-custom elements
3496 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3497 if (!IS_CUSTOM_ELEMENT(i))
3498 element_info[i].collect_count_initial = 0;
3500 // add collect count values for all elements from pre-defined list
3501 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3502 element_info[collect_count_list[i].element].collect_count_initial =
3503 collect_count_list[i].count;
3505 // ---------- initialize access direction -----------------------------------
3507 // initialize access direction values to default (access from every side)
3508 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3509 if (!IS_CUSTOM_ELEMENT(i))
3510 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3512 // set access direction value for certain elements from pre-defined list
3513 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3514 element_info[access_direction_list[i].element].access_direction =
3515 access_direction_list[i].direction;
3517 // ---------- initialize explosion content ----------------------------------
3518 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3520 if (IS_CUSTOM_ELEMENT(i))
3523 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3525 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3527 element_info[i].content.e[x][y] =
3528 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3529 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3530 i == EL_PLAYER_3 ? EL_EMERALD :
3531 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3532 i == EL_MOLE ? EL_EMERALD_RED :
3533 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3534 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3535 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3536 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3537 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3538 i == EL_WALL_EMERALD ? EL_EMERALD :
3539 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3540 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3541 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3542 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3543 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3544 i == EL_WALL_PEARL ? EL_PEARL :
3545 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3550 // ---------- initialize recursion detection --------------------------------
3551 recursion_loop_depth = 0;
3552 recursion_loop_detected = FALSE;
3553 recursion_loop_element = EL_UNDEFINED;
3555 // ---------- initialize graphics engine ------------------------------------
3556 game.scroll_delay_value =
3557 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3558 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3559 !setup.forced_scroll_delay ? 0 :
3560 setup.scroll_delay ? setup.scroll_delay_value : 0);
3561 if (game.forced_scroll_delay_value == -1)
3562 game.scroll_delay_value =
3563 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3565 // ---------- initialize game engine snapshots ------------------------------
3566 for (i = 0; i < MAX_PLAYERS; i++)
3567 game.snapshot.last_action[i] = 0;
3568 game.snapshot.changed_action = FALSE;
3569 game.snapshot.collected_item = FALSE;
3570 game.snapshot.mode =
3571 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3572 SNAPSHOT_MODE_EVERY_STEP :
3573 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3574 SNAPSHOT_MODE_EVERY_MOVE :
3575 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3576 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3577 game.snapshot.save_snapshot = FALSE;
3579 // ---------- initialize level time for Supaplex engine ---------------------
3580 // Supaplex levels with time limit currently unsupported -- should be added
3581 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3584 // ---------- initialize flags for handling game actions --------------------
3586 // set flags for game actions to default values
3587 game.use_key_actions = TRUE;
3588 game.use_mouse_actions = FALSE;
3590 // when using Mirror Magic game engine, handle mouse events only
3591 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3593 game.use_key_actions = FALSE;
3594 game.use_mouse_actions = TRUE;
3597 // check for custom elements with mouse click events
3598 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3600 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3602 int element = EL_CUSTOM_START + i;
3604 if (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3605 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3606 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3607 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3608 game.use_mouse_actions = TRUE;
3613 static int get_num_special_action(int element, int action_first,
3616 int num_special_action = 0;
3619 for (i = action_first; i <= action_last; i++)
3621 boolean found = FALSE;
3623 for (j = 0; j < NUM_DIRECTIONS; j++)
3624 if (el_act_dir2img(element, i, j) !=
3625 el_act_dir2img(element, ACTION_DEFAULT, j))
3629 num_special_action++;
3634 return num_special_action;
3638 // ============================================================================
3640 // ----------------------------------------------------------------------------
3641 // initialize and start new game
3642 // ============================================================================
3644 #if DEBUG_INIT_PLAYER
3645 static void DebugPrintPlayerStatus(char *message)
3652 Debug("game:init:player", "%s:", message);
3654 for (i = 0; i < MAX_PLAYERS; i++)
3656 struct PlayerInfo *player = &stored_player[i];
3658 Debug("game:init:player",
3659 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3663 player->connected_locally,
3664 player->connected_network,
3666 (local_player == player ? " (local player)" : ""));
3673 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3674 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3675 int fade_mask = REDRAW_FIELD;
3676 boolean restarting = (game_status == GAME_MODE_PLAYING);
3677 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3678 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3679 int initial_move_dir = MV_DOWN;
3682 // required here to update video display before fading (FIX THIS)
3683 DrawMaskedBorder(REDRAW_DOOR_2);
3685 if (!game.restart_level)
3686 CloseDoor(DOOR_CLOSE_1);
3690 // force fading out global animations displayed during game play
3691 SetGameStatus(GAME_MODE_PSEUDO_RESTARTING);
3695 SetGameStatus(GAME_MODE_PLAYING);
3697 // do not cover screen before fading out when starting from main menu
3698 game_bd.cover_screen = FALSE;
3701 if (level_editor_test_game)
3702 FadeSkipNextFadeOut();
3704 FadeSetEnterScreen();
3707 fade_mask = REDRAW_ALL;
3709 FadeLevelSoundsAndMusic();
3711 ExpireSoundLoops(TRUE);
3717 // force restarting global animations displayed during game play
3718 RestartGlobalAnimsByStatus(GAME_MODE_PSEUDO_RESTARTING);
3720 // this is required for "transforming" fade modes like cross-fading
3721 // (else global animations will be stopped, but not restarted here)
3722 SetAnimStatusBeforeFading(GAME_MODE_PSEUDO_RESTARTING);
3724 SetGameStatus(GAME_MODE_PLAYING);
3727 if (level_editor_test_game)
3728 FadeSkipNextFadeIn();
3730 // needed if different viewport properties defined for playing
3731 ChangeViewportPropertiesIfNeeded();
3735 DrawCompleteVideoDisplay();
3737 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3740 InitGameControlValues();
3744 // initialize tape actions from game when recording tape
3745 tape.use_key_actions = game.use_key_actions;
3746 tape.use_mouse_actions = game.use_mouse_actions;
3748 // initialize visible playfield size when recording tape (for team mode)
3749 tape.scr_fieldx = SCR_FIELDX;
3750 tape.scr_fieldy = SCR_FIELDY;
3753 // don't play tapes over network
3754 network_playing = (network.enabled && !tape.playing);
3756 for (i = 0; i < MAX_PLAYERS; i++)
3758 struct PlayerInfo *player = &stored_player[i];
3760 player->index_nr = i;
3761 player->index_bit = (1 << i);
3762 player->element_nr = EL_PLAYER_1 + i;
3764 player->present = FALSE;
3765 player->active = FALSE;
3766 player->mapped = FALSE;
3768 player->killed = FALSE;
3769 player->reanimated = FALSE;
3770 player->buried = FALSE;
3773 player->effective_action = 0;
3774 player->programmed_action = 0;
3775 player->snap_action = 0;
3777 player->mouse_action.lx = 0;
3778 player->mouse_action.ly = 0;
3779 player->mouse_action.button = 0;
3780 player->mouse_action.button_hint = 0;
3782 player->effective_mouse_action.lx = 0;
3783 player->effective_mouse_action.ly = 0;
3784 player->effective_mouse_action.button = 0;
3785 player->effective_mouse_action.button_hint = 0;
3787 for (j = 0; j < MAX_NUM_KEYS; j++)
3788 player->key[j] = FALSE;
3790 player->num_white_keys = 0;
3792 player->dynabomb_count = 0;
3793 player->dynabomb_size = 1;
3794 player->dynabombs_left = 0;
3795 player->dynabomb_xl = FALSE;
3797 player->MovDir = initial_move_dir;
3800 player->GfxDir = initial_move_dir;
3801 player->GfxAction = ACTION_DEFAULT;
3803 player->StepFrame = 0;
3805 player->initial_element = player->element_nr;
3806 player->artwork_element =
3807 (level.use_artwork_element[i] ? level.artwork_element[i] :
3808 player->element_nr);
3809 player->use_murphy = FALSE;
3811 player->block_last_field = FALSE; // initialized in InitPlayerField()
3812 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3814 player->gravity = level.initial_player_gravity[i];
3816 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3818 player->actual_frame_counter.count = 0;
3819 player->actual_frame_counter.value = 1;
3821 player->step_counter = 0;
3823 player->last_move_dir = initial_move_dir;
3825 player->is_active = FALSE;
3827 player->is_waiting = FALSE;
3828 player->is_moving = FALSE;
3829 player->is_auto_moving = FALSE;
3830 player->is_digging = FALSE;
3831 player->is_snapping = FALSE;
3832 player->is_collecting = FALSE;
3833 player->is_pushing = FALSE;
3834 player->is_switching = FALSE;
3835 player->is_dropping = FALSE;
3836 player->is_dropping_pressed = FALSE;
3838 player->is_bored = FALSE;
3839 player->is_sleeping = FALSE;
3841 player->was_waiting = TRUE;
3842 player->was_moving = FALSE;
3843 player->was_snapping = FALSE;
3844 player->was_dropping = FALSE;
3846 player->force_dropping = FALSE;
3848 player->frame_counter_bored = -1;
3849 player->frame_counter_sleeping = -1;
3851 player->anim_delay_counter = 0;
3852 player->post_delay_counter = 0;
3854 player->dir_waiting = initial_move_dir;
3855 player->action_waiting = ACTION_DEFAULT;
3856 player->last_action_waiting = ACTION_DEFAULT;
3857 player->special_action_bored = ACTION_DEFAULT;
3858 player->special_action_sleeping = ACTION_DEFAULT;
3860 player->switch_x = -1;
3861 player->switch_y = -1;
3863 player->drop_x = -1;
3864 player->drop_y = -1;
3866 player->show_envelope = 0;
3868 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3870 player->push_delay = -1; // initialized when pushing starts
3871 player->push_delay_value = game.initial_push_delay_value;
3873 player->drop_delay = 0;
3874 player->drop_pressed_delay = 0;
3876 player->last_jx = -1;
3877 player->last_jy = -1;
3881 player->shield_normal_time_left = 0;
3882 player->shield_deadly_time_left = 0;
3884 player->last_removed_element = EL_UNDEFINED;
3886 player->inventory_infinite_element = EL_UNDEFINED;
3887 player->inventory_size = 0;
3889 if (level.use_initial_inventory[i])
3891 for (j = 0; j < level.initial_inventory_size[i]; j++)
3893 int element = level.initial_inventory_content[i][j];
3894 int collect_count = element_info[element].collect_count_initial;
3897 if (!IS_CUSTOM_ELEMENT(element))
3900 if (collect_count == 0)
3901 player->inventory_infinite_element = element;
3903 for (k = 0; k < collect_count; k++)
3904 if (player->inventory_size < MAX_INVENTORY_SIZE)
3905 player->inventory_element[player->inventory_size++] = element;
3909 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3910 SnapField(player, 0, 0);
3912 map_player_action[i] = i;
3915 network_player_action_received = FALSE;
3917 // initial null action
3918 if (network_playing)
3919 SendToServer_MovePlayer(MV_NONE);
3924 TimeLeft = level.time;
3929 ScreenMovDir = MV_NONE;
3933 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3935 game.robot_wheel_x = -1;
3936 game.robot_wheel_y = -1;
3941 game.all_players_gone = FALSE;
3943 game.LevelSolved = FALSE;
3944 game.GameOver = FALSE;
3946 game.GamePlayed = !tape.playing;
3948 game.LevelSolved_GameWon = FALSE;
3949 game.LevelSolved_GameEnd = FALSE;
3950 game.LevelSolved_SaveTape = FALSE;
3951 game.LevelSolved_SaveScore = FALSE;
3953 game.LevelSolved_CountingTime = 0;
3954 game.LevelSolved_CountingScore = 0;
3955 game.LevelSolved_CountingHealth = 0;
3957 game.RestartGameRequested = FALSE;
3959 game.panel.active = TRUE;
3961 game.no_level_time_limit = (level.time == 0);
3962 game.time_limit = (leveldir_current->time_limit && setup.time_limit);
3964 game.yamyam_content_nr = 0;
3965 game.robot_wheel_active = FALSE;
3966 game.magic_wall_active = FALSE;
3967 game.magic_wall_time_left = 0;
3968 game.light_time_left = 0;
3969 game.timegate_time_left = 0;
3970 game.switchgate_pos = 0;
3971 game.wind_direction = level.wind_direction_initial;
3973 game.time_final = 0;
3974 game.score_time_final = 0;
3977 game.score_final = 0;
3979 game.health = MAX_HEALTH;
3980 game.health_final = MAX_HEALTH;
3982 game.gems_still_needed = level.gems_needed;
3983 game.sokoban_fields_still_needed = 0;
3984 game.sokoban_objects_still_needed = 0;
3985 game.lights_still_needed = 0;
3986 game.players_still_needed = 0;
3987 game.friends_still_needed = 0;
3989 game.lenses_time_left = 0;
3990 game.magnify_time_left = 0;
3992 game.ball_active = level.ball_active_initial;
3993 game.ball_content_nr = 0;
3995 game.explosions_delayed = TRUE;
3997 // special case: set custom artwork setting to initial value
3998 game.use_masked_elements = game.use_masked_elements_initial;
4000 for (i = 0; i < NUM_BELTS; i++)
4002 game.belt_dir[i] = MV_NONE;
4003 game.belt_dir_nr[i] = 3; // not moving, next moving left
4006 for (i = 0; i < MAX_NUM_AMOEBA; i++)
4007 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
4009 #if DEBUG_INIT_PLAYER
4010 DebugPrintPlayerStatus("Player status at level initialization");
4013 SCAN_PLAYFIELD(x, y)
4015 Tile[x][y] = Last[x][y] = level.field[x][y];
4016 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
4017 ChangeDelay[x][y] = 0;
4018 ChangePage[x][y] = -1;
4019 CustomValue[x][y] = 0; // initialized in InitField()
4020 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
4022 WasJustMoving[x][y] = 0;
4023 WasJustFalling[x][y] = 0;
4024 CheckCollision[x][y] = 0;
4025 CheckImpact[x][y] = 0;
4027 Pushed[x][y] = FALSE;
4029 ChangeCount[x][y] = 0;
4030 ChangeEvent[x][y] = -1;
4032 ExplodePhase[x][y] = 0;
4033 ExplodeDelay[x][y] = 0;
4034 ExplodeField[x][y] = EX_TYPE_NONE;
4036 RunnerVisit[x][y] = 0;
4037 PlayerVisit[x][y] = 0;
4040 GfxRandom[x][y] = INIT_GFX_RANDOM();
4041 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
4042 GfxElement[x][y] = EL_UNDEFINED;
4043 GfxElementEmpty[x][y] = EL_EMPTY;
4044 GfxAction[x][y] = ACTION_DEFAULT;
4045 GfxDir[x][y] = MV_NONE;
4046 GfxRedraw[x][y] = GFX_REDRAW_NONE;
4049 SCAN_PLAYFIELD(x, y)
4051 InitFieldForEngine(x, y);
4053 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
4055 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
4058 InitField(x, y, TRUE);
4060 ResetGfxAnimation(x, y);
4065 // required if level does not contain any "empty space" element
4066 if (element_info[EL_EMPTY].use_gfx_element)
4067 game.use_masked_elements = TRUE;
4069 for (i = 0; i < MAX_PLAYERS; i++)
4071 struct PlayerInfo *player = &stored_player[i];
4073 // set number of special actions for bored and sleeping animation
4074 player->num_special_action_bored =
4075 get_num_special_action(player->artwork_element,
4076 ACTION_BORING_1, ACTION_BORING_LAST);
4077 player->num_special_action_sleeping =
4078 get_num_special_action(player->artwork_element,
4079 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
4082 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
4083 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
4085 // initialize type of slippery elements
4086 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4088 if (!IS_CUSTOM_ELEMENT(i))
4090 // default: elements slip down either to the left or right randomly
4091 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
4093 // SP style elements prefer to slip down on the left side
4094 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
4095 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4097 // BD style elements prefer to slip down on the left side
4098 if (game.emulation == EMU_BOULDERDASH)
4099 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4103 // initialize explosion and ignition delay
4104 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4106 if (!IS_CUSTOM_ELEMENT(i))
4109 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
4110 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
4111 game.emulation == EMU_SUPAPLEX ? 3 : 2);
4112 int last_phase = (num_phase + 1) * delay;
4113 int half_phase = (num_phase / 2) * delay;
4115 element_info[i].explosion_delay = last_phase - 1;
4116 element_info[i].ignition_delay = half_phase;
4118 if (i == EL_BLACK_ORB)
4119 element_info[i].ignition_delay = 1;
4123 // correct non-moving belts to start moving left
4124 for (i = 0; i < NUM_BELTS; i++)
4125 if (game.belt_dir[i] == MV_NONE)
4126 game.belt_dir_nr[i] = 3; // not moving, next moving left
4128 #if USE_NEW_PLAYER_ASSIGNMENTS
4129 // use preferred player also in local single-player mode
4130 if (!network.enabled && !game.team_mode)
4132 int new_index_nr = setup.network_player_nr;
4134 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
4136 for (i = 0; i < MAX_PLAYERS; i++)
4137 stored_player[i].connected_locally = FALSE;
4139 stored_player[new_index_nr].connected_locally = TRUE;
4143 for (i = 0; i < MAX_PLAYERS; i++)
4145 stored_player[i].connected = FALSE;
4147 // in network game mode, the local player might not be the first player
4148 if (stored_player[i].connected_locally)
4149 local_player = &stored_player[i];
4152 if (!network.enabled)
4153 local_player->connected = TRUE;
4157 for (i = 0; i < MAX_PLAYERS; i++)
4158 stored_player[i].connected = tape.player_participates[i];
4160 else if (network.enabled)
4162 // add team mode players connected over the network (needed for correct
4163 // assignment of player figures from level to locally playing players)
4165 for (i = 0; i < MAX_PLAYERS; i++)
4166 if (stored_player[i].connected_network)
4167 stored_player[i].connected = TRUE;
4169 else if (game.team_mode)
4171 // try to guess locally connected team mode players (needed for correct
4172 // assignment of player figures from level to locally playing players)
4174 for (i = 0; i < MAX_PLAYERS; i++)
4175 if (setup.input[i].use_joystick ||
4176 setup.input[i].key.left != KSYM_UNDEFINED)
4177 stored_player[i].connected = TRUE;
4180 #if DEBUG_INIT_PLAYER
4181 DebugPrintPlayerStatus("Player status after level initialization");
4184 #if DEBUG_INIT_PLAYER
4185 Debug("game:init:player", "Reassigning players ...");
4188 // check if any connected player was not found in playfield
4189 for (i = 0; i < MAX_PLAYERS; i++)
4191 struct PlayerInfo *player = &stored_player[i];
4193 if (player->connected && !player->present)
4195 struct PlayerInfo *field_player = NULL;
4197 #if DEBUG_INIT_PLAYER
4198 Debug("game:init:player",
4199 "- looking for field player for player %d ...", i + 1);
4202 // assign first free player found that is present in the playfield
4204 // first try: look for unmapped playfield player that is not connected
4205 for (j = 0; j < MAX_PLAYERS; j++)
4206 if (field_player == NULL &&
4207 stored_player[j].present &&
4208 !stored_player[j].mapped &&
4209 !stored_player[j].connected)
4210 field_player = &stored_player[j];
4212 // second try: look for *any* unmapped playfield player
4213 for (j = 0; j < MAX_PLAYERS; j++)
4214 if (field_player == NULL &&
4215 stored_player[j].present &&
4216 !stored_player[j].mapped)
4217 field_player = &stored_player[j];
4219 if (field_player != NULL)
4221 int jx = field_player->jx, jy = field_player->jy;
4223 #if DEBUG_INIT_PLAYER
4224 Debug("game:init:player", "- found player %d",
4225 field_player->index_nr + 1);
4228 player->present = FALSE;
4229 player->active = FALSE;
4231 field_player->present = TRUE;
4232 field_player->active = TRUE;
4235 player->initial_element = field_player->initial_element;
4236 player->artwork_element = field_player->artwork_element;
4238 player->block_last_field = field_player->block_last_field;
4239 player->block_delay_adjustment = field_player->block_delay_adjustment;
4242 StorePlayer[jx][jy] = field_player->element_nr;
4244 field_player->jx = field_player->last_jx = jx;
4245 field_player->jy = field_player->last_jy = jy;
4247 if (local_player == player)
4248 local_player = field_player;
4250 map_player_action[field_player->index_nr] = i;
4252 field_player->mapped = TRUE;
4254 #if DEBUG_INIT_PLAYER
4255 Debug("game:init:player", "- map_player_action[%d] == %d",
4256 field_player->index_nr + 1, i + 1);
4261 if (player->connected && player->present)
4262 player->mapped = TRUE;
4265 #if DEBUG_INIT_PLAYER
4266 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4271 // check if any connected player was not found in playfield
4272 for (i = 0; i < MAX_PLAYERS; i++)
4274 struct PlayerInfo *player = &stored_player[i];
4276 if (player->connected && !player->present)
4278 for (j = 0; j < MAX_PLAYERS; j++)
4280 struct PlayerInfo *field_player = &stored_player[j];
4281 int jx = field_player->jx, jy = field_player->jy;
4283 // assign first free player found that is present in the playfield
4284 if (field_player->present && !field_player->connected)
4286 player->present = TRUE;
4287 player->active = TRUE;
4289 field_player->present = FALSE;
4290 field_player->active = FALSE;
4292 player->initial_element = field_player->initial_element;
4293 player->artwork_element = field_player->artwork_element;
4295 player->block_last_field = field_player->block_last_field;
4296 player->block_delay_adjustment = field_player->block_delay_adjustment;
4298 StorePlayer[jx][jy] = player->element_nr;
4300 player->jx = player->last_jx = jx;
4301 player->jy = player->last_jy = jy;
4311 Debug("game:init:player", "local_player->present == %d",
4312 local_player->present);
4315 // set focus to local player for network games, else to all players
4316 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4317 game.centered_player_nr_next = game.centered_player_nr;
4318 game.set_centered_player = FALSE;
4319 game.set_centered_player_wrap = FALSE;
4321 if (network_playing && tape.recording)
4323 // store client dependent player focus when recording network games
4324 tape.centered_player_nr_next = game.centered_player_nr_next;
4325 tape.set_centered_player = TRUE;
4330 // when playing a tape, eliminate all players who do not participate
4332 #if USE_NEW_PLAYER_ASSIGNMENTS
4334 if (!game.team_mode)
4336 for (i = 0; i < MAX_PLAYERS; i++)
4338 if (stored_player[i].active &&
4339 !tape.player_participates[map_player_action[i]])
4341 struct PlayerInfo *player = &stored_player[i];
4342 int jx = player->jx, jy = player->jy;
4344 #if DEBUG_INIT_PLAYER
4345 Debug("game:init:player", "Removing player %d at (%d, %d)",
4349 player->active = FALSE;
4350 StorePlayer[jx][jy] = 0;
4351 Tile[jx][jy] = EL_EMPTY;
4358 for (i = 0; i < MAX_PLAYERS; i++)
4360 if (stored_player[i].active &&
4361 !tape.player_participates[i])
4363 struct PlayerInfo *player = &stored_player[i];
4364 int jx = player->jx, jy = player->jy;
4366 player->active = FALSE;
4367 StorePlayer[jx][jy] = 0;
4368 Tile[jx][jy] = EL_EMPTY;
4373 else if (!network.enabled && !game.team_mode) // && !tape.playing
4375 // when in single player mode, eliminate all but the local player
4377 for (i = 0; i < MAX_PLAYERS; i++)
4379 struct PlayerInfo *player = &stored_player[i];
4381 if (player->active && player != local_player)
4383 int jx = player->jx, jy = player->jy;
4385 player->active = FALSE;
4386 player->present = FALSE;
4388 StorePlayer[jx][jy] = 0;
4389 Tile[jx][jy] = EL_EMPTY;
4394 for (i = 0; i < MAX_PLAYERS; i++)
4395 if (stored_player[i].active)
4396 game.players_still_needed++;
4398 if (level.solved_by_one_player)
4399 game.players_still_needed = 1;
4401 // when recording the game, store which players take part in the game
4404 #if USE_NEW_PLAYER_ASSIGNMENTS
4405 for (i = 0; i < MAX_PLAYERS; i++)
4406 if (stored_player[i].connected)
4407 tape.player_participates[i] = TRUE;
4409 for (i = 0; i < MAX_PLAYERS; i++)
4410 if (stored_player[i].active)
4411 tape.player_participates[i] = TRUE;
4415 #if DEBUG_INIT_PLAYER
4416 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4419 if (BorderElement == EL_EMPTY)
4422 SBX_Right = lev_fieldx - SCR_FIELDX;
4424 SBY_Lower = lev_fieldy - SCR_FIELDY;
4429 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4431 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4434 if (full_lev_fieldx <= SCR_FIELDX)
4435 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4436 if (full_lev_fieldy <= SCR_FIELDY)
4437 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4439 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4441 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4444 // if local player not found, look for custom element that might create
4445 // the player (make some assumptions about the right custom element)
4446 if (!local_player->present)
4448 int start_x = 0, start_y = 0;
4449 int found_rating = 0;
4450 int found_element = EL_UNDEFINED;
4451 int player_nr = local_player->index_nr;
4453 SCAN_PLAYFIELD(x, y)
4455 int element = Tile[x][y];
4460 if (level.use_start_element[player_nr] &&
4461 level.start_element[player_nr] == element &&
4468 found_element = element;
4471 if (!IS_CUSTOM_ELEMENT(element))
4474 if (CAN_CHANGE(element))
4476 for (i = 0; i < element_info[element].num_change_pages; i++)
4478 // check for player created from custom element as single target
4479 content = element_info[element].change_page[i].target_element;
4480 is_player = IS_PLAYER_ELEMENT(content);
4482 if (is_player && (found_rating < 3 ||
4483 (found_rating == 3 && element < found_element)))
4489 found_element = element;
4494 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4496 // check for player created from custom element as explosion content
4497 content = element_info[element].content.e[xx][yy];
4498 is_player = IS_PLAYER_ELEMENT(content);
4500 if (is_player && (found_rating < 2 ||
4501 (found_rating == 2 && element < found_element)))
4503 start_x = x + xx - 1;
4504 start_y = y + yy - 1;
4507 found_element = element;
4510 if (!CAN_CHANGE(element))
4513 for (i = 0; i < element_info[element].num_change_pages; i++)
4515 // check for player created from custom element as extended target
4517 element_info[element].change_page[i].target_content.e[xx][yy];
4519 is_player = IS_PLAYER_ELEMENT(content);
4521 if (is_player && (found_rating < 1 ||
4522 (found_rating == 1 && element < found_element)))
4524 start_x = x + xx - 1;
4525 start_y = y + yy - 1;
4528 found_element = element;
4534 scroll_x = SCROLL_POSITION_X(start_x);
4535 scroll_y = SCROLL_POSITION_Y(start_y);
4539 scroll_x = SCROLL_POSITION_X(local_player->jx);
4540 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4543 if (game.forced_scroll_x != ARG_UNDEFINED_VALUE)
4544 scroll_x = game.forced_scroll_x;
4545 if (game.forced_scroll_y != ARG_UNDEFINED_VALUE)
4546 scroll_y = game.forced_scroll_y;
4548 // !!! FIX THIS (START) !!!
4549 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
4551 InitGameEngine_BD();
4553 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4555 InitGameEngine_EM();
4557 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4559 InitGameEngine_SP();
4561 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4563 InitGameEngine_MM();
4567 DrawLevel(REDRAW_FIELD);
4570 // after drawing the level, correct some elements
4571 if (game.timegate_time_left == 0)
4572 CloseAllOpenTimegates();
4575 // blit playfield from scroll buffer to normal back buffer for fading in
4576 BlitScreenToBitmap(backbuffer);
4577 // !!! FIX THIS (END) !!!
4579 DrawMaskedBorder(fade_mask);
4584 // full screen redraw is required at this point in the following cases:
4585 // - special editor door undrawn when game was started from level editor
4586 // - drawing area (playfield) was changed and has to be removed completely
4587 redraw_mask = REDRAW_ALL;
4591 if (!game.restart_level)
4593 // copy default game door content to main double buffer
4595 // !!! CHECK AGAIN !!!
4596 SetPanelBackground();
4597 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4598 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4601 SetPanelBackground();
4602 SetDrawBackgroundMask(REDRAW_DOOR_1);
4604 UpdateAndDisplayGameControlValues();
4606 if (!game.restart_level)
4612 CreateGameButtons();
4617 // copy actual game door content to door double buffer for OpenDoor()
4618 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4620 OpenDoor(DOOR_OPEN_ALL);
4622 KeyboardAutoRepeatOffUnlessAutoplay();
4624 #if DEBUG_INIT_PLAYER
4625 DebugPrintPlayerStatus("Player status (final)");
4634 if (!game.restart_level && !tape.playing)
4636 LevelStats_incPlayed(level_nr);
4638 SaveLevelSetup_SeriesInfo();
4641 game.restart_level = FALSE;
4642 game.request_active = FALSE;
4643 game.envelope_active = FALSE;
4644 game.any_door_active = FALSE;
4646 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4647 InitGameActions_MM();
4649 SaveEngineSnapshotToListInitial();
4651 if (!game.restart_level)
4653 PlaySound(SND_GAME_STARTING);
4655 if (setup.sound_music)
4659 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4662 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4663 int actual_player_x, int actual_player_y)
4665 // this is used for non-R'n'D game engines to update certain engine values
4667 // needed to determine if sounds are played within the visible screen area
4668 scroll_x = actual_scroll_x;
4669 scroll_y = actual_scroll_y;
4671 // needed to get player position for "follow finger" playing input method
4672 local_player->jx = actual_player_x;
4673 local_player->jy = actual_player_y;
4676 void InitMovDir(int x, int y)
4678 int i, element = Tile[x][y];
4679 static int xy[4][2] =
4686 static int direction[3][4] =
4688 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4689 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4690 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4699 Tile[x][y] = EL_BUG;
4700 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4703 case EL_SPACESHIP_RIGHT:
4704 case EL_SPACESHIP_UP:
4705 case EL_SPACESHIP_LEFT:
4706 case EL_SPACESHIP_DOWN:
4707 Tile[x][y] = EL_SPACESHIP;
4708 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4711 case EL_BD_BUTTERFLY_RIGHT:
4712 case EL_BD_BUTTERFLY_UP:
4713 case EL_BD_BUTTERFLY_LEFT:
4714 case EL_BD_BUTTERFLY_DOWN:
4715 Tile[x][y] = EL_BD_BUTTERFLY;
4716 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4719 case EL_BD_FIREFLY_RIGHT:
4720 case EL_BD_FIREFLY_UP:
4721 case EL_BD_FIREFLY_LEFT:
4722 case EL_BD_FIREFLY_DOWN:
4723 Tile[x][y] = EL_BD_FIREFLY;
4724 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4727 case EL_PACMAN_RIGHT:
4729 case EL_PACMAN_LEFT:
4730 case EL_PACMAN_DOWN:
4731 Tile[x][y] = EL_PACMAN;
4732 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4735 case EL_YAMYAM_LEFT:
4736 case EL_YAMYAM_RIGHT:
4738 case EL_YAMYAM_DOWN:
4739 Tile[x][y] = EL_YAMYAM;
4740 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4743 case EL_SP_SNIKSNAK:
4744 MovDir[x][y] = MV_UP;
4747 case EL_SP_ELECTRON:
4748 MovDir[x][y] = MV_LEFT;
4755 Tile[x][y] = EL_MOLE;
4756 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4759 case EL_SPRING_LEFT:
4760 case EL_SPRING_RIGHT:
4761 Tile[x][y] = EL_SPRING;
4762 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4766 if (IS_CUSTOM_ELEMENT(element))
4768 struct ElementInfo *ei = &element_info[element];
4769 int move_direction_initial = ei->move_direction_initial;
4770 int move_pattern = ei->move_pattern;
4772 if (move_direction_initial == MV_START_PREVIOUS)
4774 if (MovDir[x][y] != MV_NONE)
4777 move_direction_initial = MV_START_AUTOMATIC;
4780 if (move_direction_initial == MV_START_RANDOM)
4781 MovDir[x][y] = 1 << RND(4);
4782 else if (move_direction_initial & MV_ANY_DIRECTION)
4783 MovDir[x][y] = move_direction_initial;
4784 else if (move_pattern == MV_ALL_DIRECTIONS ||
4785 move_pattern == MV_TURNING_LEFT ||
4786 move_pattern == MV_TURNING_RIGHT ||
4787 move_pattern == MV_TURNING_LEFT_RIGHT ||
4788 move_pattern == MV_TURNING_RIGHT_LEFT ||
4789 move_pattern == MV_TURNING_RANDOM)
4790 MovDir[x][y] = 1 << RND(4);
4791 else if (move_pattern == MV_HORIZONTAL)
4792 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4793 else if (move_pattern == MV_VERTICAL)
4794 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4795 else if (move_pattern & MV_ANY_DIRECTION)
4796 MovDir[x][y] = element_info[element].move_pattern;
4797 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4798 move_pattern == MV_ALONG_RIGHT_SIDE)
4800 // use random direction as default start direction
4801 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4802 MovDir[x][y] = 1 << RND(4);
4804 for (i = 0; i < NUM_DIRECTIONS; i++)
4806 int x1 = x + xy[i][0];
4807 int y1 = y + xy[i][1];
4809 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4811 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4812 MovDir[x][y] = direction[0][i];
4814 MovDir[x][y] = direction[1][i];
4823 MovDir[x][y] = 1 << RND(4);
4825 if (element != EL_BUG &&
4826 element != EL_SPACESHIP &&
4827 element != EL_BD_BUTTERFLY &&
4828 element != EL_BD_FIREFLY)
4831 for (i = 0; i < NUM_DIRECTIONS; i++)
4833 int x1 = x + xy[i][0];
4834 int y1 = y + xy[i][1];
4836 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4838 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4840 MovDir[x][y] = direction[0][i];
4843 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4844 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4846 MovDir[x][y] = direction[1][i];
4855 GfxDir[x][y] = MovDir[x][y];
4858 void InitAmoebaNr(int x, int y)
4861 int group_nr = AmoebaNeighbourNr(x, y);
4865 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4867 if (AmoebaCnt[i] == 0)
4875 AmoebaNr[x][y] = group_nr;
4876 AmoebaCnt[group_nr]++;
4877 AmoebaCnt2[group_nr]++;
4880 static void LevelSolved_SetFinalGameValues(void)
4882 game.time_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.time_left :
4883 game.no_level_time_limit ? TimePlayed : TimeLeft);
4884 game.score_time_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.frames_played :
4885 level.use_step_counter ? TimePlayed :
4886 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4888 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.score :
4889 level.game_engine_type == GAME_ENGINE_TYPE_EM ? game_em.lev->score :
4890 level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.score :
4893 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4894 MM_HEALTH(game_mm.laser_overload_value) :
4897 game.LevelSolved_CountingTime = game.time_final;
4898 game.LevelSolved_CountingScore = game.score_final;
4899 game.LevelSolved_CountingHealth = game.health_final;
4902 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4904 game.LevelSolved_CountingTime = time;
4905 game.LevelSolved_CountingScore = score;
4906 game.LevelSolved_CountingHealth = health;
4908 game_panel_controls[GAME_PANEL_TIME].value = time;
4909 game_panel_controls[GAME_PANEL_SCORE].value = score;
4910 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4912 DisplayGameControlValues();
4915 static void LevelSolved(void)
4917 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4918 game.players_still_needed > 0)
4921 game.LevelSolved = TRUE;
4922 game.GameOver = TRUE;
4926 // needed here to display correct panel values while player walks into exit
4927 LevelSolved_SetFinalGameValues();
4930 static boolean AdvanceToNextLevel(void)
4932 if (setup.increment_levels &&
4933 level_nr < leveldir_current->last_level &&
4936 level_nr++; // advance to next level
4937 TapeErase(); // start with empty tape
4939 if (setup.auto_play_next_level)
4941 scores.continue_playing = TRUE;
4942 scores.next_level_nr = level_nr;
4944 LoadLevel(level_nr);
4946 SaveLevelSetup_SeriesInfo();
4957 static int time_count_steps;
4958 static int time, time_final;
4959 static float score, score_final; // needed for time score < 10 for 10 seconds
4960 static int health, health_final;
4961 static int game_over_delay_1 = 0;
4962 static int game_over_delay_2 = 0;
4963 static int game_over_delay_3 = 0;
4964 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4965 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4967 if (!game.LevelSolved_GameWon)
4971 // do not start end game actions before the player stops moving (to exit)
4972 if (local_player->active && local_player->MovPos)
4975 // calculate final game values after player finished walking into exit
4976 LevelSolved_SetFinalGameValues();
4978 game.LevelSolved_GameWon = TRUE;
4979 game.LevelSolved_SaveTape = tape.recording;
4980 game.LevelSolved_SaveScore = !tape.playing;
4984 LevelStats_incSolved(level_nr);
4986 SaveLevelSetup_SeriesInfo();
4989 if (tape.auto_play) // tape might already be stopped here
4990 tape.auto_play_level_solved = TRUE;
4994 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4995 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4996 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4998 time = time_final = game.time_final;
4999 score = score_final = game.score_final;
5000 health = health_final = game.health_final;
5002 // update game panel values before (delayed) counting of score (if any)
5003 LevelSolved_DisplayFinalGameValues(time, score, health);
5005 // if level has time score defined, calculate new final game values
5008 int time_final_max = 999;
5009 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
5010 int time_frames = 0;
5011 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
5012 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
5017 time_frames = time_frames_left;
5019 else if (game.no_level_time_limit && TimePlayed < time_final_max)
5021 time_final = time_final_max;
5022 time_frames = time_frames_final_max - time_frames_played;
5025 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
5027 time_count_steps = MAX(1, ABS(time_final - time) / 100);
5029 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
5031 // keep previous values (final values already processed here)
5033 score_final = score;
5035 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
5038 score_final += health * time_score;
5041 game.score_final = score_final;
5042 game.health_final = health_final;
5045 // if not counting score after game, immediately update game panel values
5046 if (level_editor_test_game || !setup.count_score_after_game ||
5047 level.game_engine_type == GAME_ENGINE_TYPE_BD)
5050 score = score_final;
5052 LevelSolved_DisplayFinalGameValues(time, score, health);
5055 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
5057 // check if last player has left the level
5058 if (game.exit_x >= 0 &&
5061 int x = game.exit_x;
5062 int y = game.exit_y;
5063 int element = Tile[x][y];
5065 // close exit door after last player
5066 if ((game.all_players_gone &&
5067 (element == EL_EXIT_OPEN ||
5068 element == EL_SP_EXIT_OPEN ||
5069 element == EL_STEEL_EXIT_OPEN)) ||
5070 element == EL_EM_EXIT_OPEN ||
5071 element == EL_EM_STEEL_EXIT_OPEN)
5075 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
5076 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
5077 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
5078 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
5079 EL_EM_STEEL_EXIT_CLOSING);
5081 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
5084 // player disappears
5085 DrawLevelField(x, y);
5088 for (i = 0; i < MAX_PLAYERS; i++)
5090 struct PlayerInfo *player = &stored_player[i];
5092 if (player->present)
5094 RemovePlayer(player);
5096 // player disappears
5097 DrawLevelField(player->jx, player->jy);
5102 PlaySound(SND_GAME_WINNING);
5105 if (setup.count_score_after_game)
5107 if (time != time_final)
5109 if (game_over_delay_1 > 0)
5111 game_over_delay_1--;
5116 int time_to_go = ABS(time_final - time);
5117 int time_count_dir = (time < time_final ? +1 : -1);
5119 if (time_to_go < time_count_steps)
5120 time_count_steps = 1;
5122 time += time_count_steps * time_count_dir;
5123 score += time_count_steps * time_score;
5125 // set final score to correct rounding differences after counting score
5126 if (time == time_final)
5127 score = score_final;
5129 LevelSolved_DisplayFinalGameValues(time, score, health);
5131 if (time == time_final)
5132 StopSound(SND_GAME_LEVELTIME_BONUS);
5133 else if (setup.sound_loops)
5134 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5136 PlaySound(SND_GAME_LEVELTIME_BONUS);
5141 if (health != health_final)
5143 if (game_over_delay_2 > 0)
5145 game_over_delay_2--;
5150 int health_count_dir = (health < health_final ? +1 : -1);
5152 health += health_count_dir;
5153 score += time_score;
5155 LevelSolved_DisplayFinalGameValues(time, score, health);
5157 if (health == health_final)
5158 StopSound(SND_GAME_LEVELTIME_BONUS);
5159 else if (setup.sound_loops)
5160 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5162 PlaySound(SND_GAME_LEVELTIME_BONUS);
5168 game.panel.active = FALSE;
5170 if (game_over_delay_3 > 0)
5172 game_over_delay_3--;
5182 // used instead of "level_nr" (needed for network games)
5183 int last_level_nr = levelset.level_nr;
5184 boolean tape_saved = FALSE;
5185 boolean game_over = checkGameFailed();
5187 // Important note: This function is not only called after "GameWon()", but also after
5188 // "game over" (if automatically asking for restarting the game is disabled in setup)
5190 // do not handle game end if game over and automatically asking for game restart
5191 if (game_over && setup.ask_on_game_over)
5193 // (this is a special case: player pressed "return" key or fire button shortly before
5194 // automatically asking to restart the game, so skip asking and restart right away)
5196 CloseDoor(DOOR_CLOSE_1);
5198 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5203 // do not handle game end if request dialog is already active
5204 if (checkRequestActive())
5207 if (game.LevelSolved)
5208 game.LevelSolved_GameEnd = TRUE;
5210 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5212 // make sure that request dialog to save tape does not open door again
5213 if (!global.use_envelope_request)
5214 CloseDoor(DOOR_CLOSE_1);
5217 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5219 // set unique basename for score tape (also saved in high score table)
5220 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5223 // if no tape is to be saved, close both doors simultaneously
5224 CloseDoor(DOOR_CLOSE_ALL);
5226 if (level_editor_test_game || score_info_tape_play)
5228 SetGameStatus(GAME_MODE_MAIN);
5235 if (!game.GamePlayed || (!game.LevelSolved_SaveScore && !level.bd_intermission))
5237 SetGameStatus(GAME_MODE_MAIN);
5244 if (level_nr == leveldir_current->handicap_level)
5246 leveldir_current->handicap_level++;
5248 SaveLevelSetup_SeriesInfo();
5251 // save score and score tape before potentially erasing tape below
5252 if (game.LevelSolved_SaveScore)
5253 NewHighScore(last_level_nr, tape_saved);
5255 // increment and load next level (if possible and not configured otherwise)
5256 AdvanceToNextLevel();
5258 if (game.LevelSolved_SaveScore && scores.last_added >= 0 && setup.show_scores_after_game)
5260 SetGameStatus(GAME_MODE_SCORES);
5262 DrawHallOfFame(last_level_nr);
5264 else if (scores.continue_playing)
5266 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5270 SetGameStatus(GAME_MODE_MAIN);
5276 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5277 boolean one_score_entry_per_name)
5281 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5284 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5286 struct ScoreEntry *entry = &list->entry[i];
5287 boolean score_is_better = (new_entry->score > entry->score);
5288 boolean score_is_equal = (new_entry->score == entry->score);
5289 boolean time_is_better = (new_entry->time < entry->time);
5290 boolean time_is_equal = (new_entry->time == entry->time);
5291 boolean better_by_score = (score_is_better ||
5292 (score_is_equal && time_is_better));
5293 boolean better_by_time = (time_is_better ||
5294 (time_is_equal && score_is_better));
5295 boolean is_better = (level.rate_time_over_score ? better_by_time :
5297 boolean entry_is_empty = (entry->score == 0 &&
5300 // prevent adding server score entries if also existing in local score file
5301 // (special case: historic score entries have an empty tape basename entry)
5302 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5303 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5305 // add fields from server score entry not stored in local score entry
5306 // (currently, this means setting platform, version and country fields;
5307 // in rare cases, this may also correct an invalid score value, as
5308 // historic scores might have been truncated to 16-bit values locally)
5309 *entry = *new_entry;
5314 if (is_better || entry_is_empty)
5316 // player has made it to the hall of fame
5318 if (i < MAX_SCORE_ENTRIES - 1)
5320 int m = MAX_SCORE_ENTRIES - 1;
5323 if (one_score_entry_per_name)
5325 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5326 if (strEqual(list->entry[l].name, new_entry->name))
5329 if (m == i) // player's new highscore overwrites his old one
5333 for (l = m; l > i; l--)
5334 list->entry[l] = list->entry[l - 1];
5339 *entry = *new_entry;
5343 else if (one_score_entry_per_name &&
5344 strEqual(entry->name, new_entry->name))
5346 // player already in high score list with better score or time
5352 // special case: new score is beyond the last high score list position
5353 return MAX_SCORE_ENTRIES;
5356 void NewHighScore(int level_nr, boolean tape_saved)
5358 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5359 boolean one_per_name = FALSE;
5361 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5362 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5364 new_entry.score = game.score_final;
5365 new_entry.time = game.score_time_final;
5367 LoadScore(level_nr);
5369 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5371 if (scores.last_added >= MAX_SCORE_ENTRIES)
5373 scores.last_added = MAX_SCORE_ENTRIES - 1;
5374 scores.force_last_added = TRUE;
5376 scores.entry[scores.last_added] = new_entry;
5378 // store last added local score entry (before merging server scores)
5379 scores.last_added_local = scores.last_added;
5384 if (scores.last_added < 0)
5387 SaveScore(level_nr);
5389 // store last added local score entry (before merging server scores)
5390 scores.last_added_local = scores.last_added;
5392 if (!game.LevelSolved_SaveTape)
5395 SaveScoreTape(level_nr);
5397 if (setup.ask_for_using_api_server)
5399 setup.use_api_server =
5400 Request("Upload your score and tape to the high score server?", REQ_ASK);
5402 if (!setup.use_api_server)
5403 Request("Not using high score server! Use setup menu to enable again!",
5406 runtime.use_api_server = setup.use_api_server;
5408 // after asking for using API server once, do not ask again
5409 setup.ask_for_using_api_server = FALSE;
5411 SaveSetup_ServerSetup();
5414 SaveServerScore(level_nr, tape_saved);
5417 void MergeServerScore(void)
5419 struct ScoreEntry last_added_entry;
5420 boolean one_per_name = FALSE;
5423 if (scores.last_added >= 0)
5424 last_added_entry = scores.entry[scores.last_added];
5426 for (i = 0; i < server_scores.num_entries; i++)
5428 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5430 if (pos >= 0 && pos <= scores.last_added)
5431 scores.last_added++;
5434 if (scores.last_added >= MAX_SCORE_ENTRIES)
5436 scores.last_added = MAX_SCORE_ENTRIES - 1;
5437 scores.force_last_added = TRUE;
5439 scores.entry[scores.last_added] = last_added_entry;
5443 static int getElementMoveStepsizeExt(int x, int y, int direction)
5445 int element = Tile[x][y];
5446 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5447 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5448 int horiz_move = (dx != 0);
5449 int sign = (horiz_move ? dx : dy);
5450 int step = sign * element_info[element].move_stepsize;
5452 // special values for move stepsize for spring and things on conveyor belt
5455 if (CAN_FALL(element) &&
5456 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5457 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5458 else if (element == EL_SPRING)
5459 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5465 static int getElementMoveStepsize(int x, int y)
5467 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5470 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5472 if (player->GfxAction != action || player->GfxDir != dir)
5474 player->GfxAction = action;
5475 player->GfxDir = dir;
5477 player->StepFrame = 0;
5481 static void ResetGfxFrame(int x, int y)
5483 // profiling showed that "autotest" spends 10~20% of its time in this function
5484 if (DrawingDeactivatedField())
5487 int element = Tile[x][y];
5488 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5490 if (graphic_info[graphic].anim_global_sync)
5491 GfxFrame[x][y] = FrameCounter;
5492 else if (graphic_info[graphic].anim_global_anim_sync)
5493 GfxFrame[x][y] = getGlobalAnimSyncFrame();
5494 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5495 GfxFrame[x][y] = CustomValue[x][y];
5496 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5497 GfxFrame[x][y] = element_info[element].collect_score;
5498 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5499 GfxFrame[x][y] = ChangeDelay[x][y];
5502 static void ResetGfxAnimation(int x, int y)
5504 GfxAction[x][y] = ACTION_DEFAULT;
5505 GfxDir[x][y] = MovDir[x][y];
5508 ResetGfxFrame(x, y);
5511 static void ResetRandomAnimationValue(int x, int y)
5513 GfxRandom[x][y] = INIT_GFX_RANDOM();
5516 static void InitMovingField(int x, int y, int direction)
5518 int element = Tile[x][y];
5519 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5520 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5523 boolean is_moving_before, is_moving_after;
5525 // check if element was/is moving or being moved before/after mode change
5526 is_moving_before = (WasJustMoving[x][y] != 0);
5527 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5529 // reset animation only for moving elements which change direction of moving
5530 // or which just started or stopped moving
5531 // (else CEs with property "can move" / "not moving" are reset each frame)
5532 if (is_moving_before != is_moving_after ||
5533 direction != MovDir[x][y])
5534 ResetGfxAnimation(x, y);
5536 MovDir[x][y] = direction;
5537 GfxDir[x][y] = direction;
5539 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5540 direction == MV_DOWN && CAN_FALL(element) ?
5541 ACTION_FALLING : ACTION_MOVING);
5543 // this is needed for CEs with property "can move" / "not moving"
5545 if (is_moving_after)
5547 if (Tile[newx][newy] == EL_EMPTY)
5548 Tile[newx][newy] = EL_BLOCKED;
5550 MovDir[newx][newy] = MovDir[x][y];
5552 CustomValue[newx][newy] = CustomValue[x][y];
5554 GfxFrame[newx][newy] = GfxFrame[x][y];
5555 GfxRandom[newx][newy] = GfxRandom[x][y];
5556 GfxAction[newx][newy] = GfxAction[x][y];
5557 GfxDir[newx][newy] = GfxDir[x][y];
5561 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5563 int direction = MovDir[x][y];
5564 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5565 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5571 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5573 int direction = MovDir[x][y];
5574 int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0);
5575 int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0);
5577 *comes_from_x = oldx;
5578 *comes_from_y = oldy;
5581 static int MovingOrBlocked2Element(int x, int y)
5583 int element = Tile[x][y];
5585 if (element == EL_BLOCKED)
5589 Blocked2Moving(x, y, &oldx, &oldy);
5591 return Tile[oldx][oldy];
5597 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5599 // like MovingOrBlocked2Element(), but if element is moving
5600 // and (x, y) is the field the moving element is just leaving,
5601 // return EL_BLOCKED instead of the element value
5602 int element = Tile[x][y];
5604 if (IS_MOVING(x, y))
5606 if (element == EL_BLOCKED)
5610 Blocked2Moving(x, y, &oldx, &oldy);
5611 return Tile[oldx][oldy];
5620 static void RemoveField(int x, int y)
5622 Tile[x][y] = EL_EMPTY;
5628 CustomValue[x][y] = 0;
5631 ChangeDelay[x][y] = 0;
5632 ChangePage[x][y] = -1;
5633 Pushed[x][y] = FALSE;
5635 GfxElement[x][y] = EL_UNDEFINED;
5636 GfxAction[x][y] = ACTION_DEFAULT;
5637 GfxDir[x][y] = MV_NONE;
5640 static void RemoveMovingField(int x, int y)
5642 int oldx = x, oldy = y, newx = x, newy = y;
5643 int element = Tile[x][y];
5644 int next_element = EL_UNDEFINED;
5646 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5649 if (IS_MOVING(x, y))
5651 Moving2Blocked(x, y, &newx, &newy);
5653 if (Tile[newx][newy] != EL_BLOCKED)
5655 // element is moving, but target field is not free (blocked), but
5656 // already occupied by something different (example: acid pool);
5657 // in this case, only remove the moving field, but not the target
5659 RemoveField(oldx, oldy);
5661 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5663 TEST_DrawLevelField(oldx, oldy);
5668 else if (element == EL_BLOCKED)
5670 Blocked2Moving(x, y, &oldx, &oldy);
5671 if (!IS_MOVING(oldx, oldy))
5675 if (element == EL_BLOCKED &&
5676 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5677 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5678 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5679 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5680 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5681 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5682 next_element = get_next_element(Tile[oldx][oldy]);
5684 RemoveField(oldx, oldy);
5685 RemoveField(newx, newy);
5687 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5689 if (next_element != EL_UNDEFINED)
5690 Tile[oldx][oldy] = next_element;
5692 TEST_DrawLevelField(oldx, oldy);
5693 TEST_DrawLevelField(newx, newy);
5696 void DrawDynamite(int x, int y)
5698 int sx = SCREENX(x), sy = SCREENY(y);
5699 int graphic = el2img(Tile[x][y]);
5702 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5705 if (IS_WALKABLE_INSIDE(Back[x][y]))
5709 DrawLevelElement(x, y, Back[x][y]);
5710 else if (Store[x][y])
5711 DrawLevelElement(x, y, Store[x][y]);
5712 else if (game.use_masked_elements)
5713 DrawLevelElement(x, y, EL_EMPTY);
5715 frame = getGraphicAnimationFrameXY(graphic, x, y);
5717 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5718 DrawGraphicThruMask(sx, sy, graphic, frame);
5720 DrawGraphic(sx, sy, graphic, frame);
5723 static void CheckDynamite(int x, int y)
5725 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5729 if (MovDelay[x][y] != 0)
5732 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5738 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5743 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5745 boolean num_checked_players = 0;
5748 for (i = 0; i < MAX_PLAYERS; i++)
5750 if (stored_player[i].active)
5752 int sx = stored_player[i].jx;
5753 int sy = stored_player[i].jy;
5755 if (num_checked_players == 0)
5762 *sx1 = MIN(*sx1, sx);
5763 *sy1 = MIN(*sy1, sy);
5764 *sx2 = MAX(*sx2, sx);
5765 *sy2 = MAX(*sy2, sy);
5768 num_checked_players++;
5773 static boolean checkIfAllPlayersFitToScreen_RND(void)
5775 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5777 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5779 return (sx2 - sx1 < SCR_FIELDX &&
5780 sy2 - sy1 < SCR_FIELDY);
5783 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5785 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5787 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5789 *sx = (sx1 + sx2) / 2;
5790 *sy = (sy1 + sy2) / 2;
5793 static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
5794 boolean center_screen, boolean quick_relocation)
5796 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5797 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5798 boolean no_delay = (tape.warp_forward);
5799 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5800 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5801 int new_scroll_x, new_scroll_y;
5803 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5805 // case 1: quick relocation inside visible screen (without scrolling)
5812 if (!level.shifted_relocation || center_screen)
5814 // relocation _with_ centering of screen
5816 new_scroll_x = SCROLL_POSITION_X(x);
5817 new_scroll_y = SCROLL_POSITION_Y(y);
5821 // relocation _without_ centering of screen
5823 // apply distance between old and new player position to scroll position
5824 int shifted_scroll_x = scroll_x + (x - old_x);
5825 int shifted_scroll_y = scroll_y + (y - old_y);
5827 // make sure that shifted scroll position does not scroll beyond screen
5828 new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
5829 new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
5831 // special case for teleporting from one end of the playfield to the other
5832 // (this kludge prevents the destination area to be shifted by half a tile
5833 // against the source destination for even screen width or screen height;
5834 // probably most useful when used with high "game.forced_scroll_delay_value"
5835 // in combination with "game.forced_scroll_x" and "game.forced_scroll_y")
5836 if (quick_relocation)
5838 if (EVEN(SCR_FIELDX))
5840 // relocate (teleport) between left and right border (half or full)
5841 if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1)
5842 new_scroll_x = SBX_Right;
5843 else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right)
5844 new_scroll_x = SBX_Right - 1;
5845 else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1)
5846 new_scroll_x = SBX_Left;
5847 else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left)
5848 new_scroll_x = SBX_Left + 1;
5851 if (EVEN(SCR_FIELDY))
5853 // relocate (teleport) between top and bottom border (half or full)
5854 if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1)
5855 new_scroll_y = SBY_Lower;
5856 else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower)
5857 new_scroll_y = SBY_Lower - 1;
5858 else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1)
5859 new_scroll_y = SBY_Upper;
5860 else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper)
5861 new_scroll_y = SBY_Upper + 1;
5866 if (quick_relocation)
5868 // case 2: quick relocation (redraw without visible scrolling)
5870 scroll_x = new_scroll_x;
5871 scroll_y = new_scroll_y;
5878 // case 3: visible relocation (with scrolling to new position)
5880 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5882 SetVideoFrameDelay(wait_delay_value);
5884 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5886 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5887 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5889 if (dx == 0 && dy == 0) // no scrolling needed at all
5895 // set values for horizontal/vertical screen scrolling (half tile size)
5896 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5897 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5898 int pos_x = dx * TILEX / 2;
5899 int pos_y = dy * TILEY / 2;
5900 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5901 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5903 ScrollLevel(dx, dy);
5906 // scroll in two steps of half tile size to make things smoother
5907 BlitScreenToBitmapExt_RND(window, fx, fy);
5909 // scroll second step to align at full tile size
5910 BlitScreenToBitmap(window);
5916 SetVideoFrameDelay(frame_delay_value_old);
5919 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5921 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5922 int player_nr = GET_PLAYER_NR(el_player);
5923 struct PlayerInfo *player = &stored_player[player_nr];
5924 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5925 boolean no_delay = (tape.warp_forward);
5926 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5927 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5928 int old_jx = player->jx;
5929 int old_jy = player->jy;
5930 int old_element = Tile[old_jx][old_jy];
5931 int element = Tile[jx][jy];
5932 boolean player_relocated = (old_jx != jx || old_jy != jy);
5934 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5935 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5936 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5937 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5938 int leave_side_horiz = move_dir_horiz;
5939 int leave_side_vert = move_dir_vert;
5940 int enter_side = enter_side_horiz | enter_side_vert;
5941 int leave_side = leave_side_horiz | leave_side_vert;
5943 if (player->buried) // do not reanimate dead player
5946 if (!player_relocated) // no need to relocate the player
5949 if (IS_PLAYER(jx, jy)) // player already placed at new position
5951 RemoveField(jx, jy); // temporarily remove newly placed player
5952 DrawLevelField(jx, jy);
5955 if (player->present)
5957 while (player->MovPos)
5959 ScrollPlayer(player, SCROLL_GO_ON);
5960 ScrollScreen(NULL, SCROLL_GO_ON);
5962 AdvanceFrameAndPlayerCounters(player->index_nr);
5966 BackToFront_WithFrameDelay(wait_delay_value);
5969 DrawPlayer(player); // needed here only to cleanup last field
5970 DrawLevelField(player->jx, player->jy); // remove player graphic
5972 player->is_moving = FALSE;
5975 if (IS_CUSTOM_ELEMENT(old_element))
5976 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5978 player->index_bit, leave_side);
5980 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5982 player->index_bit, leave_side);
5984 Tile[jx][jy] = el_player;
5985 InitPlayerField(jx, jy, el_player, TRUE);
5987 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5988 possible that the relocation target field did not contain a player element,
5989 but a walkable element, to which the new player was relocated -- in this
5990 case, restore that (already initialized!) element on the player field */
5991 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5993 Tile[jx][jy] = element; // restore previously existing element
5996 // only visually relocate centered player
5997 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy,
5998 FALSE, level.instant_relocation);
6000 TestIfPlayerTouchesBadThing(jx, jy);
6001 TestIfPlayerTouchesCustomElement(jx, jy);
6003 if (IS_CUSTOM_ELEMENT(element))
6004 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
6005 player->index_bit, enter_side);
6007 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
6008 player->index_bit, enter_side);
6010 if (player->is_switching)
6012 /* ensure that relocation while still switching an element does not cause
6013 a new element to be treated as also switched directly after relocation
6014 (this is important for teleporter switches that teleport the player to
6015 a place where another teleporter switch is in the same direction, which
6016 would then incorrectly be treated as immediately switched before the
6017 direction key that caused the switch was released) */
6019 player->switch_x += jx - old_jx;
6020 player->switch_y += jy - old_jy;
6024 static void Explode(int ex, int ey, int phase, int mode)
6030 if (game.explosions_delayed)
6032 ExplodeField[ex][ey] = mode;
6036 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
6038 int center_element = Tile[ex][ey];
6039 int ce_value = CustomValue[ex][ey];
6040 int ce_score = element_info[center_element].collect_score;
6041 int artwork_element, explosion_element; // set these values later
6043 // remove things displayed in background while burning dynamite
6044 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
6047 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
6049 // put moving element to center field (and let it explode there)
6050 center_element = MovingOrBlocked2Element(ex, ey);
6051 RemoveMovingField(ex, ey);
6052 Tile[ex][ey] = center_element;
6055 // now "center_element" is finally determined -- set related values now
6056 artwork_element = center_element; // for custom player artwork
6057 explosion_element = center_element; // for custom player artwork
6059 if (IS_PLAYER(ex, ey))
6061 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
6063 artwork_element = stored_player[player_nr].artwork_element;
6065 if (level.use_explosion_element[player_nr])
6067 explosion_element = level.explosion_element[player_nr];
6068 artwork_element = explosion_element;
6072 if (mode == EX_TYPE_NORMAL ||
6073 mode == EX_TYPE_CENTER ||
6074 mode == EX_TYPE_CROSS)
6075 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
6077 last_phase = element_info[explosion_element].explosion_delay + 1;
6079 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
6081 int xx = x - ex + 1;
6082 int yy = y - ey + 1;
6085 if (!IN_LEV_FIELD(x, y) ||
6086 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
6087 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
6090 element = Tile[x][y];
6092 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
6094 element = MovingOrBlocked2Element(x, y);
6096 if (!IS_EXPLOSION_PROOF(element))
6097 RemoveMovingField(x, y);
6100 // indestructible elements can only explode in center (but not flames)
6101 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
6102 mode == EX_TYPE_BORDER)) ||
6103 element == EL_FLAMES)
6106 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
6107 behaviour, for example when touching a yamyam that explodes to rocks
6108 with active deadly shield, a rock is created under the player !!! */
6109 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
6111 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
6112 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
6113 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
6115 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
6118 if (IS_ACTIVE_BOMB(element))
6120 // re-activate things under the bomb like gate or penguin
6121 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
6128 // save walkable background elements while explosion on same tile
6129 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
6130 (x != ex || y != ey || mode == EX_TYPE_BORDER))
6131 Back[x][y] = element;
6133 // ignite explodable elements reached by other explosion
6134 if (element == EL_EXPLOSION)
6135 element = Store2[x][y];
6137 if (AmoebaNr[x][y] &&
6138 (element == EL_AMOEBA_FULL ||
6139 element == EL_BD_AMOEBA ||
6140 element == EL_AMOEBA_GROWING))
6142 AmoebaCnt[AmoebaNr[x][y]]--;
6143 AmoebaCnt2[AmoebaNr[x][y]]--;
6148 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
6150 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
6152 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
6154 if (PLAYERINFO(ex, ey)->use_murphy)
6155 Store[x][y] = EL_EMPTY;
6158 // !!! check this case -- currently needed for rnd_rado_negundo_v,
6159 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
6160 else if (IS_PLAYER_ELEMENT(center_element))
6161 Store[x][y] = EL_EMPTY;
6162 else if (center_element == EL_YAMYAM)
6163 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
6164 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
6165 Store[x][y] = element_info[center_element].content.e[xx][yy];
6167 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
6168 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
6169 // otherwise) -- FIX THIS !!!
6170 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
6171 Store[x][y] = element_info[element].content.e[1][1];
6173 else if (!CAN_EXPLODE(element))
6174 Store[x][y] = element_info[element].content.e[1][1];
6177 Store[x][y] = EL_EMPTY;
6179 if (IS_CUSTOM_ELEMENT(center_element))
6180 Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value :
6181 Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score :
6182 Store[x][y] >= EL_PREV_CE_8 &&
6183 Store[x][y] <= EL_NEXT_CE_8 ?
6184 RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) :
6187 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
6188 center_element == EL_AMOEBA_TO_DIAMOND)
6189 Store2[x][y] = element;
6191 Tile[x][y] = EL_EXPLOSION;
6192 GfxElement[x][y] = artwork_element;
6194 ExplodePhase[x][y] = 1;
6195 ExplodeDelay[x][y] = last_phase;
6200 if (center_element == EL_YAMYAM)
6201 game.yamyam_content_nr =
6202 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
6214 GfxFrame[x][y] = 0; // restart explosion animation
6216 last_phase = ExplodeDelay[x][y];
6218 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
6220 // this can happen if the player leaves an explosion just in time
6221 if (GfxElement[x][y] == EL_UNDEFINED)
6222 GfxElement[x][y] = EL_EMPTY;
6224 border_element = Store2[x][y];
6225 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6226 border_element = StorePlayer[x][y];
6228 if (phase == element_info[border_element].ignition_delay ||
6229 phase == last_phase)
6231 boolean border_explosion = FALSE;
6233 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6234 !PLAYER_EXPLOSION_PROTECTED(x, y))
6236 KillPlayerUnlessExplosionProtected(x, y);
6237 border_explosion = TRUE;
6239 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6241 Tile[x][y] = Store2[x][y];
6244 border_explosion = TRUE;
6246 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6248 AmoebaToDiamond(x, y);
6250 border_explosion = TRUE;
6253 // if an element just explodes due to another explosion (chain-reaction),
6254 // do not immediately end the new explosion when it was the last frame of
6255 // the explosion (as it would be done in the following "if"-statement!)
6256 if (border_explosion && phase == last_phase)
6260 // this can happen if the player was just killed by an explosion
6261 if (GfxElement[x][y] == EL_UNDEFINED)
6262 GfxElement[x][y] = EL_EMPTY;
6264 if (phase == last_phase)
6268 element = Tile[x][y] = Store[x][y];
6269 Store[x][y] = Store2[x][y] = 0;
6270 GfxElement[x][y] = EL_UNDEFINED;
6272 // player can escape from explosions and might therefore be still alive
6273 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6274 element <= EL_PLAYER_IS_EXPLODING_4)
6276 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6277 int explosion_element = EL_PLAYER_1 + player_nr;
6278 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6279 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6281 if (level.use_explosion_element[player_nr])
6282 explosion_element = level.explosion_element[player_nr];
6284 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6285 element_info[explosion_element].content.e[xx][yy]);
6288 // restore probably existing indestructible background element
6289 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6290 element = Tile[x][y] = Back[x][y];
6293 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6294 GfxDir[x][y] = MV_NONE;
6295 ChangeDelay[x][y] = 0;
6296 ChangePage[x][y] = -1;
6298 CustomValue[x][y] = 0;
6300 InitField_WithBug2(x, y, FALSE);
6302 TEST_DrawLevelField(x, y);
6304 TestIfElementTouchesCustomElement(x, y);
6306 if (GFX_CRUMBLED(element))
6307 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6309 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6310 StorePlayer[x][y] = 0;
6312 if (IS_PLAYER_ELEMENT(element))
6313 RelocatePlayer(x, y, element);
6315 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6317 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6318 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6321 TEST_DrawLevelFieldCrumbled(x, y);
6323 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6325 DrawLevelElement(x, y, Back[x][y]);
6326 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6328 else if (IS_WALKABLE_UNDER(Back[x][y]))
6330 DrawLevelGraphic(x, y, graphic, frame);
6331 DrawLevelElementThruMask(x, y, Back[x][y]);
6333 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6334 DrawLevelGraphic(x, y, graphic, frame);
6338 static void DynaExplode(int ex, int ey)
6341 int dynabomb_element = Tile[ex][ey];
6342 int dynabomb_size = 1;
6343 boolean dynabomb_xl = FALSE;
6344 struct PlayerInfo *player;
6345 struct XY *xy = xy_topdown;
6347 if (IS_ACTIVE_BOMB(dynabomb_element))
6349 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6350 dynabomb_size = player->dynabomb_size;
6351 dynabomb_xl = player->dynabomb_xl;
6352 player->dynabombs_left++;
6355 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6357 for (i = 0; i < NUM_DIRECTIONS; i++)
6359 for (j = 1; j <= dynabomb_size; j++)
6361 int x = ex + j * xy[i].x;
6362 int y = ey + j * xy[i].y;
6365 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6368 element = Tile[x][y];
6370 // do not restart explosions of fields with active bombs
6371 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6374 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6376 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6377 !IS_DIGGABLE(element) && !dynabomb_xl)
6383 void Bang(int x, int y)
6385 int element = MovingOrBlocked2Element(x, y);
6386 int explosion_type = EX_TYPE_NORMAL;
6388 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6390 struct PlayerInfo *player = PLAYERINFO(x, y);
6392 element = Tile[x][y] = player->initial_element;
6394 if (level.use_explosion_element[player->index_nr])
6396 int explosion_element = level.explosion_element[player->index_nr];
6398 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6399 explosion_type = EX_TYPE_CROSS;
6400 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6401 explosion_type = EX_TYPE_CENTER;
6409 case EL_BD_BUTTERFLY:
6412 case EL_DARK_YAMYAM:
6416 RaiseScoreElement(element);
6419 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6420 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6421 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6422 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6423 case EL_DYNABOMB_INCREASE_NUMBER:
6424 case EL_DYNABOMB_INCREASE_SIZE:
6425 case EL_DYNABOMB_INCREASE_POWER:
6426 explosion_type = EX_TYPE_DYNA;
6429 case EL_DC_LANDMINE:
6430 explosion_type = EX_TYPE_CENTER;
6435 case EL_LAMP_ACTIVE:
6436 case EL_AMOEBA_TO_DIAMOND:
6437 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6438 explosion_type = EX_TYPE_CENTER;
6442 if (element_info[element].explosion_type == EXPLODES_CROSS)
6443 explosion_type = EX_TYPE_CROSS;
6444 else if (element_info[element].explosion_type == EXPLODES_1X1)
6445 explosion_type = EX_TYPE_CENTER;
6449 if (explosion_type == EX_TYPE_DYNA)
6452 Explode(x, y, EX_PHASE_START, explosion_type);
6454 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6457 static void SplashAcid(int x, int y)
6459 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6460 (!IN_LEV_FIELD(x - 1, y - 2) ||
6461 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6462 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6464 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6465 (!IN_LEV_FIELD(x + 1, y - 2) ||
6466 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6467 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6469 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6472 static void InitBeltMovement(void)
6474 static int belt_base_element[4] =
6476 EL_CONVEYOR_BELT_1_LEFT,
6477 EL_CONVEYOR_BELT_2_LEFT,
6478 EL_CONVEYOR_BELT_3_LEFT,
6479 EL_CONVEYOR_BELT_4_LEFT
6481 static int belt_base_active_element[4] =
6483 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6484 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6485 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6486 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6491 // set frame order for belt animation graphic according to belt direction
6492 for (i = 0; i < NUM_BELTS; i++)
6496 for (j = 0; j < NUM_BELT_PARTS; j++)
6498 int element = belt_base_active_element[belt_nr] + j;
6499 int graphic_1 = el2img(element);
6500 int graphic_2 = el2panelimg(element);
6502 if (game.belt_dir[i] == MV_LEFT)
6504 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6505 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6509 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6510 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6515 SCAN_PLAYFIELD(x, y)
6517 int element = Tile[x][y];
6519 for (i = 0; i < NUM_BELTS; i++)
6521 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6523 int e_belt_nr = getBeltNrFromBeltElement(element);
6526 if (e_belt_nr == belt_nr)
6528 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6530 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6537 static void ToggleBeltSwitch(int x, int y)
6539 static int belt_base_element[4] =
6541 EL_CONVEYOR_BELT_1_LEFT,
6542 EL_CONVEYOR_BELT_2_LEFT,
6543 EL_CONVEYOR_BELT_3_LEFT,
6544 EL_CONVEYOR_BELT_4_LEFT
6546 static int belt_base_active_element[4] =
6548 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6549 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6550 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6551 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6553 static int belt_base_switch_element[4] =
6555 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6556 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6557 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6558 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6560 static int belt_move_dir[4] =
6568 int element = Tile[x][y];
6569 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6570 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6571 int belt_dir = belt_move_dir[belt_dir_nr];
6574 if (!IS_BELT_SWITCH(element))
6577 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6578 game.belt_dir[belt_nr] = belt_dir;
6580 if (belt_dir_nr == 3)
6583 // set frame order for belt animation graphic according to belt direction
6584 for (i = 0; i < NUM_BELT_PARTS; i++)
6586 int element = belt_base_active_element[belt_nr] + i;
6587 int graphic_1 = el2img(element);
6588 int graphic_2 = el2panelimg(element);
6590 if (belt_dir == MV_LEFT)
6592 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6593 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6597 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6598 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6602 SCAN_PLAYFIELD(xx, yy)
6604 int element = Tile[xx][yy];
6606 if (IS_BELT_SWITCH(element))
6608 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6610 if (e_belt_nr == belt_nr)
6612 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6613 TEST_DrawLevelField(xx, yy);
6616 else if (IS_BELT(element) && belt_dir != MV_NONE)
6618 int e_belt_nr = getBeltNrFromBeltElement(element);
6620 if (e_belt_nr == belt_nr)
6622 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6624 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6625 TEST_DrawLevelField(xx, yy);
6628 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6630 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6632 if (e_belt_nr == belt_nr)
6634 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6636 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6637 TEST_DrawLevelField(xx, yy);
6643 static void ToggleSwitchgateSwitch(void)
6647 game.switchgate_pos = !game.switchgate_pos;
6649 SCAN_PLAYFIELD(xx, yy)
6651 int element = Tile[xx][yy];
6653 if (element == EL_SWITCHGATE_SWITCH_UP)
6655 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6656 TEST_DrawLevelField(xx, yy);
6658 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6660 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6661 TEST_DrawLevelField(xx, yy);
6663 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6665 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6666 TEST_DrawLevelField(xx, yy);
6668 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6670 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6671 TEST_DrawLevelField(xx, yy);
6673 else if (element == EL_SWITCHGATE_OPEN ||
6674 element == EL_SWITCHGATE_OPENING)
6676 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6678 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6680 else if (element == EL_SWITCHGATE_CLOSED ||
6681 element == EL_SWITCHGATE_CLOSING)
6683 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6685 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6690 static int getInvisibleActiveFromInvisibleElement(int element)
6692 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6693 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6694 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6698 static int getInvisibleFromInvisibleActiveElement(int element)
6700 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6701 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6702 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6706 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6710 SCAN_PLAYFIELD(x, y)
6712 int element = Tile[x][y];
6714 if (element == EL_LIGHT_SWITCH &&
6715 game.light_time_left > 0)
6717 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6718 TEST_DrawLevelField(x, y);
6720 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6721 game.light_time_left == 0)
6723 Tile[x][y] = EL_LIGHT_SWITCH;
6724 TEST_DrawLevelField(x, y);
6726 else if (element == EL_EMC_DRIPPER &&
6727 game.light_time_left > 0)
6729 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6730 TEST_DrawLevelField(x, y);
6732 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6733 game.light_time_left == 0)
6735 Tile[x][y] = EL_EMC_DRIPPER;
6736 TEST_DrawLevelField(x, y);
6738 else if (element == EL_INVISIBLE_STEELWALL ||
6739 element == EL_INVISIBLE_WALL ||
6740 element == EL_INVISIBLE_SAND)
6742 if (game.light_time_left > 0)
6743 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6745 TEST_DrawLevelField(x, y);
6747 // uncrumble neighbour fields, if needed
6748 if (element == EL_INVISIBLE_SAND)
6749 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6751 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6752 element == EL_INVISIBLE_WALL_ACTIVE ||
6753 element == EL_INVISIBLE_SAND_ACTIVE)
6755 if (game.light_time_left == 0)
6756 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6758 TEST_DrawLevelField(x, y);
6760 // re-crumble neighbour fields, if needed
6761 if (element == EL_INVISIBLE_SAND)
6762 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6767 static void RedrawAllInvisibleElementsForLenses(void)
6771 SCAN_PLAYFIELD(x, y)
6773 int element = Tile[x][y];
6775 if (element == EL_EMC_DRIPPER &&
6776 game.lenses_time_left > 0)
6778 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6779 TEST_DrawLevelField(x, y);
6781 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6782 game.lenses_time_left == 0)
6784 Tile[x][y] = EL_EMC_DRIPPER;
6785 TEST_DrawLevelField(x, y);
6787 else if (element == EL_INVISIBLE_STEELWALL ||
6788 element == EL_INVISIBLE_WALL ||
6789 element == EL_INVISIBLE_SAND)
6791 if (game.lenses_time_left > 0)
6792 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6794 TEST_DrawLevelField(x, y);
6796 // uncrumble neighbour fields, if needed
6797 if (element == EL_INVISIBLE_SAND)
6798 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6800 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6801 element == EL_INVISIBLE_WALL_ACTIVE ||
6802 element == EL_INVISIBLE_SAND_ACTIVE)
6804 if (game.lenses_time_left == 0)
6805 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6807 TEST_DrawLevelField(x, y);
6809 // re-crumble neighbour fields, if needed
6810 if (element == EL_INVISIBLE_SAND)
6811 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6816 static void RedrawAllInvisibleElementsForMagnifier(void)
6820 SCAN_PLAYFIELD(x, y)
6822 int element = Tile[x][y];
6824 if (element == EL_EMC_FAKE_GRASS &&
6825 game.magnify_time_left > 0)
6827 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6828 TEST_DrawLevelField(x, y);
6830 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6831 game.magnify_time_left == 0)
6833 Tile[x][y] = EL_EMC_FAKE_GRASS;
6834 TEST_DrawLevelField(x, y);
6836 else if (IS_GATE_GRAY(element) &&
6837 game.magnify_time_left > 0)
6839 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6840 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6841 IS_EM_GATE_GRAY(element) ?
6842 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6843 IS_EMC_GATE_GRAY(element) ?
6844 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6845 IS_DC_GATE_GRAY(element) ?
6846 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6848 TEST_DrawLevelField(x, y);
6850 else if (IS_GATE_GRAY_ACTIVE(element) &&
6851 game.magnify_time_left == 0)
6853 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6854 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6855 IS_EM_GATE_GRAY_ACTIVE(element) ?
6856 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6857 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6858 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6859 IS_DC_GATE_GRAY_ACTIVE(element) ?
6860 EL_DC_GATE_WHITE_GRAY :
6862 TEST_DrawLevelField(x, y);
6867 static void ToggleLightSwitch(int x, int y)
6869 int element = Tile[x][y];
6871 game.light_time_left =
6872 (element == EL_LIGHT_SWITCH ?
6873 level.time_light * FRAMES_PER_SECOND : 0);
6875 RedrawAllLightSwitchesAndInvisibleElements();
6878 static void ActivateTimegateSwitch(int x, int y)
6882 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6884 SCAN_PLAYFIELD(xx, yy)
6886 int element = Tile[xx][yy];
6888 if (element == EL_TIMEGATE_CLOSED ||
6889 element == EL_TIMEGATE_CLOSING)
6891 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6892 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6896 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6898 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6899 TEST_DrawLevelField(xx, yy);
6905 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6906 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6909 static void Impact(int x, int y)
6911 boolean last_line = (y == lev_fieldy - 1);
6912 boolean object_hit = FALSE;
6913 boolean impact = (last_line || object_hit);
6914 int element = Tile[x][y];
6915 int smashed = EL_STEELWALL;
6917 if (!last_line) // check if element below was hit
6919 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6922 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6923 MovDir[x][y + 1] != MV_DOWN ||
6924 MovPos[x][y + 1] <= TILEY / 2));
6926 // do not smash moving elements that left the smashed field in time
6927 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6928 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6931 #if USE_QUICKSAND_IMPACT_BUGFIX
6932 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6934 RemoveMovingField(x, y + 1);
6935 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6936 Tile[x][y + 2] = EL_ROCK;
6937 TEST_DrawLevelField(x, y + 2);
6942 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6944 RemoveMovingField(x, y + 1);
6945 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6946 Tile[x][y + 2] = EL_ROCK;
6947 TEST_DrawLevelField(x, y + 2);
6954 smashed = MovingOrBlocked2Element(x, y + 1);
6956 impact = (last_line || object_hit);
6959 if (!last_line && smashed == EL_ACID) // element falls into acid
6961 SplashAcid(x, y + 1);
6965 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6966 // only reset graphic animation if graphic really changes after impact
6968 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6970 ResetGfxAnimation(x, y);
6971 TEST_DrawLevelField(x, y);
6974 if (impact && CAN_EXPLODE_IMPACT(element))
6979 else if (impact && element == EL_PEARL &&
6980 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6982 ResetGfxAnimation(x, y);
6984 Tile[x][y] = EL_PEARL_BREAKING;
6985 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6988 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6990 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6995 if (impact && element == EL_AMOEBA_DROP)
6997 if (object_hit && IS_PLAYER(x, y + 1))
6998 KillPlayerUnlessEnemyProtected(x, y + 1);
6999 else if (object_hit && smashed == EL_PENGUIN)
7003 Tile[x][y] = EL_AMOEBA_GROWING;
7004 Store[x][y] = EL_AMOEBA_WET;
7006 ResetRandomAnimationValue(x, y);
7011 if (object_hit) // check which object was hit
7013 if ((CAN_PASS_MAGIC_WALL(element) &&
7014 (smashed == EL_MAGIC_WALL ||
7015 smashed == EL_BD_MAGIC_WALL)) ||
7016 (CAN_PASS_DC_MAGIC_WALL(element) &&
7017 smashed == EL_DC_MAGIC_WALL))
7020 int activated_magic_wall =
7021 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
7022 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
7023 EL_DC_MAGIC_WALL_ACTIVE);
7025 // activate magic wall / mill
7026 SCAN_PLAYFIELD(xx, yy)
7028 if (Tile[xx][yy] == smashed)
7029 Tile[xx][yy] = activated_magic_wall;
7032 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
7033 game.magic_wall_active = TRUE;
7035 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
7036 SND_MAGIC_WALL_ACTIVATING :
7037 smashed == EL_BD_MAGIC_WALL ?
7038 SND_BD_MAGIC_WALL_ACTIVATING :
7039 SND_DC_MAGIC_WALL_ACTIVATING));
7042 if (IS_PLAYER(x, y + 1))
7044 if (CAN_SMASH_PLAYER(element))
7046 KillPlayerUnlessEnemyProtected(x, y + 1);
7050 else if (smashed == EL_PENGUIN)
7052 if (CAN_SMASH_PLAYER(element))
7058 else if (element == EL_BD_DIAMOND)
7060 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
7066 else if (((element == EL_SP_INFOTRON ||
7067 element == EL_SP_ZONK) &&
7068 (smashed == EL_SP_SNIKSNAK ||
7069 smashed == EL_SP_ELECTRON ||
7070 smashed == EL_SP_DISK_ORANGE)) ||
7071 (element == EL_SP_INFOTRON &&
7072 smashed == EL_SP_DISK_YELLOW))
7077 else if (CAN_SMASH_EVERYTHING(element))
7079 if (IS_CLASSIC_ENEMY(smashed) ||
7080 CAN_EXPLODE_SMASHED(smashed))
7085 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
7087 if (smashed == EL_LAMP ||
7088 smashed == EL_LAMP_ACTIVE)
7093 else if (smashed == EL_NUT)
7095 Tile[x][y + 1] = EL_NUT_BREAKING;
7096 PlayLevelSound(x, y, SND_NUT_BREAKING);
7097 RaiseScoreElement(EL_NUT);
7100 else if (smashed == EL_PEARL)
7102 ResetGfxAnimation(x, y);
7104 Tile[x][y + 1] = EL_PEARL_BREAKING;
7105 PlayLevelSound(x, y, SND_PEARL_BREAKING);
7108 else if (smashed == EL_DIAMOND)
7110 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
7111 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
7114 else if (IS_BELT_SWITCH(smashed))
7116 ToggleBeltSwitch(x, y + 1);
7118 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
7119 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
7120 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
7121 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
7123 ToggleSwitchgateSwitch();
7125 else if (smashed == EL_LIGHT_SWITCH ||
7126 smashed == EL_LIGHT_SWITCH_ACTIVE)
7128 ToggleLightSwitch(x, y + 1);
7132 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7134 CheckElementChangeBySide(x, y + 1, smashed, element,
7135 CE_SWITCHED, CH_SIDE_TOP);
7136 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
7142 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7147 // play sound of magic wall / mill
7149 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7150 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
7151 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
7153 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7154 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
7155 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7156 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
7157 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7158 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
7163 // play sound of object that hits the ground
7164 if (last_line || object_hit)
7165 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
7168 static void TurnRoundExt(int x, int y)
7180 { 0, 0 }, { 0, 0 }, { 0, 0 },
7185 int left, right, back;
7189 { MV_DOWN, MV_UP, MV_RIGHT },
7190 { MV_UP, MV_DOWN, MV_LEFT },
7192 { MV_LEFT, MV_RIGHT, MV_DOWN },
7196 { MV_RIGHT, MV_LEFT, MV_UP }
7199 int element = Tile[x][y];
7200 int move_pattern = element_info[element].move_pattern;
7202 int old_move_dir = MovDir[x][y];
7203 int left_dir = turn[old_move_dir].left;
7204 int right_dir = turn[old_move_dir].right;
7205 int back_dir = turn[old_move_dir].back;
7207 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
7208 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
7209 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
7210 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
7212 int left_x = x + left_dx, left_y = y + left_dy;
7213 int right_x = x + right_dx, right_y = y + right_dy;
7214 int move_x = x + move_dx, move_y = y + move_dy;
7218 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
7220 TestIfBadThingTouchesOtherBadThing(x, y);
7222 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
7223 MovDir[x][y] = right_dir;
7224 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7225 MovDir[x][y] = left_dir;
7227 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7229 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7232 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7234 TestIfBadThingTouchesOtherBadThing(x, y);
7236 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7237 MovDir[x][y] = left_dir;
7238 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7239 MovDir[x][y] = right_dir;
7241 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7243 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7246 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7248 TestIfBadThingTouchesOtherBadThing(x, y);
7250 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7251 MovDir[x][y] = left_dir;
7252 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7253 MovDir[x][y] = right_dir;
7255 if (MovDir[x][y] != old_move_dir)
7258 else if (element == EL_YAMYAM)
7260 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7261 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7263 if (can_turn_left && can_turn_right)
7264 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7265 else if (can_turn_left)
7266 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7267 else if (can_turn_right)
7268 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7270 MovDir[x][y] = back_dir;
7272 MovDelay[x][y] = 16 + 16 * RND(3);
7274 else if (element == EL_DARK_YAMYAM)
7276 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7278 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7281 if (can_turn_left && can_turn_right)
7282 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7283 else if (can_turn_left)
7284 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7285 else if (can_turn_right)
7286 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7288 MovDir[x][y] = back_dir;
7290 MovDelay[x][y] = 16 + 16 * RND(3);
7292 else if (element == EL_PACMAN)
7294 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7295 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7297 if (can_turn_left && can_turn_right)
7298 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7299 else if (can_turn_left)
7300 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7301 else if (can_turn_right)
7302 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7304 MovDir[x][y] = back_dir;
7306 MovDelay[x][y] = 6 + RND(40);
7308 else if (element == EL_PIG)
7310 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7311 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7312 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7313 boolean should_turn_left, should_turn_right, should_move_on;
7315 int rnd = RND(rnd_value);
7317 should_turn_left = (can_turn_left &&
7319 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7320 y + back_dy + left_dy)));
7321 should_turn_right = (can_turn_right &&
7323 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7324 y + back_dy + right_dy)));
7325 should_move_on = (can_move_on &&
7328 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7329 y + move_dy + left_dy) ||
7330 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7331 y + move_dy + right_dy)));
7333 if (should_turn_left || should_turn_right || should_move_on)
7335 if (should_turn_left && should_turn_right && should_move_on)
7336 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7337 rnd < 2 * rnd_value / 3 ? right_dir :
7339 else if (should_turn_left && should_turn_right)
7340 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7341 else if (should_turn_left && should_move_on)
7342 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7343 else if (should_turn_right && should_move_on)
7344 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7345 else if (should_turn_left)
7346 MovDir[x][y] = left_dir;
7347 else if (should_turn_right)
7348 MovDir[x][y] = right_dir;
7349 else if (should_move_on)
7350 MovDir[x][y] = old_move_dir;
7352 else if (can_move_on && rnd > rnd_value / 8)
7353 MovDir[x][y] = old_move_dir;
7354 else if (can_turn_left && can_turn_right)
7355 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7356 else if (can_turn_left && rnd > rnd_value / 8)
7357 MovDir[x][y] = left_dir;
7358 else if (can_turn_right && rnd > rnd_value/8)
7359 MovDir[x][y] = right_dir;
7361 MovDir[x][y] = back_dir;
7363 xx = x + move_xy[MovDir[x][y]].dx;
7364 yy = y + move_xy[MovDir[x][y]].dy;
7366 if (!IN_LEV_FIELD(xx, yy) ||
7367 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7368 MovDir[x][y] = old_move_dir;
7372 else if (element == EL_DRAGON)
7374 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7375 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7376 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7378 int rnd = RND(rnd_value);
7380 if (can_move_on && rnd > rnd_value / 8)
7381 MovDir[x][y] = old_move_dir;
7382 else if (can_turn_left && can_turn_right)
7383 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7384 else if (can_turn_left && rnd > rnd_value / 8)
7385 MovDir[x][y] = left_dir;
7386 else if (can_turn_right && rnd > rnd_value / 8)
7387 MovDir[x][y] = right_dir;
7389 MovDir[x][y] = back_dir;
7391 xx = x + move_xy[MovDir[x][y]].dx;
7392 yy = y + move_xy[MovDir[x][y]].dy;
7394 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7395 MovDir[x][y] = old_move_dir;
7399 else if (element == EL_MOLE)
7401 boolean can_move_on =
7402 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7403 IS_AMOEBOID(Tile[move_x][move_y]) ||
7404 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7407 boolean can_turn_left =
7408 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7409 IS_AMOEBOID(Tile[left_x][left_y])));
7411 boolean can_turn_right =
7412 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7413 IS_AMOEBOID(Tile[right_x][right_y])));
7415 if (can_turn_left && can_turn_right)
7416 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7417 else if (can_turn_left)
7418 MovDir[x][y] = left_dir;
7420 MovDir[x][y] = right_dir;
7423 if (MovDir[x][y] != old_move_dir)
7426 else if (element == EL_BALLOON)
7428 MovDir[x][y] = game.wind_direction;
7431 else if (element == EL_SPRING)
7433 if (MovDir[x][y] & MV_HORIZONTAL)
7435 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7436 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7438 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7439 ResetGfxAnimation(move_x, move_y);
7440 TEST_DrawLevelField(move_x, move_y);
7442 MovDir[x][y] = back_dir;
7444 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7445 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7446 MovDir[x][y] = MV_NONE;
7451 else if (element == EL_ROBOT ||
7452 element == EL_SATELLITE ||
7453 element == EL_PENGUIN ||
7454 element == EL_EMC_ANDROID)
7456 int attr_x = -1, attr_y = -1;
7458 if (game.all_players_gone)
7460 attr_x = game.exit_x;
7461 attr_y = game.exit_y;
7467 for (i = 0; i < MAX_PLAYERS; i++)
7469 struct PlayerInfo *player = &stored_player[i];
7470 int jx = player->jx, jy = player->jy;
7472 if (!player->active)
7476 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7484 if (element == EL_ROBOT &&
7485 game.robot_wheel_x >= 0 &&
7486 game.robot_wheel_y >= 0 &&
7487 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7488 game.engine_version < VERSION_IDENT(3,1,0,0)))
7490 attr_x = game.robot_wheel_x;
7491 attr_y = game.robot_wheel_y;
7494 if (element == EL_PENGUIN)
7497 struct XY *xy = xy_topdown;
7499 for (i = 0; i < NUM_DIRECTIONS; i++)
7501 int ex = x + xy[i].x;
7502 int ey = y + xy[i].y;
7504 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7505 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7506 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7507 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7516 MovDir[x][y] = MV_NONE;
7518 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7519 else if (attr_x > x)
7520 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7522 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7523 else if (attr_y > y)
7524 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7526 if (element == EL_ROBOT)
7530 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7531 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7532 Moving2Blocked(x, y, &newx, &newy);
7534 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7535 MovDelay[x][y] = 8 + 8 * !RND(3);
7537 MovDelay[x][y] = 16;
7539 else if (element == EL_PENGUIN)
7545 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7547 boolean first_horiz = RND(2);
7548 int new_move_dir = MovDir[x][y];
7551 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7552 Moving2Blocked(x, y, &newx, &newy);
7554 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7558 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7559 Moving2Blocked(x, y, &newx, &newy);
7561 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7564 MovDir[x][y] = old_move_dir;
7568 else if (element == EL_SATELLITE)
7574 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7576 boolean first_horiz = RND(2);
7577 int new_move_dir = MovDir[x][y];
7580 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7581 Moving2Blocked(x, y, &newx, &newy);
7583 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7587 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7588 Moving2Blocked(x, y, &newx, &newy);
7590 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7593 MovDir[x][y] = old_move_dir;
7597 else if (element == EL_EMC_ANDROID)
7599 static int check_pos[16] =
7601 -1, // 0 => (invalid)
7604 -1, // 3 => (invalid)
7606 0, // 5 => MV_LEFT | MV_UP
7607 2, // 6 => MV_RIGHT | MV_UP
7608 -1, // 7 => (invalid)
7610 6, // 9 => MV_LEFT | MV_DOWN
7611 4, // 10 => MV_RIGHT | MV_DOWN
7612 -1, // 11 => (invalid)
7613 -1, // 12 => (invalid)
7614 -1, // 13 => (invalid)
7615 -1, // 14 => (invalid)
7616 -1, // 15 => (invalid)
7624 { -1, -1, MV_LEFT | MV_UP },
7626 { +1, -1, MV_RIGHT | MV_UP },
7627 { +1, 0, MV_RIGHT },
7628 { +1, +1, MV_RIGHT | MV_DOWN },
7630 { -1, +1, MV_LEFT | MV_DOWN },
7633 int start_pos, check_order;
7634 boolean can_clone = FALSE;
7637 // check if there is any free field around current position
7638 for (i = 0; i < 8; i++)
7640 int newx = x + check_xy[i].dx;
7641 int newy = y + check_xy[i].dy;
7643 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7651 if (can_clone) // randomly find an element to clone
7655 start_pos = check_pos[RND(8)];
7656 check_order = (RND(2) ? -1 : +1);
7658 for (i = 0; i < 8; i++)
7660 int pos_raw = start_pos + i * check_order;
7661 int pos = (pos_raw + 8) % 8;
7662 int newx = x + check_xy[pos].dx;
7663 int newy = y + check_xy[pos].dy;
7665 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7667 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7668 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7670 Store[x][y] = Tile[newx][newy];
7679 if (can_clone) // randomly find a direction to move
7683 start_pos = check_pos[RND(8)];
7684 check_order = (RND(2) ? -1 : +1);
7686 for (i = 0; i < 8; i++)
7688 int pos_raw = start_pos + i * check_order;
7689 int pos = (pos_raw + 8) % 8;
7690 int newx = x + check_xy[pos].dx;
7691 int newy = y + check_xy[pos].dy;
7692 int new_move_dir = check_xy[pos].dir;
7694 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7696 MovDir[x][y] = new_move_dir;
7697 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7706 if (can_clone) // cloning and moving successful
7709 // cannot clone -- try to move towards player
7711 start_pos = check_pos[MovDir[x][y] & 0x0f];
7712 check_order = (RND(2) ? -1 : +1);
7714 for (i = 0; i < 3; i++)
7716 // first check start_pos, then previous/next or (next/previous) pos
7717 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7718 int pos = (pos_raw + 8) % 8;
7719 int newx = x + check_xy[pos].dx;
7720 int newy = y + check_xy[pos].dy;
7721 int new_move_dir = check_xy[pos].dir;
7723 if (IS_PLAYER(newx, newy))
7726 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7728 MovDir[x][y] = new_move_dir;
7729 MovDelay[x][y] = level.android_move_time * 8 + 1;
7736 else if (move_pattern == MV_TURNING_LEFT ||
7737 move_pattern == MV_TURNING_RIGHT ||
7738 move_pattern == MV_TURNING_LEFT_RIGHT ||
7739 move_pattern == MV_TURNING_RIGHT_LEFT ||
7740 move_pattern == MV_TURNING_RANDOM ||
7741 move_pattern == MV_ALL_DIRECTIONS)
7743 boolean can_turn_left =
7744 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7745 boolean can_turn_right =
7746 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y);
7748 if (element_info[element].move_stepsize == 0) // "not moving"
7751 if (move_pattern == MV_TURNING_LEFT)
7752 MovDir[x][y] = left_dir;
7753 else if (move_pattern == MV_TURNING_RIGHT)
7754 MovDir[x][y] = right_dir;
7755 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7756 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7757 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7758 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7759 else if (move_pattern == MV_TURNING_RANDOM)
7760 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7761 can_turn_right && !can_turn_left ? right_dir :
7762 RND(2) ? left_dir : right_dir);
7763 else if (can_turn_left && can_turn_right)
7764 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7765 else if (can_turn_left)
7766 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7767 else if (can_turn_right)
7768 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7770 MovDir[x][y] = back_dir;
7772 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7774 else if (move_pattern == MV_HORIZONTAL ||
7775 move_pattern == MV_VERTICAL)
7777 if (move_pattern & old_move_dir)
7778 MovDir[x][y] = back_dir;
7779 else if (move_pattern == MV_HORIZONTAL)
7780 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7781 else if (move_pattern == MV_VERTICAL)
7782 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7784 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7786 else if (move_pattern & MV_ANY_DIRECTION)
7788 MovDir[x][y] = move_pattern;
7789 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7791 else if (move_pattern & MV_WIND_DIRECTION)
7793 MovDir[x][y] = game.wind_direction;
7794 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7796 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7798 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7799 MovDir[x][y] = left_dir;
7800 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7801 MovDir[x][y] = right_dir;
7803 if (MovDir[x][y] != old_move_dir)
7804 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7806 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7808 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7809 MovDir[x][y] = right_dir;
7810 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7811 MovDir[x][y] = left_dir;
7813 if (MovDir[x][y] != old_move_dir)
7814 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7816 else if (move_pattern == MV_TOWARDS_PLAYER ||
7817 move_pattern == MV_AWAY_FROM_PLAYER)
7819 int attr_x = -1, attr_y = -1;
7821 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7823 if (game.all_players_gone)
7825 attr_x = game.exit_x;
7826 attr_y = game.exit_y;
7832 for (i = 0; i < MAX_PLAYERS; i++)
7834 struct PlayerInfo *player = &stored_player[i];
7835 int jx = player->jx, jy = player->jy;
7837 if (!player->active)
7841 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7849 MovDir[x][y] = MV_NONE;
7851 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7852 else if (attr_x > x)
7853 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7855 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7856 else if (attr_y > y)
7857 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7859 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7861 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7863 boolean first_horiz = RND(2);
7864 int new_move_dir = MovDir[x][y];
7866 if (element_info[element].move_stepsize == 0) // "not moving"
7868 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7869 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7875 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7876 Moving2Blocked(x, y, &newx, &newy);
7878 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7882 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7883 Moving2Blocked(x, y, &newx, &newy);
7885 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7888 MovDir[x][y] = old_move_dir;
7891 else if (move_pattern == MV_WHEN_PUSHED ||
7892 move_pattern == MV_WHEN_DROPPED)
7894 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7895 MovDir[x][y] = MV_NONE;
7899 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7901 struct XY *test_xy = xy_topdown;
7902 static int test_dir[4] =
7909 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7910 int move_preference = -1000000; // start with very low preference
7911 int new_move_dir = MV_NONE;
7912 int start_test = RND(4);
7915 for (i = 0; i < NUM_DIRECTIONS; i++)
7917 int j = (start_test + i) % 4;
7918 int move_dir = test_dir[j];
7919 int move_dir_preference;
7921 xx = x + test_xy[j].x;
7922 yy = y + test_xy[j].y;
7924 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7925 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7927 new_move_dir = move_dir;
7932 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7935 move_dir_preference = -1 * RunnerVisit[xx][yy];
7936 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7937 move_dir_preference = PlayerVisit[xx][yy];
7939 if (move_dir_preference > move_preference)
7941 // prefer field that has not been visited for the longest time
7942 move_preference = move_dir_preference;
7943 new_move_dir = move_dir;
7945 else if (move_dir_preference == move_preference &&
7946 move_dir == old_move_dir)
7948 // prefer last direction when all directions are preferred equally
7949 move_preference = move_dir_preference;
7950 new_move_dir = move_dir;
7954 MovDir[x][y] = new_move_dir;
7955 if (old_move_dir != new_move_dir)
7956 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7960 static void TurnRound(int x, int y)
7962 int direction = MovDir[x][y];
7966 GfxDir[x][y] = MovDir[x][y];
7968 if (direction != MovDir[x][y])
7972 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7974 ResetGfxFrame(x, y);
7977 static boolean JustBeingPushed(int x, int y)
7981 for (i = 0; i < MAX_PLAYERS; i++)
7983 struct PlayerInfo *player = &stored_player[i];
7985 if (player->active && player->is_pushing && player->MovPos)
7987 int next_jx = player->jx + (player->jx - player->last_jx);
7988 int next_jy = player->jy + (player->jy - player->last_jy);
7990 if (x == next_jx && y == next_jy)
7998 static void StartMoving(int x, int y)
8000 boolean started_moving = FALSE; // some elements can fall _and_ move
8001 int element = Tile[x][y];
8006 if (MovDelay[x][y] == 0)
8007 GfxAction[x][y] = ACTION_DEFAULT;
8009 if (CAN_FALL(element) && y < lev_fieldy - 1)
8011 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
8012 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
8013 if (JustBeingPushed(x, y))
8016 if (element == EL_QUICKSAND_FULL)
8018 if (IS_FREE(x, y + 1))
8020 InitMovingField(x, y, MV_DOWN);
8021 started_moving = TRUE;
8023 Tile[x][y] = EL_QUICKSAND_EMPTYING;
8024 #if USE_QUICKSAND_BD_ROCK_BUGFIX
8025 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
8026 Store[x][y] = EL_ROCK;
8028 Store[x][y] = EL_ROCK;
8031 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
8033 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8035 if (!MovDelay[x][y])
8037 MovDelay[x][y] = TILEY + 1;
8039 ResetGfxAnimation(x, y);
8040 ResetGfxAnimation(x, y + 1);
8045 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
8046 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8053 Tile[x][y] = EL_QUICKSAND_EMPTY;
8054 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8055 Store[x][y + 1] = Store[x][y];
8058 PlayLevelSoundAction(x, y, ACTION_FILLING);
8060 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8062 if (!MovDelay[x][y])
8064 MovDelay[x][y] = TILEY + 1;
8066 ResetGfxAnimation(x, y);
8067 ResetGfxAnimation(x, y + 1);
8072 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
8073 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8080 Tile[x][y] = EL_QUICKSAND_EMPTY;
8081 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8082 Store[x][y + 1] = Store[x][y];
8085 PlayLevelSoundAction(x, y, ACTION_FILLING);
8088 else if (element == EL_QUICKSAND_FAST_FULL)
8090 if (IS_FREE(x, y + 1))
8092 InitMovingField(x, y, MV_DOWN);
8093 started_moving = TRUE;
8095 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
8096 #if USE_QUICKSAND_BD_ROCK_BUGFIX
8097 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
8098 Store[x][y] = EL_ROCK;
8100 Store[x][y] = EL_ROCK;
8103 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
8105 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8107 if (!MovDelay[x][y])
8109 MovDelay[x][y] = TILEY + 1;
8111 ResetGfxAnimation(x, y);
8112 ResetGfxAnimation(x, y + 1);
8117 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8118 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8125 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8126 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8127 Store[x][y + 1] = Store[x][y];
8130 PlayLevelSoundAction(x, y, ACTION_FILLING);
8132 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8134 if (!MovDelay[x][y])
8136 MovDelay[x][y] = TILEY + 1;
8138 ResetGfxAnimation(x, y);
8139 ResetGfxAnimation(x, y + 1);
8144 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8145 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8152 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8153 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8154 Store[x][y + 1] = Store[x][y];
8157 PlayLevelSoundAction(x, y, ACTION_FILLING);
8160 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8161 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8163 InitMovingField(x, y, MV_DOWN);
8164 started_moving = TRUE;
8166 Tile[x][y] = EL_QUICKSAND_FILLING;
8167 Store[x][y] = element;
8169 PlayLevelSoundAction(x, y, ACTION_FILLING);
8171 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8172 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8174 InitMovingField(x, y, MV_DOWN);
8175 started_moving = TRUE;
8177 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
8178 Store[x][y] = element;
8180 PlayLevelSoundAction(x, y, ACTION_FILLING);
8182 else if (element == EL_MAGIC_WALL_FULL)
8184 if (IS_FREE(x, y + 1))
8186 InitMovingField(x, y, MV_DOWN);
8187 started_moving = TRUE;
8189 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
8190 Store[x][y] = EL_CHANGED(Store[x][y]);
8192 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
8194 if (!MovDelay[x][y])
8195 MovDelay[x][y] = TILEY / 4 + 1;
8204 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
8205 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
8206 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8210 else if (element == EL_BD_MAGIC_WALL_FULL)
8212 if (IS_FREE(x, y + 1))
8214 InitMovingField(x, y, MV_DOWN);
8215 started_moving = TRUE;
8217 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8218 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8220 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8222 if (!MovDelay[x][y])
8223 MovDelay[x][y] = TILEY / 4 + 1;
8232 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8233 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8234 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8238 else if (element == EL_DC_MAGIC_WALL_FULL)
8240 if (IS_FREE(x, y + 1))
8242 InitMovingField(x, y, MV_DOWN);
8243 started_moving = TRUE;
8245 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8246 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8248 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8250 if (!MovDelay[x][y])
8251 MovDelay[x][y] = TILEY / 4 + 1;
8260 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8261 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8262 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8266 else if ((CAN_PASS_MAGIC_WALL(element) &&
8267 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8268 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8269 (CAN_PASS_DC_MAGIC_WALL(element) &&
8270 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8273 InitMovingField(x, y, MV_DOWN);
8274 started_moving = TRUE;
8277 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8278 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8279 EL_DC_MAGIC_WALL_FILLING);
8280 Store[x][y] = element;
8282 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8284 SplashAcid(x, y + 1);
8286 InitMovingField(x, y, MV_DOWN);
8287 started_moving = TRUE;
8289 Store[x][y] = EL_ACID;
8292 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8293 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8294 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8295 CAN_FALL(element) && WasJustFalling[x][y] &&
8296 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8298 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8299 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8300 (Tile[x][y + 1] == EL_BLOCKED)))
8302 /* this is needed for a special case not covered by calling "Impact()"
8303 from "ContinueMoving()": if an element moves to a tile directly below
8304 another element which was just falling on that tile (which was empty
8305 in the previous frame), the falling element above would just stop
8306 instead of smashing the element below (in previous version, the above
8307 element was just checked for "moving" instead of "falling", resulting
8308 in incorrect smashes caused by horizontal movement of the above
8309 element; also, the case of the player being the element to smash was
8310 simply not covered here... :-/ ) */
8312 CheckCollision[x][y] = 0;
8313 CheckImpact[x][y] = 0;
8317 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8319 if (MovDir[x][y] == MV_NONE)
8321 InitMovingField(x, y, MV_DOWN);
8322 started_moving = TRUE;
8325 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8327 if (WasJustFalling[x][y]) // prevent animation from being restarted
8328 MovDir[x][y] = MV_DOWN;
8330 InitMovingField(x, y, MV_DOWN);
8331 started_moving = TRUE;
8333 else if (element == EL_AMOEBA_DROP)
8335 Tile[x][y] = EL_AMOEBA_GROWING;
8336 Store[x][y] = EL_AMOEBA_WET;
8338 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8339 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8340 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8341 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8343 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8344 (IS_FREE(x - 1, y + 1) ||
8345 Tile[x - 1][y + 1] == EL_ACID));
8346 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8347 (IS_FREE(x + 1, y + 1) ||
8348 Tile[x + 1][y + 1] == EL_ACID));
8349 boolean can_fall_any = (can_fall_left || can_fall_right);
8350 boolean can_fall_both = (can_fall_left && can_fall_right);
8351 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8353 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8355 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8356 can_fall_right = FALSE;
8357 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8358 can_fall_left = FALSE;
8359 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8360 can_fall_right = FALSE;
8361 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8362 can_fall_left = FALSE;
8364 can_fall_any = (can_fall_left || can_fall_right);
8365 can_fall_both = FALSE;
8370 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8371 can_fall_right = FALSE; // slip down on left side
8373 can_fall_left = !(can_fall_right = RND(2));
8375 can_fall_both = FALSE;
8380 // if not determined otherwise, prefer left side for slipping down
8381 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8382 started_moving = TRUE;
8385 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8387 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8388 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8389 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8390 int belt_dir = game.belt_dir[belt_nr];
8392 if ((belt_dir == MV_LEFT && left_is_free) ||
8393 (belt_dir == MV_RIGHT && right_is_free))
8395 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8397 InitMovingField(x, y, belt_dir);
8398 started_moving = TRUE;
8400 Pushed[x][y] = TRUE;
8401 Pushed[nextx][y] = TRUE;
8403 GfxAction[x][y] = ACTION_DEFAULT;
8407 MovDir[x][y] = 0; // if element was moving, stop it
8412 // not "else if" because of elements that can fall and move (EL_SPRING)
8413 if (CAN_MOVE(element) && !started_moving)
8415 int move_pattern = element_info[element].move_pattern;
8418 Moving2Blocked(x, y, &newx, &newy);
8420 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8423 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8424 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8426 WasJustMoving[x][y] = 0;
8427 CheckCollision[x][y] = 0;
8429 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8431 if (Tile[x][y] != element) // element has changed
8435 if (!MovDelay[x][y]) // start new movement phase
8437 // all objects that can change their move direction after each step
8438 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8440 if (element != EL_YAMYAM &&
8441 element != EL_DARK_YAMYAM &&
8442 element != EL_PACMAN &&
8443 !(move_pattern & MV_ANY_DIRECTION) &&
8444 move_pattern != MV_TURNING_LEFT &&
8445 move_pattern != MV_TURNING_RIGHT &&
8446 move_pattern != MV_TURNING_LEFT_RIGHT &&
8447 move_pattern != MV_TURNING_RIGHT_LEFT &&
8448 move_pattern != MV_TURNING_RANDOM)
8452 if (MovDelay[x][y] && (element == EL_BUG ||
8453 element == EL_SPACESHIP ||
8454 element == EL_SP_SNIKSNAK ||
8455 element == EL_SP_ELECTRON ||
8456 element == EL_MOLE))
8457 TEST_DrawLevelField(x, y);
8461 if (MovDelay[x][y]) // wait some time before next movement
8465 if (element == EL_ROBOT ||
8466 element == EL_YAMYAM ||
8467 element == EL_DARK_YAMYAM)
8469 DrawLevelElementAnimationIfNeeded(x, y, element);
8470 PlayLevelSoundAction(x, y, ACTION_WAITING);
8472 else if (element == EL_SP_ELECTRON)
8473 DrawLevelElementAnimationIfNeeded(x, y, element);
8474 else if (element == EL_DRAGON)
8477 int dir = MovDir[x][y];
8478 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8479 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8480 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8481 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8482 dir == MV_UP ? IMG_FLAMES_1_UP :
8483 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8484 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8486 GfxAction[x][y] = ACTION_ATTACKING;
8488 if (IS_PLAYER(x, y))
8489 DrawPlayerField(x, y);
8491 TEST_DrawLevelField(x, y);
8493 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8495 for (i = 1; i <= 3; i++)
8497 int xx = x + i * dx;
8498 int yy = y + i * dy;
8499 int sx = SCREENX(xx);
8500 int sy = SCREENY(yy);
8501 int flame_graphic = graphic + (i - 1);
8503 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8508 int flamed = MovingOrBlocked2Element(xx, yy);
8510 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8513 RemoveMovingField(xx, yy);
8515 ChangeDelay[xx][yy] = 0;
8517 Tile[xx][yy] = EL_FLAMES;
8519 if (IN_SCR_FIELD(sx, sy))
8521 TEST_DrawLevelFieldCrumbled(xx, yy);
8522 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8527 if (Tile[xx][yy] == EL_FLAMES)
8528 Tile[xx][yy] = EL_EMPTY;
8529 TEST_DrawLevelField(xx, yy);
8534 if (MovDelay[x][y]) // element still has to wait some time
8536 PlayLevelSoundAction(x, y, ACTION_WAITING);
8542 // now make next step
8544 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8546 if (DONT_COLLIDE_WITH(element) &&
8547 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8548 !PLAYER_ENEMY_PROTECTED(newx, newy))
8550 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8555 else if (CAN_MOVE_INTO_ACID(element) &&
8556 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8557 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8558 (MovDir[x][y] == MV_DOWN ||
8559 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8561 SplashAcid(newx, newy);
8562 Store[x][y] = EL_ACID;
8564 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8566 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8567 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8568 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8569 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8572 TEST_DrawLevelField(x, y);
8574 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8575 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8576 DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0);
8578 game.friends_still_needed--;
8579 if (!game.friends_still_needed &&
8581 game.all_players_gone)
8586 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8588 if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING)
8589 TEST_DrawLevelField(newx, newy);
8591 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8593 else if (!IS_FREE(newx, newy))
8595 GfxAction[x][y] = ACTION_WAITING;
8597 if (IS_PLAYER(x, y))
8598 DrawPlayerField(x, y);
8600 TEST_DrawLevelField(x, y);
8605 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8607 if (IS_FOOD_PIG(Tile[newx][newy]))
8609 if (IS_MOVING(newx, newy))
8610 RemoveMovingField(newx, newy);
8613 Tile[newx][newy] = EL_EMPTY;
8614 TEST_DrawLevelField(newx, newy);
8617 PlayLevelSound(x, y, SND_PIG_DIGGING);
8619 else if (!IS_FREE(newx, newy))
8621 if (IS_PLAYER(x, y))
8622 DrawPlayerField(x, y);
8624 TEST_DrawLevelField(x, y);
8629 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8631 if (Store[x][y] != EL_EMPTY)
8633 boolean can_clone = FALSE;
8636 // check if element to clone is still there
8637 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8639 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8647 // cannot clone or target field not free anymore -- do not clone
8648 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8649 Store[x][y] = EL_EMPTY;
8652 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8654 if (IS_MV_DIAGONAL(MovDir[x][y]))
8656 int diagonal_move_dir = MovDir[x][y];
8657 int stored = Store[x][y];
8658 int change_delay = 8;
8661 // android is moving diagonally
8663 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8665 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8666 GfxElement[x][y] = EL_EMC_ANDROID;
8667 GfxAction[x][y] = ACTION_SHRINKING;
8668 GfxDir[x][y] = diagonal_move_dir;
8669 ChangeDelay[x][y] = change_delay;
8671 if (Store[x][y] == EL_EMPTY)
8672 Store[x][y] = GfxElementEmpty[x][y];
8674 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8677 DrawLevelGraphicAnimation(x, y, graphic);
8678 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8680 if (Tile[newx][newy] == EL_ACID)
8682 SplashAcid(newx, newy);
8687 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8689 Store[newx][newy] = EL_EMC_ANDROID;
8690 GfxElement[newx][newy] = EL_EMC_ANDROID;
8691 GfxAction[newx][newy] = ACTION_GROWING;
8692 GfxDir[newx][newy] = diagonal_move_dir;
8693 ChangeDelay[newx][newy] = change_delay;
8695 graphic = el_act_dir2img(GfxElement[newx][newy],
8696 GfxAction[newx][newy], GfxDir[newx][newy]);
8698 DrawLevelGraphicAnimation(newx, newy, graphic);
8699 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8705 Tile[newx][newy] = EL_EMPTY;
8706 TEST_DrawLevelField(newx, newy);
8708 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8711 else if (!IS_FREE(newx, newy))
8716 else if (IS_CUSTOM_ELEMENT(element) &&
8717 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8719 if (!DigFieldByCE(newx, newy, element))
8722 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8724 RunnerVisit[x][y] = FrameCounter;
8725 PlayerVisit[x][y] /= 8; // expire player visit path
8728 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8730 if (!IS_FREE(newx, newy))
8732 if (IS_PLAYER(x, y))
8733 DrawPlayerField(x, y);
8735 TEST_DrawLevelField(x, y);
8741 boolean wanna_flame = !RND(10);
8742 int dx = newx - x, dy = newy - y;
8743 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8744 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8745 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8746 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8747 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8748 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8751 IS_CLASSIC_ENEMY(element1) ||
8752 IS_CLASSIC_ENEMY(element2)) &&
8753 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8754 element1 != EL_FLAMES && element2 != EL_FLAMES)
8756 ResetGfxAnimation(x, y);
8757 GfxAction[x][y] = ACTION_ATTACKING;
8759 if (IS_PLAYER(x, y))
8760 DrawPlayerField(x, y);
8762 TEST_DrawLevelField(x, y);
8764 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8766 MovDelay[x][y] = 50;
8768 Tile[newx][newy] = EL_FLAMES;
8769 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8770 Tile[newx1][newy1] = EL_FLAMES;
8771 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8772 Tile[newx2][newy2] = EL_FLAMES;
8778 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8779 Tile[newx][newy] == EL_DIAMOND)
8781 if (IS_MOVING(newx, newy))
8782 RemoveMovingField(newx, newy);
8785 Tile[newx][newy] = EL_EMPTY;
8786 TEST_DrawLevelField(newx, newy);
8789 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8791 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8792 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8794 if (AmoebaNr[newx][newy])
8796 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8797 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8798 Tile[newx][newy] == EL_BD_AMOEBA)
8799 AmoebaCnt[AmoebaNr[newx][newy]]--;
8802 if (IS_MOVING(newx, newy))
8804 RemoveMovingField(newx, newy);
8808 Tile[newx][newy] = EL_EMPTY;
8809 TEST_DrawLevelField(newx, newy);
8812 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8814 else if ((element == EL_PACMAN || element == EL_MOLE)
8815 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8817 if (AmoebaNr[newx][newy])
8819 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8820 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8821 Tile[newx][newy] == EL_BD_AMOEBA)
8822 AmoebaCnt[AmoebaNr[newx][newy]]--;
8825 if (element == EL_MOLE)
8827 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8828 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8830 ResetGfxAnimation(x, y);
8831 GfxAction[x][y] = ACTION_DIGGING;
8832 TEST_DrawLevelField(x, y);
8834 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8836 return; // wait for shrinking amoeba
8838 else // element == EL_PACMAN
8840 Tile[newx][newy] = EL_EMPTY;
8841 TEST_DrawLevelField(newx, newy);
8842 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8845 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8846 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8847 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8849 // wait for shrinking amoeba to completely disappear
8852 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8854 // object was running against a wall
8858 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8859 DrawLevelElementAnimation(x, y, element);
8861 if (DONT_TOUCH(element))
8862 TestIfBadThingTouchesPlayer(x, y);
8867 InitMovingField(x, y, MovDir[x][y]);
8869 PlayLevelSoundAction(x, y, ACTION_MOVING);
8873 ContinueMoving(x, y);
8876 void ContinueMoving(int x, int y)
8878 int element = Tile[x][y];
8879 struct ElementInfo *ei = &element_info[element];
8880 int direction = MovDir[x][y];
8881 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8882 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8883 int newx = x + dx, newy = y + dy;
8884 int stored = Store[x][y];
8885 int stored_new = Store[newx][newy];
8886 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8887 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8888 boolean last_line = (newy == lev_fieldy - 1);
8889 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8891 if (pushed_by_player) // special case: moving object pushed by player
8893 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos));
8895 else if (use_step_delay) // special case: moving object has step delay
8897 if (!MovDelay[x][y])
8898 MovPos[x][y] += getElementMoveStepsize(x, y);
8903 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8907 TEST_DrawLevelField(x, y);
8909 return; // element is still waiting
8912 else // normal case: generically moving object
8914 MovPos[x][y] += getElementMoveStepsize(x, y);
8917 if (ABS(MovPos[x][y]) < TILEX)
8919 TEST_DrawLevelField(x, y);
8921 return; // element is still moving
8924 // element reached destination field
8926 Tile[x][y] = EL_EMPTY;
8927 Tile[newx][newy] = element;
8928 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8930 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8932 element = Tile[newx][newy] = EL_ACID;
8934 else if (element == EL_MOLE)
8936 Tile[x][y] = EL_SAND;
8938 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8940 else if (element == EL_QUICKSAND_FILLING)
8942 element = Tile[newx][newy] = get_next_element(element);
8943 Store[newx][newy] = Store[x][y];
8945 else if (element == EL_QUICKSAND_EMPTYING)
8947 Tile[x][y] = get_next_element(element);
8948 element = Tile[newx][newy] = Store[x][y];
8950 else if (element == EL_QUICKSAND_FAST_FILLING)
8952 element = Tile[newx][newy] = get_next_element(element);
8953 Store[newx][newy] = Store[x][y];
8955 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8957 Tile[x][y] = get_next_element(element);
8958 element = Tile[newx][newy] = Store[x][y];
8960 else if (element == EL_MAGIC_WALL_FILLING)
8962 element = Tile[newx][newy] = get_next_element(element);
8963 if (!game.magic_wall_active)
8964 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8965 Store[newx][newy] = Store[x][y];
8967 else if (element == EL_MAGIC_WALL_EMPTYING)
8969 Tile[x][y] = get_next_element(element);
8970 if (!game.magic_wall_active)
8971 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8972 element = Tile[newx][newy] = Store[x][y];
8974 InitField(newx, newy, FALSE);
8976 else if (element == EL_BD_MAGIC_WALL_FILLING)
8978 element = Tile[newx][newy] = get_next_element(element);
8979 if (!game.magic_wall_active)
8980 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8981 Store[newx][newy] = Store[x][y];
8983 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8985 Tile[x][y] = get_next_element(element);
8986 if (!game.magic_wall_active)
8987 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8988 element = Tile[newx][newy] = Store[x][y];
8990 InitField(newx, newy, FALSE);
8992 else if (element == EL_DC_MAGIC_WALL_FILLING)
8994 element = Tile[newx][newy] = get_next_element(element);
8995 if (!game.magic_wall_active)
8996 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8997 Store[newx][newy] = Store[x][y];
8999 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
9001 Tile[x][y] = get_next_element(element);
9002 if (!game.magic_wall_active)
9003 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
9004 element = Tile[newx][newy] = Store[x][y];
9006 InitField(newx, newy, FALSE);
9008 else if (element == EL_AMOEBA_DROPPING)
9010 Tile[x][y] = get_next_element(element);
9011 element = Tile[newx][newy] = Store[x][y];
9013 else if (element == EL_SOKOBAN_OBJECT)
9016 Tile[x][y] = Back[x][y];
9018 if (Back[newx][newy])
9019 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
9021 Back[x][y] = Back[newx][newy] = 0;
9024 Store[x][y] = EL_EMPTY;
9029 MovDelay[newx][newy] = 0;
9031 if (CAN_CHANGE_OR_HAS_ACTION(element))
9033 // copy element change control values to new field
9034 ChangeDelay[newx][newy] = ChangeDelay[x][y];
9035 ChangePage[newx][newy] = ChangePage[x][y];
9036 ChangeCount[newx][newy] = ChangeCount[x][y];
9037 ChangeEvent[newx][newy] = ChangeEvent[x][y];
9040 CustomValue[newx][newy] = CustomValue[x][y];
9042 ChangeDelay[x][y] = 0;
9043 ChangePage[x][y] = -1;
9044 ChangeCount[x][y] = 0;
9045 ChangeEvent[x][y] = -1;
9047 CustomValue[x][y] = 0;
9049 // copy animation control values to new field
9050 GfxFrame[newx][newy] = GfxFrame[x][y];
9051 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
9052 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
9053 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
9055 Pushed[x][y] = Pushed[newx][newy] = FALSE;
9057 // some elements can leave other elements behind after moving
9058 if (ei->move_leave_element != EL_EMPTY &&
9059 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
9060 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
9062 int move_leave_element = ei->move_leave_element;
9064 // this makes it possible to leave the removed element again
9065 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
9066 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
9068 Tile[x][y] = move_leave_element;
9070 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
9071 MovDir[x][y] = direction;
9073 InitField(x, y, FALSE);
9075 if (GFX_CRUMBLED(Tile[x][y]))
9076 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
9078 if (IS_PLAYER_ELEMENT(move_leave_element))
9079 RelocatePlayer(x, y, move_leave_element);
9082 // do this after checking for left-behind element
9083 ResetGfxAnimation(x, y); // reset animation values for old field
9085 if (!CAN_MOVE(element) ||
9086 (CAN_FALL(element) && direction == MV_DOWN &&
9087 (element == EL_SPRING ||
9088 element_info[element].move_pattern == MV_WHEN_PUSHED ||
9089 element_info[element].move_pattern == MV_WHEN_DROPPED)))
9090 GfxDir[x][y] = MovDir[newx][newy] = 0;
9092 TEST_DrawLevelField(x, y);
9093 TEST_DrawLevelField(newx, newy);
9095 Stop[newx][newy] = TRUE; // ignore this element until the next frame
9097 // prevent pushed element from moving on in pushed direction
9098 if (pushed_by_player && CAN_MOVE(element) &&
9099 element_info[element].move_pattern & MV_ANY_DIRECTION &&
9100 !(element_info[element].move_pattern & direction))
9101 TurnRound(newx, newy);
9103 // prevent elements on conveyor belt from moving on in last direction
9104 if (pushed_by_conveyor && CAN_FALL(element) &&
9105 direction & MV_HORIZONTAL)
9106 MovDir[newx][newy] = 0;
9108 if (!pushed_by_player)
9110 int nextx = newx + dx, nexty = newy + dy;
9111 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
9113 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
9115 if (CAN_FALL(element) && direction == MV_DOWN)
9116 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
9118 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
9119 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
9121 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
9122 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
9125 if (DONT_TOUCH(element)) // object may be nasty to player or others
9127 TestIfBadThingTouchesPlayer(newx, newy);
9128 TestIfBadThingTouchesFriend(newx, newy);
9130 if (!IS_CUSTOM_ELEMENT(element))
9131 TestIfBadThingTouchesOtherBadThing(newx, newy);
9133 else if (element == EL_PENGUIN)
9134 TestIfFriendTouchesBadThing(newx, newy);
9136 if (DONT_GET_HIT_BY(element))
9138 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
9141 // give the player one last chance (one more frame) to move away
9142 if (CAN_FALL(element) && direction == MV_DOWN &&
9143 (last_line || (!IS_FREE(x, newy + 1) &&
9144 (!IS_PLAYER(x, newy + 1) ||
9145 game.engine_version < VERSION_IDENT(3,1,1,0)))))
9148 if (pushed_by_player && !game.use_change_when_pushing_bug)
9150 int push_side = MV_DIR_OPPOSITE(direction);
9151 struct PlayerInfo *player = PLAYERINFO(x, y);
9153 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
9154 player->index_bit, push_side);
9155 CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X,
9156 player->index_bit, push_side);
9159 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
9160 MovDelay[newx][newy] = 1;
9162 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
9164 TestIfElementTouchesCustomElement(x, y); // empty or new element
9165 TestIfElementHitsCustomElement(newx, newy, direction);
9166 TestIfPlayerTouchesCustomElement(newx, newy);
9167 TestIfElementTouchesCustomElement(newx, newy);
9169 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
9170 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
9171 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
9172 MV_DIR_OPPOSITE(direction));
9175 int AmoebaNeighbourNr(int ax, int ay)
9178 int element = Tile[ax][ay];
9180 struct XY *xy = xy_topdown;
9182 for (i = 0; i < NUM_DIRECTIONS; i++)
9184 int x = ax + xy[i].x;
9185 int y = ay + xy[i].y;
9187 if (!IN_LEV_FIELD(x, y))
9190 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
9191 group_nr = AmoebaNr[x][y];
9197 static void AmoebaMerge(int ax, int ay)
9199 int i, x, y, xx, yy;
9200 int new_group_nr = AmoebaNr[ax][ay];
9201 struct XY *xy = xy_topdown;
9203 if (new_group_nr == 0)
9206 for (i = 0; i < NUM_DIRECTIONS; i++)
9211 if (!IN_LEV_FIELD(x, y))
9214 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9215 Tile[x][y] == EL_BD_AMOEBA ||
9216 Tile[x][y] == EL_AMOEBA_DEAD) &&
9217 AmoebaNr[x][y] != new_group_nr)
9219 int old_group_nr = AmoebaNr[x][y];
9221 if (old_group_nr == 0)
9224 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9225 AmoebaCnt[old_group_nr] = 0;
9226 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9227 AmoebaCnt2[old_group_nr] = 0;
9229 SCAN_PLAYFIELD(xx, yy)
9231 if (AmoebaNr[xx][yy] == old_group_nr)
9232 AmoebaNr[xx][yy] = new_group_nr;
9238 void AmoebaToDiamond(int ax, int ay)
9242 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9244 int group_nr = AmoebaNr[ax][ay];
9249 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9250 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9256 SCAN_PLAYFIELD(x, y)
9258 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9261 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9265 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9266 SND_AMOEBA_TURNING_TO_GEM :
9267 SND_AMOEBA_TURNING_TO_ROCK));
9272 struct XY *xy = xy_topdown;
9274 for (i = 0; i < NUM_DIRECTIONS; i++)
9279 if (!IN_LEV_FIELD(x, y))
9282 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9284 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9285 SND_AMOEBA_TURNING_TO_GEM :
9286 SND_AMOEBA_TURNING_TO_ROCK));
9293 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9296 int group_nr = AmoebaNr[ax][ay];
9297 boolean done = FALSE;
9302 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9303 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9309 SCAN_PLAYFIELD(x, y)
9311 if (AmoebaNr[x][y] == group_nr &&
9312 (Tile[x][y] == EL_AMOEBA_DEAD ||
9313 Tile[x][y] == EL_BD_AMOEBA ||
9314 Tile[x][y] == EL_AMOEBA_GROWING))
9317 Tile[x][y] = new_element;
9318 InitField(x, y, FALSE);
9319 TEST_DrawLevelField(x, y);
9325 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9326 SND_BD_AMOEBA_TURNING_TO_ROCK :
9327 SND_BD_AMOEBA_TURNING_TO_GEM));
9330 static void AmoebaGrowing(int x, int y)
9332 static DelayCounter sound_delay = { 0 };
9334 if (!MovDelay[x][y]) // start new growing cycle
9338 if (DelayReached(&sound_delay))
9340 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9341 sound_delay.value = 30;
9345 if (MovDelay[x][y]) // wait some time before growing bigger
9348 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9350 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9351 6 - MovDelay[x][y]);
9353 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9356 if (!MovDelay[x][y])
9358 Tile[x][y] = Store[x][y];
9360 TEST_DrawLevelField(x, y);
9365 static void AmoebaShrinking(int x, int y)
9367 static DelayCounter sound_delay = { 0 };
9369 if (!MovDelay[x][y]) // start new shrinking cycle
9373 if (DelayReached(&sound_delay))
9374 sound_delay.value = 30;
9377 if (MovDelay[x][y]) // wait some time before shrinking
9380 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9382 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9383 6 - MovDelay[x][y]);
9385 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9388 if (!MovDelay[x][y])
9390 Tile[x][y] = EL_EMPTY;
9391 TEST_DrawLevelField(x, y);
9393 // don't let mole enter this field in this cycle;
9394 // (give priority to objects falling to this field from above)
9400 static void AmoebaReproduce(int ax, int ay)
9403 int element = Tile[ax][ay];
9404 int graphic = el2img(element);
9405 int newax = ax, neway = ay;
9406 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9407 struct XY *xy = xy_topdown;
9409 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9411 Tile[ax][ay] = EL_AMOEBA_DEAD;
9412 TEST_DrawLevelField(ax, ay);
9416 if (IS_ANIMATED(graphic))
9417 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9419 if (!MovDelay[ax][ay]) // start making new amoeba field
9420 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9422 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9425 if (MovDelay[ax][ay])
9429 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9432 int x = ax + xy[start].x;
9433 int y = ay + xy[start].y;
9435 if (!IN_LEV_FIELD(x, y))
9438 if (IS_FREE(x, y) ||
9439 CAN_GROW_INTO(Tile[x][y]) ||
9440 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9441 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9447 if (newax == ax && neway == ay)
9450 else // normal or "filled" (BD style) amoeba
9453 boolean waiting_for_player = FALSE;
9455 for (i = 0; i < NUM_DIRECTIONS; i++)
9457 int j = (start + i) % 4;
9458 int x = ax + xy[j].x;
9459 int y = ay + xy[j].y;
9461 if (!IN_LEV_FIELD(x, y))
9464 if (IS_FREE(x, y) ||
9465 CAN_GROW_INTO(Tile[x][y]) ||
9466 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9467 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9473 else if (IS_PLAYER(x, y))
9474 waiting_for_player = TRUE;
9477 if (newax == ax && neway == ay) // amoeba cannot grow
9479 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9481 Tile[ax][ay] = EL_AMOEBA_DEAD;
9482 TEST_DrawLevelField(ax, ay);
9483 AmoebaCnt[AmoebaNr[ax][ay]]--;
9485 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9487 if (element == EL_AMOEBA_FULL)
9488 AmoebaToDiamond(ax, ay);
9489 else if (element == EL_BD_AMOEBA)
9490 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9495 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9497 // amoeba gets larger by growing in some direction
9499 int new_group_nr = AmoebaNr[ax][ay];
9502 if (new_group_nr == 0)
9504 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9506 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9512 AmoebaNr[newax][neway] = new_group_nr;
9513 AmoebaCnt[new_group_nr]++;
9514 AmoebaCnt2[new_group_nr]++;
9516 // if amoeba touches other amoeba(s) after growing, unify them
9517 AmoebaMerge(newax, neway);
9519 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9521 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9527 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9528 (neway == lev_fieldy - 1 && newax != ax))
9530 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9531 Store[newax][neway] = element;
9533 else if (neway == ay || element == EL_EMC_DRIPPER)
9535 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9537 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9541 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9542 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9543 Store[ax][ay] = EL_AMOEBA_DROP;
9544 ContinueMoving(ax, ay);
9548 TEST_DrawLevelField(newax, neway);
9551 static void Life(int ax, int ay)
9555 int element = Tile[ax][ay];
9556 int graphic = el2img(element);
9557 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9559 boolean changed = FALSE;
9561 if (IS_ANIMATED(graphic))
9562 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9567 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9568 MovDelay[ax][ay] = life_time;
9570 if (MovDelay[ax][ay]) // wait some time before next cycle
9573 if (MovDelay[ax][ay])
9577 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9579 int xx = ax + x1, yy = ay + y1;
9580 int old_element = Tile[xx][yy];
9581 int num_neighbours = 0;
9583 if (!IN_LEV_FIELD(xx, yy))
9586 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9588 int x = xx + x2, y = yy + y2;
9590 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9593 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9594 boolean is_neighbour = FALSE;
9596 if (level.use_life_bugs)
9598 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9599 (IS_FREE(x, y) && Stop[x][y]));
9602 (Last[x][y] == element || is_player_cell);
9608 boolean is_free = FALSE;
9610 if (level.use_life_bugs)
9611 is_free = (IS_FREE(xx, yy));
9613 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9615 if (xx == ax && yy == ay) // field in the middle
9617 if (num_neighbours < life_parameter[0] ||
9618 num_neighbours > life_parameter[1])
9620 Tile[xx][yy] = EL_EMPTY;
9621 if (Tile[xx][yy] != old_element)
9622 TEST_DrawLevelField(xx, yy);
9623 Stop[xx][yy] = TRUE;
9627 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9628 { // free border field
9629 if (num_neighbours >= life_parameter[2] &&
9630 num_neighbours <= life_parameter[3])
9632 Tile[xx][yy] = element;
9633 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9634 if (Tile[xx][yy] != old_element)
9635 TEST_DrawLevelField(xx, yy);
9636 Stop[xx][yy] = TRUE;
9643 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9644 SND_GAME_OF_LIFE_GROWING);
9647 static void InitRobotWheel(int x, int y)
9649 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9652 static void RunRobotWheel(int x, int y)
9654 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9657 static void StopRobotWheel(int x, int y)
9659 if (game.robot_wheel_x == x &&
9660 game.robot_wheel_y == y)
9662 game.robot_wheel_x = -1;
9663 game.robot_wheel_y = -1;
9664 game.robot_wheel_active = FALSE;
9668 static void InitTimegateWheel(int x, int y)
9670 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9673 static void RunTimegateWheel(int x, int y)
9675 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9678 static void InitMagicBallDelay(int x, int y)
9680 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9683 static void ActivateMagicBall(int bx, int by)
9687 if (level.ball_random)
9689 int pos_border = RND(8); // select one of the eight border elements
9690 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9691 int xx = pos_content % 3;
9692 int yy = pos_content / 3;
9697 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9698 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9702 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9704 int xx = x - bx + 1;
9705 int yy = y - by + 1;
9707 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9708 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9712 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9715 static void CheckExit(int x, int y)
9717 if (game.gems_still_needed > 0 ||
9718 game.sokoban_fields_still_needed > 0 ||
9719 game.sokoban_objects_still_needed > 0 ||
9720 game.lights_still_needed > 0)
9722 int element = Tile[x][y];
9723 int graphic = el2img(element);
9725 if (IS_ANIMATED(graphic))
9726 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9731 // do not re-open exit door closed after last player
9732 if (game.all_players_gone)
9735 Tile[x][y] = EL_EXIT_OPENING;
9737 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9740 static void CheckExitEM(int x, int y)
9742 if (game.gems_still_needed > 0 ||
9743 game.sokoban_fields_still_needed > 0 ||
9744 game.sokoban_objects_still_needed > 0 ||
9745 game.lights_still_needed > 0)
9747 int element = Tile[x][y];
9748 int graphic = el2img(element);
9750 if (IS_ANIMATED(graphic))
9751 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9756 // do not re-open exit door closed after last player
9757 if (game.all_players_gone)
9760 Tile[x][y] = EL_EM_EXIT_OPENING;
9762 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9765 static void CheckExitSteel(int x, int y)
9767 if (game.gems_still_needed > 0 ||
9768 game.sokoban_fields_still_needed > 0 ||
9769 game.sokoban_objects_still_needed > 0 ||
9770 game.lights_still_needed > 0)
9772 int element = Tile[x][y];
9773 int graphic = el2img(element);
9775 if (IS_ANIMATED(graphic))
9776 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9781 // do not re-open exit door closed after last player
9782 if (game.all_players_gone)
9785 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9787 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9790 static void CheckExitSteelEM(int x, int y)
9792 if (game.gems_still_needed > 0 ||
9793 game.sokoban_fields_still_needed > 0 ||
9794 game.sokoban_objects_still_needed > 0 ||
9795 game.lights_still_needed > 0)
9797 int element = Tile[x][y];
9798 int graphic = el2img(element);
9800 if (IS_ANIMATED(graphic))
9801 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9806 // do not re-open exit door closed after last player
9807 if (game.all_players_gone)
9810 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9812 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9815 static void CheckExitSP(int x, int y)
9817 if (game.gems_still_needed > 0)
9819 int element = Tile[x][y];
9820 int graphic = el2img(element);
9822 if (IS_ANIMATED(graphic))
9823 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9828 // do not re-open exit door closed after last player
9829 if (game.all_players_gone)
9832 Tile[x][y] = EL_SP_EXIT_OPENING;
9834 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9837 static void CloseAllOpenTimegates(void)
9841 SCAN_PLAYFIELD(x, y)
9843 int element = Tile[x][y];
9845 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9847 Tile[x][y] = EL_TIMEGATE_CLOSING;
9849 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9854 static void DrawTwinkleOnField(int x, int y)
9856 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9859 if (Tile[x][y] == EL_BD_DIAMOND)
9862 if (MovDelay[x][y] == 0) // next animation frame
9863 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9865 if (MovDelay[x][y] != 0) // wait some time before next frame
9869 DrawLevelElementAnimation(x, y, Tile[x][y]);
9871 if (MovDelay[x][y] != 0)
9873 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9874 10 - MovDelay[x][y]);
9876 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9881 static void WallGrowing(int x, int y)
9885 if (!MovDelay[x][y]) // next animation frame
9886 MovDelay[x][y] = 3 * delay;
9888 if (MovDelay[x][y]) // wait some time before next frame
9892 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9894 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9895 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9897 DrawLevelGraphic(x, y, graphic, frame);
9900 if (!MovDelay[x][y])
9902 if (MovDir[x][y] == MV_LEFT)
9904 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9905 TEST_DrawLevelField(x - 1, y);
9907 else if (MovDir[x][y] == MV_RIGHT)
9909 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9910 TEST_DrawLevelField(x + 1, y);
9912 else if (MovDir[x][y] == MV_UP)
9914 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9915 TEST_DrawLevelField(x, y - 1);
9919 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9920 TEST_DrawLevelField(x, y + 1);
9923 Tile[x][y] = Store[x][y];
9925 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9926 TEST_DrawLevelField(x, y);
9931 static void CheckWallGrowing(int ax, int ay)
9933 int element = Tile[ax][ay];
9934 int graphic = el2img(element);
9935 boolean free_top = FALSE;
9936 boolean free_bottom = FALSE;
9937 boolean free_left = FALSE;
9938 boolean free_right = FALSE;
9939 boolean stop_top = FALSE;
9940 boolean stop_bottom = FALSE;
9941 boolean stop_left = FALSE;
9942 boolean stop_right = FALSE;
9943 boolean new_wall = FALSE;
9945 boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9946 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9947 element == EL_EXPANDABLE_STEELWALL_ANY);
9949 boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9950 element == EL_EXPANDABLE_WALL_ANY ||
9951 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9952 element == EL_EXPANDABLE_STEELWALL_ANY);
9954 boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9955 element == EL_EXPANDABLE_WALL_ANY ||
9956 element == EL_EXPANDABLE_WALL ||
9957 element == EL_BD_EXPANDABLE_WALL ||
9958 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9959 element == EL_EXPANDABLE_STEELWALL_ANY);
9961 boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9962 element == EL_EXPANDABLE_STEELWALL_VERTICAL);
9964 boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9965 element == EL_EXPANDABLE_WALL ||
9966 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL);
9968 int wall_growing = (is_steelwall ?
9969 EL_EXPANDABLE_STEELWALL_GROWING :
9970 EL_EXPANDABLE_WALL_GROWING);
9972 int gfx_wall_growing_up = (is_steelwall ?
9973 IMG_EXPANDABLE_STEELWALL_GROWING_UP :
9974 IMG_EXPANDABLE_WALL_GROWING_UP);
9975 int gfx_wall_growing_down = (is_steelwall ?
9976 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN :
9977 IMG_EXPANDABLE_WALL_GROWING_DOWN);
9978 int gfx_wall_growing_left = (is_steelwall ?
9979 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT :
9980 IMG_EXPANDABLE_WALL_GROWING_LEFT);
9981 int gfx_wall_growing_right = (is_steelwall ?
9982 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT :
9983 IMG_EXPANDABLE_WALL_GROWING_RIGHT);
9985 if (IS_ANIMATED(graphic))
9986 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9988 if (!MovDelay[ax][ay]) // start building new wall
9989 MovDelay[ax][ay] = 6;
9991 if (MovDelay[ax][ay]) // wait some time before building new wall
9994 if (MovDelay[ax][ay])
9998 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
10000 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
10001 free_bottom = TRUE;
10002 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
10004 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
10011 Tile[ax][ay - 1] = wall_growing;
10012 Store[ax][ay - 1] = element;
10013 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
10015 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
10016 DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0);
10023 Tile[ax][ay + 1] = wall_growing;
10024 Store[ax][ay + 1] = element;
10025 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
10027 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
10028 DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0);
10034 if (grow_horizontal)
10038 Tile[ax - 1][ay] = wall_growing;
10039 Store[ax - 1][ay] = element;
10040 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
10042 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
10043 DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0);
10050 Tile[ax + 1][ay] = wall_growing;
10051 Store[ax + 1][ay] = element;
10052 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
10054 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
10055 DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0);
10061 if (element == EL_EXPANDABLE_WALL && (free_left || free_right))
10062 TEST_DrawLevelField(ax, ay);
10064 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
10066 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
10067 stop_bottom = TRUE;
10068 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
10070 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
10073 if (((stop_top && stop_bottom) || stop_horizontal) &&
10074 ((stop_left && stop_right) || stop_vertical))
10075 Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL);
10078 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
10081 static void CheckForDragon(int x, int y)
10084 boolean dragon_found = FALSE;
10085 struct XY *xy = xy_topdown;
10087 for (i = 0; i < NUM_DIRECTIONS; i++)
10089 for (j = 0; j < 4; j++)
10091 int xx = x + j * xy[i].x;
10092 int yy = y + j * xy[i].y;
10094 if (IN_LEV_FIELD(xx, yy) &&
10095 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
10097 if (Tile[xx][yy] == EL_DRAGON)
10098 dragon_found = TRUE;
10107 for (i = 0; i < NUM_DIRECTIONS; i++)
10109 for (j = 0; j < 3; j++)
10111 int xx = x + j * xy[i].x;
10112 int yy = y + j * xy[i].y;
10114 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
10116 Tile[xx][yy] = EL_EMPTY;
10117 TEST_DrawLevelField(xx, yy);
10126 static void InitBuggyBase(int x, int y)
10128 int element = Tile[x][y];
10129 int activating_delay = FRAMES_PER_SECOND / 4;
10131 ChangeDelay[x][y] =
10132 (element == EL_SP_BUGGY_BASE ?
10133 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10134 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10136 element == EL_SP_BUGGY_BASE_ACTIVE ?
10137 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10140 static void WarnBuggyBase(int x, int y)
10143 struct XY *xy = xy_topdown;
10145 for (i = 0; i < NUM_DIRECTIONS; i++)
10147 int xx = x + xy[i].x;
10148 int yy = y + xy[i].y;
10150 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10152 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10159 static void InitTrap(int x, int y)
10161 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10164 static void ActivateTrap(int x, int y)
10166 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10169 static void ChangeActiveTrap(int x, int y)
10171 int graphic = IMG_TRAP_ACTIVE;
10173 // if new animation frame was drawn, correct crumbled sand border
10174 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10175 TEST_DrawLevelFieldCrumbled(x, y);
10178 static int getSpecialActionElement(int element, int number, int base_element)
10180 return (element != EL_EMPTY ? element :
10181 number != -1 ? base_element + number - 1 :
10185 static int getModifiedActionNumber(int value_old, int operator, int operand,
10186 int value_min, int value_max)
10188 int value_new = (operator == CA_MODE_SET ? operand :
10189 operator == CA_MODE_ADD ? value_old + operand :
10190 operator == CA_MODE_SUBTRACT ? value_old - operand :
10191 operator == CA_MODE_MULTIPLY ? value_old * operand :
10192 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10193 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10196 return (value_new < value_min ? value_min :
10197 value_new > value_max ? value_max :
10201 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10203 struct ElementInfo *ei = &element_info[element];
10204 struct ElementChangeInfo *change = &ei->change_page[page];
10205 int target_element = change->target_element;
10206 int action_type = change->action_type;
10207 int action_mode = change->action_mode;
10208 int action_arg = change->action_arg;
10209 int action_element = change->action_element;
10212 if (!change->has_action)
10215 // ---------- determine action paramater values -----------------------------
10217 int level_time_value =
10218 (level.time > 0 ? TimeLeft :
10221 int action_arg_element_raw =
10222 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10223 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10224 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10225 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10226 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10227 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10228 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10230 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10232 int action_arg_direction =
10233 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10234 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10235 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10236 change->actual_trigger_side :
10237 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10238 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10241 int action_arg_number_min =
10242 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10245 int action_arg_number_max =
10246 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10247 action_type == CA_SET_LEVEL_GEMS ? 999 :
10248 action_type == CA_SET_LEVEL_TIME ? 9999 :
10249 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10250 action_type == CA_SET_CE_VALUE ? 9999 :
10251 action_type == CA_SET_CE_SCORE ? 9999 :
10254 int action_arg_number_reset =
10255 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10256 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10257 action_type == CA_SET_LEVEL_TIME ? level.time :
10258 action_type == CA_SET_LEVEL_SCORE ? 0 :
10259 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10260 action_type == CA_SET_CE_SCORE ? 0 :
10263 int action_arg_number =
10264 (action_arg <= CA_ARG_MAX ? action_arg :
10265 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10266 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10267 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10268 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10269 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10270 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10271 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10272 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10273 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10274 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10275 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10276 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10277 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10278 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10279 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10280 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10281 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10282 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10283 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10284 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10285 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10288 int action_arg_number_old =
10289 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10290 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10291 action_type == CA_SET_LEVEL_SCORE ? game.score :
10292 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10293 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10296 int action_arg_number_new =
10297 getModifiedActionNumber(action_arg_number_old,
10298 action_mode, action_arg_number,
10299 action_arg_number_min, action_arg_number_max);
10301 int trigger_player_bits =
10302 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10303 change->actual_trigger_player_bits : change->trigger_player);
10305 int action_arg_player_bits =
10306 (action_arg >= CA_ARG_PLAYER_1 &&
10307 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10308 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10309 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10312 // ---------- execute action -----------------------------------------------
10314 switch (action_type)
10321 // ---------- level actions ----------------------------------------------
10323 case CA_RESTART_LEVEL:
10325 game.restart_level = TRUE;
10330 case CA_SHOW_ENVELOPE:
10332 int element = getSpecialActionElement(action_arg_element,
10333 action_arg_number, EL_ENVELOPE_1);
10335 if (IS_ENVELOPE(element))
10336 local_player->show_envelope = element;
10341 case CA_SET_LEVEL_TIME:
10343 if (level.time > 0) // only modify limited time value
10345 TimeLeft = action_arg_number_new;
10347 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10349 DisplayGameControlValues();
10351 if (!TimeLeft && game.time_limit)
10352 for (i = 0; i < MAX_PLAYERS; i++)
10353 KillPlayer(&stored_player[i]);
10359 case CA_SET_LEVEL_SCORE:
10361 game.score = action_arg_number_new;
10363 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10365 DisplayGameControlValues();
10370 case CA_SET_LEVEL_GEMS:
10372 game.gems_still_needed = action_arg_number_new;
10374 game.snapshot.collected_item = TRUE;
10376 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10378 DisplayGameControlValues();
10383 case CA_SET_LEVEL_WIND:
10385 game.wind_direction = action_arg_direction;
10390 case CA_SET_LEVEL_RANDOM_SEED:
10392 // ensure that setting a new random seed while playing is predictable
10393 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10398 // ---------- player actions ---------------------------------------------
10400 case CA_MOVE_PLAYER:
10401 case CA_MOVE_PLAYER_NEW:
10403 // automatically move to the next field in specified direction
10404 for (i = 0; i < MAX_PLAYERS; i++)
10405 if (trigger_player_bits & (1 << i))
10406 if (action_type == CA_MOVE_PLAYER ||
10407 stored_player[i].MovPos == 0)
10408 stored_player[i].programmed_action = action_arg_direction;
10413 case CA_EXIT_PLAYER:
10415 for (i = 0; i < MAX_PLAYERS; i++)
10416 if (action_arg_player_bits & (1 << i))
10417 ExitPlayer(&stored_player[i]);
10419 if (game.players_still_needed == 0)
10425 case CA_KILL_PLAYER:
10427 for (i = 0; i < MAX_PLAYERS; i++)
10428 if (action_arg_player_bits & (1 << i))
10429 KillPlayer(&stored_player[i]);
10434 case CA_SET_PLAYER_KEYS:
10436 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10437 int element = getSpecialActionElement(action_arg_element,
10438 action_arg_number, EL_KEY_1);
10440 if (IS_KEY(element))
10442 for (i = 0; i < MAX_PLAYERS; i++)
10444 if (trigger_player_bits & (1 << i))
10446 stored_player[i].key[KEY_NR(element)] = key_state;
10448 DrawGameDoorValues();
10456 case CA_SET_PLAYER_SPEED:
10458 for (i = 0; i < MAX_PLAYERS; i++)
10460 if (trigger_player_bits & (1 << i))
10462 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10464 if (action_arg == CA_ARG_SPEED_FASTER &&
10465 stored_player[i].cannot_move)
10467 action_arg_number = STEPSIZE_VERY_SLOW;
10469 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10470 action_arg == CA_ARG_SPEED_FASTER)
10472 action_arg_number = 2;
10473 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10476 else if (action_arg == CA_ARG_NUMBER_RESET)
10478 action_arg_number = level.initial_player_stepsize[i];
10482 getModifiedActionNumber(move_stepsize,
10485 action_arg_number_min,
10486 action_arg_number_max);
10488 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10495 case CA_SET_PLAYER_SHIELD:
10497 for (i = 0; i < MAX_PLAYERS; i++)
10499 if (trigger_player_bits & (1 << i))
10501 if (action_arg == CA_ARG_SHIELD_OFF)
10503 stored_player[i].shield_normal_time_left = 0;
10504 stored_player[i].shield_deadly_time_left = 0;
10506 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10508 stored_player[i].shield_normal_time_left = 999999;
10510 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10512 stored_player[i].shield_normal_time_left = 999999;
10513 stored_player[i].shield_deadly_time_left = 999999;
10521 case CA_SET_PLAYER_GRAVITY:
10523 for (i = 0; i < MAX_PLAYERS; i++)
10525 if (trigger_player_bits & (1 << i))
10527 stored_player[i].gravity =
10528 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10529 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10530 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10531 stored_player[i].gravity);
10538 case CA_SET_PLAYER_ARTWORK:
10540 for (i = 0; i < MAX_PLAYERS; i++)
10542 if (trigger_player_bits & (1 << i))
10544 int artwork_element = action_arg_element;
10546 if (action_arg == CA_ARG_ELEMENT_RESET)
10548 (level.use_artwork_element[i] ? level.artwork_element[i] :
10549 stored_player[i].element_nr);
10551 if (stored_player[i].artwork_element != artwork_element)
10552 stored_player[i].Frame = 0;
10554 stored_player[i].artwork_element = artwork_element;
10556 SetPlayerWaiting(&stored_player[i], FALSE);
10558 // set number of special actions for bored and sleeping animation
10559 stored_player[i].num_special_action_bored =
10560 get_num_special_action(artwork_element,
10561 ACTION_BORING_1, ACTION_BORING_LAST);
10562 stored_player[i].num_special_action_sleeping =
10563 get_num_special_action(artwork_element,
10564 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10571 case CA_SET_PLAYER_INVENTORY:
10573 for (i = 0; i < MAX_PLAYERS; i++)
10575 struct PlayerInfo *player = &stored_player[i];
10578 if (trigger_player_bits & (1 << i))
10580 int inventory_element = action_arg_element;
10582 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10583 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10584 action_arg == CA_ARG_ELEMENT_ACTION)
10586 int element = inventory_element;
10587 int collect_count = element_info[element].collect_count_initial;
10589 if (!IS_CUSTOM_ELEMENT(element))
10592 if (collect_count == 0)
10593 player->inventory_infinite_element = element;
10595 for (k = 0; k < collect_count; k++)
10596 if (player->inventory_size < MAX_INVENTORY_SIZE)
10597 player->inventory_element[player->inventory_size++] =
10600 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10601 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10602 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10604 if (player->inventory_infinite_element != EL_UNDEFINED &&
10605 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10606 action_arg_element_raw))
10607 player->inventory_infinite_element = EL_UNDEFINED;
10609 for (k = 0, j = 0; j < player->inventory_size; j++)
10611 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10612 action_arg_element_raw))
10613 player->inventory_element[k++] = player->inventory_element[j];
10616 player->inventory_size = k;
10618 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10620 if (player->inventory_size > 0)
10622 for (j = 0; j < player->inventory_size - 1; j++)
10623 player->inventory_element[j] = player->inventory_element[j + 1];
10625 player->inventory_size--;
10628 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10630 if (player->inventory_size > 0)
10631 player->inventory_size--;
10633 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10635 player->inventory_infinite_element = EL_UNDEFINED;
10636 player->inventory_size = 0;
10638 else if (action_arg == CA_ARG_INVENTORY_RESET)
10640 player->inventory_infinite_element = EL_UNDEFINED;
10641 player->inventory_size = 0;
10643 if (level.use_initial_inventory[i])
10645 for (j = 0; j < level.initial_inventory_size[i]; j++)
10647 int element = level.initial_inventory_content[i][j];
10648 int collect_count = element_info[element].collect_count_initial;
10650 if (!IS_CUSTOM_ELEMENT(element))
10653 if (collect_count == 0)
10654 player->inventory_infinite_element = element;
10656 for (k = 0; k < collect_count; k++)
10657 if (player->inventory_size < MAX_INVENTORY_SIZE)
10658 player->inventory_element[player->inventory_size++] =
10669 // ---------- CE actions -------------------------------------------------
10671 case CA_SET_CE_VALUE:
10673 int last_ce_value = CustomValue[x][y];
10675 CustomValue[x][y] = action_arg_number_new;
10677 if (CustomValue[x][y] != last_ce_value)
10679 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10680 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10682 if (CustomValue[x][y] == 0)
10684 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10685 ChangeCount[x][y] = 0; // allow at least one more change
10687 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10688 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10695 case CA_SET_CE_SCORE:
10697 int last_ce_score = ei->collect_score;
10699 ei->collect_score = action_arg_number_new;
10701 if (ei->collect_score != last_ce_score)
10703 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10704 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10706 if (ei->collect_score == 0)
10710 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10711 ChangeCount[x][y] = 0; // allow at least one more change
10713 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10714 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10717 This is a very special case that seems to be a mixture between
10718 CheckElementChange() and CheckTriggeredElementChange(): while
10719 the first one only affects single elements that are triggered
10720 directly, the second one affects multiple elements in the playfield
10721 that are triggered indirectly by another element. This is a third
10722 case: Changing the CE score always affects multiple identical CEs,
10723 so every affected CE must be checked, not only the single CE for
10724 which the CE score was changed in the first place (as every instance
10725 of that CE shares the same CE score, and therefore also can change)!
10727 SCAN_PLAYFIELD(xx, yy)
10729 if (Tile[xx][yy] == element)
10730 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10731 CE_SCORE_GETS_ZERO);
10739 case CA_SET_CE_ARTWORK:
10741 int artwork_element = action_arg_element;
10742 boolean reset_frame = FALSE;
10745 if (action_arg == CA_ARG_ELEMENT_RESET)
10746 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10749 if (ei->gfx_element != artwork_element)
10750 reset_frame = TRUE;
10752 ei->gfx_element = artwork_element;
10754 SCAN_PLAYFIELD(xx, yy)
10756 if (Tile[xx][yy] == element)
10760 ResetGfxAnimation(xx, yy);
10761 ResetRandomAnimationValue(xx, yy);
10764 TEST_DrawLevelField(xx, yy);
10771 // ---------- engine actions ---------------------------------------------
10773 case CA_SET_ENGINE_SCAN_MODE:
10775 InitPlayfieldScanMode(action_arg);
10785 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10787 int old_element = Tile[x][y];
10788 int new_element = GetElementFromGroupElement(element);
10789 int previous_move_direction = MovDir[x][y];
10790 int last_ce_value = CustomValue[x][y];
10791 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10792 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10793 boolean add_player_onto_element = (new_element_is_player &&
10794 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10795 IS_WALKABLE(old_element));
10797 if (!add_player_onto_element)
10799 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10800 RemoveMovingField(x, y);
10804 Tile[x][y] = new_element;
10806 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10807 MovDir[x][y] = previous_move_direction;
10809 if (element_info[new_element].use_last_ce_value)
10810 CustomValue[x][y] = last_ce_value;
10812 InitField_WithBug1(x, y, FALSE);
10814 new_element = Tile[x][y]; // element may have changed
10816 ResetGfxAnimation(x, y);
10817 ResetRandomAnimationValue(x, y);
10819 TEST_DrawLevelField(x, y);
10821 if (GFX_CRUMBLED(new_element))
10822 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10824 if (old_element == EL_EXPLOSION)
10826 Store[x][y] = Store2[x][y] = 0;
10828 // check if new element replaces an exploding player, requiring cleanup
10829 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
10830 StorePlayer[x][y] = 0;
10833 // check if element under the player changes from accessible to unaccessible
10834 // (needed for special case of dropping element which then changes)
10835 // (must be checked after creating new element for walkable group elements)
10836 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10837 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10839 KillPlayer(PLAYERINFO(x, y));
10845 // "ChangeCount" not set yet to allow "entered by player" change one time
10846 if (new_element_is_player)
10847 RelocatePlayer(x, y, new_element);
10850 ChangeCount[x][y]++; // count number of changes in the same frame
10852 TestIfBadThingTouchesPlayer(x, y);
10853 TestIfPlayerTouchesCustomElement(x, y);
10854 TestIfElementTouchesCustomElement(x, y);
10857 static void CreateField(int x, int y, int element)
10859 CreateFieldExt(x, y, element, FALSE);
10862 static void CreateElementFromChange(int x, int y, int element)
10864 element = GET_VALID_RUNTIME_ELEMENT(element);
10866 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10868 int old_element = Tile[x][y];
10870 // prevent changed element from moving in same engine frame
10871 // unless both old and new element can either fall or move
10872 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10873 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10877 CreateFieldExt(x, y, element, TRUE);
10880 static boolean ChangeElement(int x, int y, int element, int page)
10882 struct ElementInfo *ei = &element_info[element];
10883 struct ElementChangeInfo *change = &ei->change_page[page];
10884 int ce_value = CustomValue[x][y];
10885 int ce_score = ei->collect_score;
10886 int target_element;
10887 int old_element = Tile[x][y];
10889 // always use default change event to prevent running into a loop
10890 if (ChangeEvent[x][y] == -1)
10891 ChangeEvent[x][y] = CE_DELAY;
10893 if (ChangeEvent[x][y] == CE_DELAY)
10895 // reset actual trigger element, trigger player and action element
10896 change->actual_trigger_element = EL_EMPTY;
10897 change->actual_trigger_player = EL_EMPTY;
10898 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10899 change->actual_trigger_side = CH_SIDE_NONE;
10900 change->actual_trigger_ce_value = 0;
10901 change->actual_trigger_ce_score = 0;
10902 change->actual_trigger_x = -1;
10903 change->actual_trigger_y = -1;
10906 // do not change elements more than a specified maximum number of changes
10907 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10910 ChangeCount[x][y]++; // count number of changes in the same frame
10912 if (ei->has_anim_event)
10913 HandleGlobalAnimEventByElementChange(element, page, x, y,
10914 change->actual_trigger_x,
10915 change->actual_trigger_y);
10917 if (change->explode)
10924 if (change->use_target_content)
10926 boolean complete_replace = TRUE;
10927 boolean can_replace[3][3];
10930 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10933 boolean is_walkable;
10934 boolean is_diggable;
10935 boolean is_collectible;
10936 boolean is_removable;
10937 boolean is_destructible;
10938 int ex = x + xx - 1;
10939 int ey = y + yy - 1;
10940 int content_element = change->target_content.e[xx][yy];
10943 can_replace[xx][yy] = TRUE;
10945 if (ex == x && ey == y) // do not check changing element itself
10948 if (content_element == EL_EMPTY_SPACE)
10950 can_replace[xx][yy] = FALSE; // do not replace border with space
10955 if (!IN_LEV_FIELD(ex, ey))
10957 can_replace[xx][yy] = FALSE;
10958 complete_replace = FALSE;
10965 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10966 e = MovingOrBlocked2Element(ex, ey);
10968 is_empty = (IS_FREE(ex, ey) ||
10969 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10971 is_walkable = (is_empty || IS_WALKABLE(e));
10972 is_diggable = (is_empty || IS_DIGGABLE(e));
10973 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10974 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10975 is_removable = (is_diggable || is_collectible);
10977 can_replace[xx][yy] =
10978 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10979 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10980 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10981 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10982 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10983 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10984 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10986 if (!can_replace[xx][yy])
10987 complete_replace = FALSE;
10990 if (!change->only_if_complete || complete_replace)
10992 boolean something_has_changed = FALSE;
10994 if (change->only_if_complete && change->use_random_replace &&
10995 RND(100) < change->random_percentage)
10998 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
11000 int ex = x + xx - 1;
11001 int ey = y + yy - 1;
11002 int content_element;
11004 if (can_replace[xx][yy] && (!change->use_random_replace ||
11005 RND(100) < change->random_percentage))
11007 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
11008 RemoveMovingField(ex, ey);
11010 ChangeEvent[ex][ey] = ChangeEvent[x][y];
11012 content_element = change->target_content.e[xx][yy];
11013 target_element = GET_TARGET_ELEMENT(element, content_element, change,
11014 ce_value, ce_score);
11016 CreateElementFromChange(ex, ey, target_element);
11018 something_has_changed = TRUE;
11020 // for symmetry reasons, freeze newly created border elements
11021 if (ex != x || ey != y)
11022 Stop[ex][ey] = TRUE; // no more moving in this frame
11026 if (something_has_changed)
11028 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
11029 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
11035 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
11036 ce_value, ce_score);
11038 if (element == EL_DIAGONAL_GROWING ||
11039 element == EL_DIAGONAL_SHRINKING)
11041 target_element = Store[x][y];
11043 Store[x][y] = EL_EMPTY;
11046 // special case: element changes to player (and may be kept if walkable)
11047 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
11048 CreateElementFromChange(x, y, EL_EMPTY);
11050 CreateElementFromChange(x, y, target_element);
11052 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
11053 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
11056 // this uses direct change before indirect change
11057 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
11062 static void HandleElementChange(int x, int y, int page)
11064 int element = MovingOrBlocked2Element(x, y);
11065 struct ElementInfo *ei = &element_info[element];
11066 struct ElementChangeInfo *change = &ei->change_page[page];
11067 boolean handle_action_before_change = FALSE;
11070 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
11071 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
11073 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
11074 x, y, element, element_info[element].token_name);
11075 Debug("game:playing:HandleElementChange", "This should never happen!");
11079 // this can happen with classic bombs on walkable, changing elements
11080 if (!CAN_CHANGE_OR_HAS_ACTION(element))
11085 if (ChangeDelay[x][y] == 0) // initialize element change
11087 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
11089 if (change->can_change)
11091 // !!! not clear why graphic animation should be reset at all here !!!
11092 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
11093 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
11096 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
11098 When using an animation frame delay of 1 (this only happens with
11099 "sp_zonk.moving.left/right" in the classic graphics), the default
11100 (non-moving) animation shows wrong animation frames (while the
11101 moving animation, like "sp_zonk.moving.left/right", is correct,
11102 so this graphical bug never shows up with the classic graphics).
11103 For an animation with 4 frames, this causes wrong frames 0,0,1,2
11104 be drawn instead of the correct frames 0,1,2,3. This is caused by
11105 "GfxFrame[][]" being reset *twice* (in two successive frames) after
11106 an element change: First when the change delay ("ChangeDelay[][]")
11107 counter has reached zero after decrementing, then a second time in
11108 the next frame (after "GfxFrame[][]" was already incremented) when
11109 "ChangeDelay[][]" is reset to the initial delay value again.
11111 This causes frame 0 to be drawn twice, while the last frame won't
11112 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
11114 As some animations may already be cleverly designed around this bug
11115 (at least the "Snake Bite" snake tail animation does this), it cannot
11116 simply be fixed here without breaking such existing animations.
11117 Unfortunately, it cannot easily be detected if a graphics set was
11118 designed "before" or "after" the bug was fixed. As a workaround,
11119 a new graphics set option "game.graphics_engine_version" was added
11120 to be able to specify the game's major release version for which the
11121 graphics set was designed, which can then be used to decide if the
11122 bugfix should be used (version 4 and above) or not (version 3 or
11123 below, or if no version was specified at all, as with old sets).
11125 (The wrong/fixed animation frames can be tested with the test level set
11126 "test_gfxframe" and level "000", which contains a specially prepared
11127 custom element at level position (x/y) == (11/9) which uses the zonk
11128 animation mentioned above. Using "game.graphics_engine_version: 4"
11129 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
11130 This can also be seen from the debug output for this test element.)
11133 // when a custom element is about to change (for example by change delay),
11134 // do not reset graphic animation when the custom element is moving
11135 if (game.graphics_engine_version < 4 &&
11138 ResetGfxAnimation(x, y);
11139 ResetRandomAnimationValue(x, y);
11142 if (change->pre_change_function)
11143 change->pre_change_function(x, y);
11147 ChangeDelay[x][y]--;
11149 if (ChangeDelay[x][y] != 0) // continue element change
11151 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11153 // also needed if CE can not change, but has CE delay with CE action
11154 if (IS_ANIMATED(graphic))
11155 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11157 if (change->can_change)
11159 if (change->change_function)
11160 change->change_function(x, y);
11163 else // finish element change
11165 if (ChangePage[x][y] != -1) // remember page from delayed change
11167 page = ChangePage[x][y];
11168 ChangePage[x][y] = -1;
11170 change = &ei->change_page[page];
11173 if (IS_MOVING(x, y)) // never change a running system ;-)
11175 ChangeDelay[x][y] = 1; // try change after next move step
11176 ChangePage[x][y] = page; // remember page to use for change
11181 // special case: set new level random seed before changing element
11182 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11183 handle_action_before_change = TRUE;
11185 if (change->has_action && handle_action_before_change)
11186 ExecuteCustomElementAction(x, y, element, page);
11188 if (change->can_change)
11190 if (ChangeElement(x, y, element, page))
11192 if (change->post_change_function)
11193 change->post_change_function(x, y);
11197 if (change->has_action && !handle_action_before_change)
11198 ExecuteCustomElementAction(x, y, element, page);
11202 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11203 int trigger_element,
11205 int trigger_player,
11209 boolean change_done_any = FALSE;
11210 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11213 if (!(trigger_events[trigger_element][trigger_event]))
11216 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11218 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11220 int element = EL_CUSTOM_START + i;
11221 boolean change_done = FALSE;
11224 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11225 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11228 for (p = 0; p < element_info[element].num_change_pages; p++)
11230 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11232 if (change->can_change_or_has_action &&
11233 change->has_event[trigger_event] &&
11234 change->trigger_side & trigger_side &&
11235 change->trigger_player & trigger_player &&
11236 change->trigger_page & trigger_page_bits &&
11237 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11239 change->actual_trigger_element = trigger_element;
11240 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11241 change->actual_trigger_player_bits = trigger_player;
11242 change->actual_trigger_side = trigger_side;
11243 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11244 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11245 change->actual_trigger_x = trigger_x;
11246 change->actual_trigger_y = trigger_y;
11248 if ((change->can_change && !change_done) || change->has_action)
11252 SCAN_PLAYFIELD(x, y)
11254 if (Tile[x][y] == element)
11256 if (change->can_change && !change_done)
11258 // if element already changed in this frame, not only prevent
11259 // another element change (checked in ChangeElement()), but
11260 // also prevent additional element actions for this element
11262 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11263 !level.use_action_after_change_bug)
11266 ChangeDelay[x][y] = 1;
11267 ChangeEvent[x][y] = trigger_event;
11269 HandleElementChange(x, y, p);
11271 else if (change->has_action)
11273 // if element already changed in this frame, not only prevent
11274 // another element change (checked in ChangeElement()), but
11275 // also prevent additional element actions for this element
11277 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11278 !level.use_action_after_change_bug)
11281 ExecuteCustomElementAction(x, y, element, p);
11282 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11287 if (change->can_change)
11289 change_done = TRUE;
11290 change_done_any = TRUE;
11297 RECURSION_LOOP_DETECTION_END();
11299 return change_done_any;
11302 static boolean CheckElementChangeExt(int x, int y,
11304 int trigger_element,
11306 int trigger_player,
11309 boolean change_done = FALSE;
11312 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11313 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11316 if (Tile[x][y] == EL_BLOCKED)
11318 Blocked2Moving(x, y, &x, &y);
11319 element = Tile[x][y];
11322 // check if element has already changed or is about to change after moving
11323 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11324 Tile[x][y] != element) ||
11326 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11327 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11328 ChangePage[x][y] != -1)))
11331 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11333 for (p = 0; p < element_info[element].num_change_pages; p++)
11335 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11337 /* check trigger element for all events where the element that is checked
11338 for changing interacts with a directly adjacent element -- this is
11339 different to element changes that affect other elements to change on the
11340 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11341 boolean check_trigger_element =
11342 (trigger_event == CE_NEXT_TO_X ||
11343 trigger_event == CE_TOUCHING_X ||
11344 trigger_event == CE_HITTING_X ||
11345 trigger_event == CE_HIT_BY_X ||
11346 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11348 if (change->can_change_or_has_action &&
11349 change->has_event[trigger_event] &&
11350 change->trigger_side & trigger_side &&
11351 change->trigger_player & trigger_player &&
11352 (!check_trigger_element ||
11353 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11355 change->actual_trigger_element = trigger_element;
11356 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11357 change->actual_trigger_player_bits = trigger_player;
11358 change->actual_trigger_side = trigger_side;
11359 change->actual_trigger_ce_value = CustomValue[x][y];
11360 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11361 change->actual_trigger_x = x;
11362 change->actual_trigger_y = y;
11364 // special case: trigger element not at (x,y) position for some events
11365 if (check_trigger_element)
11377 { 0, 0 }, { 0, 0 }, { 0, 0 },
11381 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11382 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11384 change->actual_trigger_ce_value = CustomValue[xx][yy];
11385 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11386 change->actual_trigger_x = xx;
11387 change->actual_trigger_y = yy;
11390 if (change->can_change && !change_done)
11392 ChangeDelay[x][y] = 1;
11393 ChangeEvent[x][y] = trigger_event;
11395 HandleElementChange(x, y, p);
11397 change_done = TRUE;
11399 else if (change->has_action)
11401 ExecuteCustomElementAction(x, y, element, p);
11402 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11407 RECURSION_LOOP_DETECTION_END();
11409 return change_done;
11412 static void PlayPlayerSound(struct PlayerInfo *player)
11414 int jx = player->jx, jy = player->jy;
11415 int sound_element = player->artwork_element;
11416 int last_action = player->last_action_waiting;
11417 int action = player->action_waiting;
11419 if (player->is_waiting)
11421 if (action != last_action)
11422 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11424 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11428 if (action != last_action)
11429 StopSound(element_info[sound_element].sound[last_action]);
11431 if (last_action == ACTION_SLEEPING)
11432 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11436 static void PlayAllPlayersSound(void)
11440 for (i = 0; i < MAX_PLAYERS; i++)
11441 if (stored_player[i].active)
11442 PlayPlayerSound(&stored_player[i]);
11445 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11447 boolean last_waiting = player->is_waiting;
11448 int move_dir = player->MovDir;
11450 player->dir_waiting = move_dir;
11451 player->last_action_waiting = player->action_waiting;
11455 if (!last_waiting) // not waiting -> waiting
11457 player->is_waiting = TRUE;
11459 player->frame_counter_bored =
11461 game.player_boring_delay_fixed +
11462 GetSimpleRandom(game.player_boring_delay_random);
11463 player->frame_counter_sleeping =
11465 game.player_sleeping_delay_fixed +
11466 GetSimpleRandom(game.player_sleeping_delay_random);
11468 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11471 if (game.player_sleeping_delay_fixed +
11472 game.player_sleeping_delay_random > 0 &&
11473 player->anim_delay_counter == 0 &&
11474 player->post_delay_counter == 0 &&
11475 FrameCounter >= player->frame_counter_sleeping)
11476 player->is_sleeping = TRUE;
11477 else if (game.player_boring_delay_fixed +
11478 game.player_boring_delay_random > 0 &&
11479 FrameCounter >= player->frame_counter_bored)
11480 player->is_bored = TRUE;
11482 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11483 player->is_bored ? ACTION_BORING :
11486 if (player->is_sleeping && player->use_murphy)
11488 // special case for sleeping Murphy when leaning against non-free tile
11490 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11491 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11492 !IS_MOVING(player->jx - 1, player->jy)))
11493 move_dir = MV_LEFT;
11494 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11495 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11496 !IS_MOVING(player->jx + 1, player->jy)))
11497 move_dir = MV_RIGHT;
11499 player->is_sleeping = FALSE;
11501 player->dir_waiting = move_dir;
11504 if (player->is_sleeping)
11506 if (player->num_special_action_sleeping > 0)
11508 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11510 int last_special_action = player->special_action_sleeping;
11511 int num_special_action = player->num_special_action_sleeping;
11512 int special_action =
11513 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11514 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11515 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11516 last_special_action + 1 : ACTION_SLEEPING);
11517 int special_graphic =
11518 el_act_dir2img(player->artwork_element, special_action, move_dir);
11520 player->anim_delay_counter =
11521 graphic_info[special_graphic].anim_delay_fixed +
11522 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11523 player->post_delay_counter =
11524 graphic_info[special_graphic].post_delay_fixed +
11525 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11527 player->special_action_sleeping = special_action;
11530 if (player->anim_delay_counter > 0)
11532 player->action_waiting = player->special_action_sleeping;
11533 player->anim_delay_counter--;
11535 else if (player->post_delay_counter > 0)
11537 player->post_delay_counter--;
11541 else if (player->is_bored)
11543 if (player->num_special_action_bored > 0)
11545 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11547 int special_action =
11548 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11549 int special_graphic =
11550 el_act_dir2img(player->artwork_element, special_action, move_dir);
11552 player->anim_delay_counter =
11553 graphic_info[special_graphic].anim_delay_fixed +
11554 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11555 player->post_delay_counter =
11556 graphic_info[special_graphic].post_delay_fixed +
11557 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11559 player->special_action_bored = special_action;
11562 if (player->anim_delay_counter > 0)
11564 player->action_waiting = player->special_action_bored;
11565 player->anim_delay_counter--;
11567 else if (player->post_delay_counter > 0)
11569 player->post_delay_counter--;
11574 else if (last_waiting) // waiting -> not waiting
11576 player->is_waiting = FALSE;
11577 player->is_bored = FALSE;
11578 player->is_sleeping = FALSE;
11580 player->frame_counter_bored = -1;
11581 player->frame_counter_sleeping = -1;
11583 player->anim_delay_counter = 0;
11584 player->post_delay_counter = 0;
11586 player->dir_waiting = player->MovDir;
11587 player->action_waiting = ACTION_DEFAULT;
11589 player->special_action_bored = ACTION_DEFAULT;
11590 player->special_action_sleeping = ACTION_DEFAULT;
11594 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11596 if ((!player->is_moving && player->was_moving) ||
11597 (player->MovPos == 0 && player->was_moving) ||
11598 (player->is_snapping && !player->was_snapping) ||
11599 (player->is_dropping && !player->was_dropping))
11601 if (!CheckSaveEngineSnapshotToList())
11604 player->was_moving = FALSE;
11605 player->was_snapping = TRUE;
11606 player->was_dropping = TRUE;
11610 if (player->is_moving)
11611 player->was_moving = TRUE;
11613 if (!player->is_snapping)
11614 player->was_snapping = FALSE;
11616 if (!player->is_dropping)
11617 player->was_dropping = FALSE;
11620 static struct MouseActionInfo mouse_action_last = { 0 };
11621 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11622 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11625 CheckSaveEngineSnapshotToList();
11627 mouse_action_last = mouse_action;
11630 static void CheckSingleStepMode(struct PlayerInfo *player)
11632 if (tape.single_step && tape.recording && !tape.pausing)
11634 // as it is called "single step mode", just return to pause mode when the
11635 // player stopped moving after one tile (or never starts moving at all)
11636 // (reverse logic needed here in case single step mode used in team mode)
11637 if (player->is_moving ||
11638 player->is_pushing ||
11639 player->is_dropping_pressed ||
11640 player->effective_mouse_action.button)
11641 game.enter_single_step_mode = FALSE;
11644 CheckSaveEngineSnapshot(player);
11647 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11649 int left = player_action & JOY_LEFT;
11650 int right = player_action & JOY_RIGHT;
11651 int up = player_action & JOY_UP;
11652 int down = player_action & JOY_DOWN;
11653 int button1 = player_action & JOY_BUTTON_1;
11654 int button2 = player_action & JOY_BUTTON_2;
11655 int dx = (left ? -1 : right ? 1 : 0);
11656 int dy = (up ? -1 : down ? 1 : 0);
11658 if (!player->active || tape.pausing)
11664 SnapField(player, dx, dy);
11668 DropElement(player);
11670 MovePlayer(player, dx, dy);
11673 CheckSingleStepMode(player);
11675 SetPlayerWaiting(player, FALSE);
11677 return player_action;
11681 // no actions for this player (no input at player's configured device)
11683 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11684 SnapField(player, 0, 0);
11685 CheckGravityMovementWhenNotMoving(player);
11687 if (player->MovPos == 0)
11688 SetPlayerWaiting(player, TRUE);
11690 if (player->MovPos == 0) // needed for tape.playing
11691 player->is_moving = FALSE;
11693 player->is_dropping = FALSE;
11694 player->is_dropping_pressed = FALSE;
11695 player->drop_pressed_delay = 0;
11697 CheckSingleStepMode(player);
11703 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11706 if (!tape.use_mouse_actions)
11709 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11710 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11711 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11714 static void SetTapeActionFromMouseAction(byte *tape_action,
11715 struct MouseActionInfo *mouse_action)
11717 if (!tape.use_mouse_actions)
11720 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11721 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11722 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11725 static void CheckLevelSolved(void)
11727 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11729 if (game_bd.level_solved &&
11730 !game_bd.game_over) // game won
11734 game_bd.game_over = TRUE;
11736 game.all_players_gone = TRUE;
11739 if (game_bd.game_over) // game lost
11740 game.all_players_gone = TRUE;
11742 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11744 if (game_em.level_solved &&
11745 !game_em.game_over) // game won
11749 game_em.game_over = TRUE;
11751 game.all_players_gone = TRUE;
11754 if (game_em.game_over) // game lost
11755 game.all_players_gone = TRUE;
11757 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11759 if (game_sp.level_solved &&
11760 !game_sp.game_over) // game won
11764 game_sp.game_over = TRUE;
11766 game.all_players_gone = TRUE;
11769 if (game_sp.game_over) // game lost
11770 game.all_players_gone = TRUE;
11772 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11774 if (game_mm.level_solved &&
11775 !game_mm.game_over) // game won
11779 game_mm.game_over = TRUE;
11781 game.all_players_gone = TRUE;
11784 if (game_mm.game_over) // game lost
11785 game.all_players_gone = TRUE;
11789 static void PlayTimeoutSound(int seconds_left)
11791 // will be played directly by BD engine (for classic bonus time sounds)
11792 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && checkBonusTime_BD())
11795 // try to use individual "running out of time" sound for each second left
11796 int sound = SND_GAME_RUNNING_OUT_OF_TIME_0 - seconds_left;
11798 // if special sound per second not defined, use default sound
11799 if (getSoundInfoEntryFilename(sound) == NULL)
11800 sound = SND_GAME_RUNNING_OUT_OF_TIME;
11802 // if out of time, but player still alive, play special "timeout" sound, if defined
11803 if (seconds_left == 0 && !checkGameFailed())
11804 if (getSoundInfoEntryFilename(SND_GAME_TIMEOUT) != NULL)
11805 sound = SND_GAME_TIMEOUT;
11810 static void CheckLevelTime_StepCounter(void)
11820 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11821 PlayTimeoutSound(TimeLeft);
11823 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11825 DisplayGameControlValues();
11827 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11828 for (i = 0; i < MAX_PLAYERS; i++)
11829 KillPlayer(&stored_player[i]);
11831 else if (game.no_level_time_limit && !game.all_players_gone)
11833 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11835 DisplayGameControlValues();
11839 static void CheckLevelTime(void)
11841 int frames_per_second = FRAMES_PER_SECOND;
11844 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11846 // level time may be running slower in native BD engine
11847 frames_per_second = getFramesPerSecond_BD();
11849 // if native engine time changed, force main engine time change
11850 if (getTimeLeft_BD() < TimeLeft)
11851 TimeFrames = frames_per_second;
11853 // if last second running, wait for native engine time to exactly reach zero
11854 if (getTimeLeft_BD() == 1 && TimeLeft == 1)
11855 TimeFrames = frames_per_second - 1;
11857 // needed to store final time after solving game (before counting down remaining time)
11858 SetTimeFrames_BD(TimePlayed * FRAMES_PER_SECOND + TimeFrames);
11861 if (TimeFrames >= frames_per_second)
11865 for (i = 0; i < MAX_PLAYERS; i++)
11867 struct PlayerInfo *player = &stored_player[i];
11869 if (SHIELD_ON(player))
11871 player->shield_normal_time_left--;
11873 if (player->shield_deadly_time_left > 0)
11874 player->shield_deadly_time_left--;
11878 if (!game.LevelSolved && !level.use_step_counter)
11886 if (TimeLeft <= 10 && game.time_limit)
11887 PlayTimeoutSound(TimeLeft);
11889 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11890 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11892 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11894 if (!TimeLeft && game.time_limit)
11896 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11898 if (game_bd.game->cave->player_state == GD_PL_LIVING)
11899 game_bd.game->cave->player_state = GD_PL_TIMEOUT;
11901 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11903 game_em.lev->killed_out_of_time = TRUE;
11907 for (i = 0; i < MAX_PLAYERS; i++)
11908 KillPlayer(&stored_player[i]);
11912 else if (game.no_level_time_limit && !game.all_players_gone)
11914 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11917 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11921 if (TapeTimeFrames >= FRAMES_PER_SECOND)
11923 TapeTimeFrames = 0;
11926 if (tape.recording || tape.playing)
11927 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11930 if (tape.recording || tape.playing)
11931 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11933 UpdateAndDisplayGameControlValues();
11936 void AdvanceFrameAndPlayerCounters(int player_nr)
11940 // handle game and tape time differently for native BD game engine
11942 // tape time is running in native BD engine even if player is not hatched yet
11943 if (!checkGameRunning())
11946 // advance frame counters (global frame counter and tape time frame counter)
11950 // level time is running in native BD engine after player is being hatched
11951 if (!checkGamePlaying())
11954 // advance time frame counter (used to control available time to solve level)
11957 // advance player counters (counters for move delay, move animation etc.)
11958 for (i = 0; i < MAX_PLAYERS; i++)
11960 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11961 int move_delay_value = stored_player[i].move_delay_value;
11962 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11964 if (!advance_player_counters) // not all players may be affected
11967 if (move_frames == 0) // less than one move per game frame
11969 int stepsize = TILEX / move_delay_value;
11970 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11971 int count = (stored_player[i].is_moving ?
11972 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11974 if (count % delay == 0)
11978 stored_player[i].Frame += move_frames;
11980 if (stored_player[i].MovPos != 0)
11981 stored_player[i].StepFrame += move_frames;
11983 if (stored_player[i].move_delay > 0)
11984 stored_player[i].move_delay--;
11986 // due to bugs in previous versions, counter must count up, not down
11987 if (stored_player[i].push_delay != -1)
11988 stored_player[i].push_delay++;
11990 if (stored_player[i].drop_delay > 0)
11991 stored_player[i].drop_delay--;
11993 if (stored_player[i].is_dropping_pressed)
11994 stored_player[i].drop_pressed_delay++;
11998 void AdvanceFrameCounter(void)
12003 void AdvanceGfxFrame(void)
12007 SCAN_PLAYFIELD(x, y)
12013 static void HandleMouseAction(struct MouseActionInfo *mouse_action,
12014 struct MouseActionInfo *mouse_action_last)
12016 if (mouse_action->button)
12018 int new_button = (mouse_action->button && mouse_action_last->button == 0);
12019 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button);
12020 int x = mouse_action->lx;
12021 int y = mouse_action->ly;
12022 int element = Tile[x][y];
12026 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12027 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12031 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12032 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12035 if (level.use_step_counter)
12037 boolean counted_click = FALSE;
12039 // element clicked that can change when clicked/pressed
12040 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
12041 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
12042 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
12043 counted_click = TRUE;
12045 // element clicked that can trigger change when clicked/pressed
12046 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
12047 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
12048 counted_click = TRUE;
12050 if (new_button && counted_click)
12051 CheckLevelTime_StepCounter();
12056 void StartGameActions(boolean init_network_game, boolean record_tape,
12059 unsigned int new_random_seed = InitRND(random_seed);
12062 TapeStartRecording(new_random_seed);
12064 if (setup.auto_pause_on_start && !tape.pausing)
12065 TapeTogglePause(TAPE_TOGGLE_MANUAL);
12067 if (init_network_game)
12069 SendToServer_LevelFile();
12070 SendToServer_StartPlaying();
12078 static void GameActionsExt(void)
12081 static unsigned int game_frame_delay = 0;
12083 unsigned int game_frame_delay_value;
12084 byte *recorded_player_action;
12085 byte summarized_player_action = 0;
12086 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
12089 // detect endless loops, caused by custom element programming
12090 if (recursion_loop_detected && recursion_loop_depth == 0)
12092 char *message = getStringCat3("Internal Error! Element ",
12093 EL_NAME(recursion_loop_element),
12094 " caused endless loop! Quit the game?");
12096 Warn("element '%s' caused endless loop in game engine",
12097 EL_NAME(recursion_loop_element));
12099 RequestQuitGameExt(program.headless, level_editor_test_game, message);
12101 recursion_loop_detected = FALSE; // if game should be continued
12108 if (game.restart_level)
12109 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
12111 CheckLevelSolved();
12113 if (game.LevelSolved && !game.LevelSolved_GameEnd)
12116 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
12119 if (game_status != GAME_MODE_PLAYING) // status might have changed
12122 game_frame_delay_value =
12123 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
12125 if (tape.playing && tape.warp_forward && !tape.pausing)
12126 game_frame_delay_value = 0;
12128 SetVideoFrameDelay(game_frame_delay_value);
12130 // (de)activate virtual buttons depending on current game status
12131 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
12133 if (game.all_players_gone) // if no players there to be controlled anymore
12134 SetOverlayActive(FALSE);
12135 else if (!tape.playing) // if game continues after tape stopped playing
12136 SetOverlayActive(TRUE);
12141 // ---------- main game synchronization point ----------
12143 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12145 Debug("game:playing:skip", "skip == %d", skip);
12148 // ---------- main game synchronization point ----------
12150 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12154 if (network_playing && !network_player_action_received)
12156 // try to get network player actions in time
12158 // last chance to get network player actions without main loop delay
12159 HandleNetworking();
12161 // game was quit by network peer
12162 if (game_status != GAME_MODE_PLAYING)
12165 // check if network player actions still missing and game still running
12166 if (!network_player_action_received && !checkGameEnded())
12167 return; // failed to get network player actions in time
12169 // do not yet reset "network_player_action_received" (for tape.pausing)
12175 // at this point we know that we really continue executing the game
12177 network_player_action_received = FALSE;
12179 // when playing tape, read previously recorded player input from tape data
12180 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
12182 local_player->effective_mouse_action = local_player->mouse_action;
12184 if (recorded_player_action != NULL)
12185 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
12186 recorded_player_action);
12188 // TapePlayAction() may return NULL when toggling to "pause before death"
12192 if (tape.set_centered_player)
12194 game.centered_player_nr_next = tape.centered_player_nr_next;
12195 game.set_centered_player = TRUE;
12198 for (i = 0; i < MAX_PLAYERS; i++)
12200 summarized_player_action |= stored_player[i].action;
12202 if (!network_playing && (game.team_mode || tape.playing))
12203 stored_player[i].effective_action = stored_player[i].action;
12206 if (network_playing && !checkGameEnded())
12207 SendToServer_MovePlayer(summarized_player_action);
12209 // summarize all actions at local players mapped input device position
12210 // (this allows using different input devices in single player mode)
12211 if (!network.enabled && !game.team_mode)
12212 stored_player[map_player_action[local_player->index_nr]].effective_action =
12213 summarized_player_action;
12215 // summarize all actions at centered player in local team mode
12216 if (tape.recording &&
12217 setup.team_mode && !network.enabled &&
12218 setup.input_on_focus &&
12219 game.centered_player_nr != -1)
12221 for (i = 0; i < MAX_PLAYERS; i++)
12222 stored_player[map_player_action[i]].effective_action =
12223 (i == game.centered_player_nr ? summarized_player_action : 0);
12226 if (recorded_player_action != NULL)
12227 for (i = 0; i < MAX_PLAYERS; i++)
12228 stored_player[i].effective_action = recorded_player_action[i];
12230 for (i = 0; i < MAX_PLAYERS; i++)
12232 tape_action[i] = stored_player[i].effective_action;
12234 /* (this may happen in the RND game engine if a player was not present on
12235 the playfield on level start, but appeared later from a custom element */
12236 if (setup.team_mode &&
12239 !tape.player_participates[i])
12240 tape.player_participates[i] = TRUE;
12243 SetTapeActionFromMouseAction(tape_action,
12244 &local_player->effective_mouse_action);
12246 // only record actions from input devices, but not programmed actions
12247 if (tape.recording)
12248 TapeRecordAction(tape_action);
12250 // remember if game was played (especially after tape stopped playing)
12251 if (!tape.playing && summarized_player_action && !checkGameFailed())
12252 game.GamePlayed = TRUE;
12254 #if USE_NEW_PLAYER_ASSIGNMENTS
12255 // !!! also map player actions in single player mode !!!
12256 // if (game.team_mode)
12259 byte mapped_action[MAX_PLAYERS];
12261 #if DEBUG_PLAYER_ACTIONS
12262 for (i = 0; i < MAX_PLAYERS; i++)
12263 DebugContinued("", "%d, ", stored_player[i].effective_action);
12266 for (i = 0; i < MAX_PLAYERS; i++)
12267 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
12269 for (i = 0; i < MAX_PLAYERS; i++)
12270 stored_player[i].effective_action = mapped_action[i];
12272 #if DEBUG_PLAYER_ACTIONS
12273 DebugContinued("", "=> ");
12274 for (i = 0; i < MAX_PLAYERS; i++)
12275 DebugContinued("", "%d, ", stored_player[i].effective_action);
12276 DebugContinued("game:playing:player", "\n");
12279 #if DEBUG_PLAYER_ACTIONS
12282 for (i = 0; i < MAX_PLAYERS; i++)
12283 DebugContinued("", "%d, ", stored_player[i].effective_action);
12284 DebugContinued("game:playing:player", "\n");
12289 for (i = 0; i < MAX_PLAYERS; i++)
12291 // allow engine snapshot in case of changed movement attempt
12292 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12293 (stored_player[i].effective_action & KEY_MOTION))
12294 game.snapshot.changed_action = TRUE;
12296 // allow engine snapshot in case of snapping/dropping attempt
12297 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12298 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12299 game.snapshot.changed_action = TRUE;
12301 game.snapshot.last_action[i] = stored_player[i].effective_action;
12304 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
12306 GameActions_BD_Main();
12308 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12310 GameActions_EM_Main();
12312 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12314 GameActions_SP_Main();
12316 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12318 GameActions_MM_Main();
12322 GameActions_RND_Main();
12325 BlitScreenToBitmap(backbuffer);
12327 CheckLevelSolved();
12330 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12332 if (global.show_frames_per_second)
12334 static unsigned int fps_counter = 0;
12335 static int fps_frames = 0;
12336 unsigned int fps_delay_ms = Counter() - fps_counter;
12340 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12342 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12345 fps_counter = Counter();
12347 // always draw FPS to screen after FPS value was updated
12348 redraw_mask |= REDRAW_FPS;
12351 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12352 if (GetDrawDeactivationMask() == REDRAW_NONE)
12353 redraw_mask |= REDRAW_FPS;
12357 static void GameActions_CheckSaveEngineSnapshot(void)
12359 if (!game.snapshot.save_snapshot)
12362 // clear flag for saving snapshot _before_ saving snapshot
12363 game.snapshot.save_snapshot = FALSE;
12365 SaveEngineSnapshotToList();
12368 void GameActions(void)
12372 GameActions_CheckSaveEngineSnapshot();
12375 void GameActions_BD_Main(void)
12377 byte effective_action[MAX_PLAYERS];
12380 for (i = 0; i < MAX_PLAYERS; i++)
12381 effective_action[i] = stored_player[i].effective_action;
12383 GameActions_BD(effective_action);
12386 void GameActions_EM_Main(void)
12388 byte effective_action[MAX_PLAYERS];
12391 for (i = 0; i < MAX_PLAYERS; i++)
12392 effective_action[i] = stored_player[i].effective_action;
12394 GameActions_EM(effective_action);
12397 void GameActions_SP_Main(void)
12399 byte effective_action[MAX_PLAYERS];
12402 for (i = 0; i < MAX_PLAYERS; i++)
12403 effective_action[i] = stored_player[i].effective_action;
12405 GameActions_SP(effective_action);
12407 for (i = 0; i < MAX_PLAYERS; i++)
12409 if (stored_player[i].force_dropping)
12410 stored_player[i].action |= KEY_BUTTON_DROP;
12412 stored_player[i].force_dropping = FALSE;
12416 void GameActions_MM_Main(void)
12420 GameActions_MM(local_player->effective_mouse_action);
12423 void GameActions_RND_Main(void)
12428 void GameActions_RND(void)
12430 static struct MouseActionInfo mouse_action_last = { 0 };
12431 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12432 int magic_wall_x = 0, magic_wall_y = 0;
12433 int i, x, y, element, graphic, last_gfx_frame;
12435 InitPlayfieldScanModeVars();
12437 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12439 SCAN_PLAYFIELD(x, y)
12441 ChangeCount[x][y] = 0;
12442 ChangeEvent[x][y] = -1;
12446 if (game.set_centered_player)
12448 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12450 // switching to "all players" only possible if all players fit to screen
12451 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12453 game.centered_player_nr_next = game.centered_player_nr;
12454 game.set_centered_player = FALSE;
12457 // do not switch focus to non-existing (or non-active) player
12458 if (game.centered_player_nr_next >= 0 &&
12459 !stored_player[game.centered_player_nr_next].active)
12461 game.centered_player_nr_next = game.centered_player_nr;
12462 game.set_centered_player = FALSE;
12466 if (game.set_centered_player &&
12467 ScreenMovPos == 0) // screen currently aligned at tile position
12471 if (game.centered_player_nr_next == -1)
12473 setScreenCenteredToAllPlayers(&sx, &sy);
12477 sx = stored_player[game.centered_player_nr_next].jx;
12478 sy = stored_player[game.centered_player_nr_next].jy;
12481 game.centered_player_nr = game.centered_player_nr_next;
12482 game.set_centered_player = FALSE;
12484 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12485 DrawGameDoorValues();
12488 // check single step mode (set flag and clear again if any player is active)
12489 game.enter_single_step_mode =
12490 (tape.single_step && tape.recording && !tape.pausing);
12492 for (i = 0; i < MAX_PLAYERS; i++)
12494 int actual_player_action = stored_player[i].effective_action;
12497 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12498 - rnd_equinox_tetrachloride 048
12499 - rnd_equinox_tetrachloride_ii 096
12500 - rnd_emanuel_schmieg 002
12501 - doctor_sloan_ww 001, 020
12503 if (stored_player[i].MovPos == 0)
12504 CheckGravityMovement(&stored_player[i]);
12507 // overwrite programmed action with tape action
12508 if (stored_player[i].programmed_action)
12509 actual_player_action = stored_player[i].programmed_action;
12511 PlayerActions(&stored_player[i], actual_player_action);
12513 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12516 // single step pause mode may already have been toggled by "ScrollPlayer()"
12517 if (game.enter_single_step_mode && !tape.pausing)
12518 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12520 ScrollScreen(NULL, SCROLL_GO_ON);
12522 /* for backwards compatibility, the following code emulates a fixed bug that
12523 occured when pushing elements (causing elements that just made their last
12524 pushing step to already (if possible) make their first falling step in the
12525 same game frame, which is bad); this code is also needed to use the famous
12526 "spring push bug" which is used in older levels and might be wanted to be
12527 used also in newer levels, but in this case the buggy pushing code is only
12528 affecting the "spring" element and no other elements */
12530 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12532 for (i = 0; i < MAX_PLAYERS; i++)
12534 struct PlayerInfo *player = &stored_player[i];
12535 int x = player->jx;
12536 int y = player->jy;
12538 if (player->active && player->is_pushing && player->is_moving &&
12540 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12541 Tile[x][y] == EL_SPRING))
12543 ContinueMoving(x, y);
12545 // continue moving after pushing (this is actually a bug)
12546 if (!IS_MOVING(x, y))
12547 Stop[x][y] = FALSE;
12552 SCAN_PLAYFIELD(x, y)
12554 Last[x][y] = Tile[x][y];
12556 ChangeCount[x][y] = 0;
12557 ChangeEvent[x][y] = -1;
12559 // this must be handled before main playfield loop
12560 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12563 if (MovDelay[x][y] <= 0)
12567 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12570 if (MovDelay[x][y] <= 0)
12572 int element = Store[x][y];
12573 int move_direction = MovDir[x][y];
12574 int player_index_bit = Store2[x][y];
12580 TEST_DrawLevelField(x, y);
12582 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12584 if (IS_ENVELOPE(element))
12585 local_player->show_envelope = element;
12590 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12592 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12594 Debug("game:playing:GameActions_RND", "This should never happen!");
12596 ChangePage[x][y] = -1;
12600 Stop[x][y] = FALSE;
12601 if (WasJustMoving[x][y] > 0)
12602 WasJustMoving[x][y]--;
12603 if (WasJustFalling[x][y] > 0)
12604 WasJustFalling[x][y]--;
12605 if (CheckCollision[x][y] > 0)
12606 CheckCollision[x][y]--;
12607 if (CheckImpact[x][y] > 0)
12608 CheckImpact[x][y]--;
12612 /* reset finished pushing action (not done in ContinueMoving() to allow
12613 continuous pushing animation for elements with zero push delay) */
12614 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12616 ResetGfxAnimation(x, y);
12617 TEST_DrawLevelField(x, y);
12621 if (IS_BLOCKED(x, y))
12625 Blocked2Moving(x, y, &oldx, &oldy);
12626 if (!IS_MOVING(oldx, oldy))
12628 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12629 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12630 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12631 Debug("game:playing:GameActions_RND", "This should never happen!");
12637 HandleMouseAction(&mouse_action, &mouse_action_last);
12639 SCAN_PLAYFIELD(x, y)
12641 element = Tile[x][y];
12642 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12643 last_gfx_frame = GfxFrame[x][y];
12645 if (element == EL_EMPTY)
12646 graphic = el2img(GfxElementEmpty[x][y]);
12648 ResetGfxFrame(x, y);
12650 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12651 DrawLevelGraphicAnimation(x, y, graphic);
12653 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12654 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12655 ResetRandomAnimationValue(x, y);
12657 SetRandomAnimationValue(x, y);
12659 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12661 if (IS_INACTIVE(element))
12663 if (IS_ANIMATED(graphic))
12664 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12669 // this may take place after moving, so 'element' may have changed
12670 if (IS_CHANGING(x, y) &&
12671 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12673 int page = element_info[element].event_page_nr[CE_DELAY];
12675 HandleElementChange(x, y, page);
12677 element = Tile[x][y];
12678 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12681 CheckNextToConditions(x, y);
12683 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12687 element = Tile[x][y];
12688 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12690 if (IS_ANIMATED(graphic) &&
12691 !IS_MOVING(x, y) &&
12693 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12695 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12696 TEST_DrawTwinkleOnField(x, y);
12698 else if (element == EL_ACID)
12701 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12703 else if ((element == EL_EXIT_OPEN ||
12704 element == EL_EM_EXIT_OPEN ||
12705 element == EL_SP_EXIT_OPEN ||
12706 element == EL_STEEL_EXIT_OPEN ||
12707 element == EL_EM_STEEL_EXIT_OPEN ||
12708 element == EL_SP_TERMINAL ||
12709 element == EL_SP_TERMINAL_ACTIVE ||
12710 element == EL_EXTRA_TIME ||
12711 element == EL_SHIELD_NORMAL ||
12712 element == EL_SHIELD_DEADLY) &&
12713 IS_ANIMATED(graphic))
12714 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12715 else if (IS_MOVING(x, y))
12716 ContinueMoving(x, y);
12717 else if (IS_ACTIVE_BOMB(element))
12718 CheckDynamite(x, y);
12719 else if (element == EL_AMOEBA_GROWING)
12720 AmoebaGrowing(x, y);
12721 else if (element == EL_AMOEBA_SHRINKING)
12722 AmoebaShrinking(x, y);
12724 #if !USE_NEW_AMOEBA_CODE
12725 else if (IS_AMOEBALIVE(element))
12726 AmoebaReproduce(x, y);
12729 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12731 else if (element == EL_EXIT_CLOSED)
12733 else if (element == EL_EM_EXIT_CLOSED)
12735 else if (element == EL_STEEL_EXIT_CLOSED)
12736 CheckExitSteel(x, y);
12737 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12738 CheckExitSteelEM(x, y);
12739 else if (element == EL_SP_EXIT_CLOSED)
12741 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12742 element == EL_EXPANDABLE_STEELWALL_GROWING)
12744 else if (element == EL_EXPANDABLE_WALL ||
12745 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12746 element == EL_EXPANDABLE_WALL_VERTICAL ||
12747 element == EL_EXPANDABLE_WALL_ANY ||
12748 element == EL_BD_EXPANDABLE_WALL ||
12749 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12750 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12751 element == EL_EXPANDABLE_STEELWALL_ANY)
12752 CheckWallGrowing(x, y);
12753 else if (element == EL_FLAMES)
12754 CheckForDragon(x, y);
12755 else if (element == EL_EXPLOSION)
12756 ; // drawing of correct explosion animation is handled separately
12757 else if (element == EL_ELEMENT_SNAPPING ||
12758 element == EL_DIAGONAL_SHRINKING ||
12759 element == EL_DIAGONAL_GROWING)
12761 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]);
12763 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12765 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12766 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12768 if (IS_BELT_ACTIVE(element))
12769 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12771 if (game.magic_wall_active)
12773 int jx = local_player->jx, jy = local_player->jy;
12775 // play the element sound at the position nearest to the player
12776 if ((element == EL_MAGIC_WALL_FULL ||
12777 element == EL_MAGIC_WALL_ACTIVE ||
12778 element == EL_MAGIC_WALL_EMPTYING ||
12779 element == EL_BD_MAGIC_WALL_FULL ||
12780 element == EL_BD_MAGIC_WALL_ACTIVE ||
12781 element == EL_BD_MAGIC_WALL_EMPTYING ||
12782 element == EL_DC_MAGIC_WALL_FULL ||
12783 element == EL_DC_MAGIC_WALL_ACTIVE ||
12784 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12785 ABS(x - jx) + ABS(y - jy) <
12786 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12794 #if USE_NEW_AMOEBA_CODE
12795 // new experimental amoeba growth stuff
12796 if (!(FrameCounter % 8))
12798 static unsigned int random = 1684108901;
12800 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12802 x = RND(lev_fieldx);
12803 y = RND(lev_fieldy);
12804 element = Tile[x][y];
12806 if (!IS_PLAYER(x, y) &&
12807 (element == EL_EMPTY ||
12808 CAN_GROW_INTO(element) ||
12809 element == EL_QUICKSAND_EMPTY ||
12810 element == EL_QUICKSAND_FAST_EMPTY ||
12811 element == EL_ACID_SPLASH_LEFT ||
12812 element == EL_ACID_SPLASH_RIGHT))
12814 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12815 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12816 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12817 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12818 Tile[x][y] = EL_AMOEBA_DROP;
12821 random = random * 129 + 1;
12826 game.explosions_delayed = FALSE;
12828 SCAN_PLAYFIELD(x, y)
12830 element = Tile[x][y];
12832 if (ExplodeField[x][y])
12833 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12834 else if (element == EL_EXPLOSION)
12835 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12837 ExplodeField[x][y] = EX_TYPE_NONE;
12840 game.explosions_delayed = TRUE;
12842 if (game.magic_wall_active)
12844 if (!(game.magic_wall_time_left % 4))
12846 int element = Tile[magic_wall_x][magic_wall_y];
12848 if (element == EL_BD_MAGIC_WALL_FULL ||
12849 element == EL_BD_MAGIC_WALL_ACTIVE ||
12850 element == EL_BD_MAGIC_WALL_EMPTYING)
12851 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12852 else if (element == EL_DC_MAGIC_WALL_FULL ||
12853 element == EL_DC_MAGIC_WALL_ACTIVE ||
12854 element == EL_DC_MAGIC_WALL_EMPTYING)
12855 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12857 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12860 if (game.magic_wall_time_left > 0)
12862 game.magic_wall_time_left--;
12864 if (!game.magic_wall_time_left)
12866 SCAN_PLAYFIELD(x, y)
12868 element = Tile[x][y];
12870 if (element == EL_MAGIC_WALL_ACTIVE ||
12871 element == EL_MAGIC_WALL_FULL)
12873 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12874 TEST_DrawLevelField(x, y);
12876 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12877 element == EL_BD_MAGIC_WALL_FULL)
12879 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12880 TEST_DrawLevelField(x, y);
12882 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12883 element == EL_DC_MAGIC_WALL_FULL)
12885 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12886 TEST_DrawLevelField(x, y);
12890 game.magic_wall_active = FALSE;
12895 if (game.light_time_left > 0)
12897 game.light_time_left--;
12899 if (game.light_time_left == 0)
12900 RedrawAllLightSwitchesAndInvisibleElements();
12903 if (game.timegate_time_left > 0)
12905 game.timegate_time_left--;
12907 if (game.timegate_time_left == 0)
12908 CloseAllOpenTimegates();
12911 if (game.lenses_time_left > 0)
12913 game.lenses_time_left--;
12915 if (game.lenses_time_left == 0)
12916 RedrawAllInvisibleElementsForLenses();
12919 if (game.magnify_time_left > 0)
12921 game.magnify_time_left--;
12923 if (game.magnify_time_left == 0)
12924 RedrawAllInvisibleElementsForMagnifier();
12927 for (i = 0; i < MAX_PLAYERS; i++)
12929 struct PlayerInfo *player = &stored_player[i];
12931 if (SHIELD_ON(player))
12933 if (player->shield_deadly_time_left)
12934 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12935 else if (player->shield_normal_time_left)
12936 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12940 #if USE_DELAYED_GFX_REDRAW
12941 SCAN_PLAYFIELD(x, y)
12943 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12945 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12946 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12948 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12949 DrawLevelField(x, y);
12951 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12952 DrawLevelFieldCrumbled(x, y);
12954 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12955 DrawLevelFieldCrumbledNeighbours(x, y);
12957 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12958 DrawTwinkleOnField(x, y);
12961 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12966 PlayAllPlayersSound();
12968 for (i = 0; i < MAX_PLAYERS; i++)
12970 struct PlayerInfo *player = &stored_player[i];
12972 if (player->show_envelope != 0 && (!player->active ||
12973 player->MovPos == 0))
12975 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12977 player->show_envelope = 0;
12981 // use random number generator in every frame to make it less predictable
12982 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12985 mouse_action_last = mouse_action;
12988 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12990 int min_x = x, min_y = y, max_x = x, max_y = y;
12991 int scr_fieldx = getScreenFieldSizeX();
12992 int scr_fieldy = getScreenFieldSizeY();
12995 for (i = 0; i < MAX_PLAYERS; i++)
12997 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12999 if (!stored_player[i].active || &stored_player[i] == player)
13002 min_x = MIN(min_x, jx);
13003 min_y = MIN(min_y, jy);
13004 max_x = MAX(max_x, jx);
13005 max_y = MAX(max_y, jy);
13008 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
13011 static boolean AllPlayersInVisibleScreen(void)
13015 for (i = 0; i < MAX_PLAYERS; i++)
13017 int jx = stored_player[i].jx, jy = stored_player[i].jy;
13019 if (!stored_player[i].active)
13022 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13029 void ScrollLevel(int dx, int dy)
13031 int scroll_offset = 2 * TILEX_VAR;
13034 BlitBitmap(drawto_field, drawto_field,
13035 FX + TILEX_VAR * (dx == -1) - scroll_offset,
13036 FY + TILEY_VAR * (dy == -1) - scroll_offset,
13037 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
13038 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
13039 FX + TILEX_VAR * (dx == 1) - scroll_offset,
13040 FY + TILEY_VAR * (dy == 1) - scroll_offset);
13044 x = (dx == 1 ? BX1 : BX2);
13045 for (y = BY1; y <= BY2; y++)
13046 DrawScreenField(x, y);
13051 y = (dy == 1 ? BY1 : BY2);
13052 for (x = BX1; x <= BX2; x++)
13053 DrawScreenField(x, y);
13056 redraw_mask |= REDRAW_FIELD;
13059 static boolean canFallDown(struct PlayerInfo *player)
13061 int jx = player->jx, jy = player->jy;
13063 return (IN_LEV_FIELD(jx, jy + 1) &&
13064 (IS_FREE(jx, jy + 1) ||
13065 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
13066 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
13067 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
13070 static boolean canPassField(int x, int y, int move_dir)
13072 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13073 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13074 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13075 int nextx = x + dx;
13076 int nexty = y + dy;
13077 int element = Tile[x][y];
13079 return (IS_PASSABLE_FROM(element, opposite_dir) &&
13080 !CAN_MOVE(element) &&
13081 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
13082 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
13083 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
13086 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
13088 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13089 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13090 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13094 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
13095 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
13096 (IS_DIGGABLE(Tile[newx][newy]) ||
13097 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
13098 canPassField(newx, newy, move_dir)));
13101 static void CheckGravityMovement(struct PlayerInfo *player)
13103 if (player->gravity && !player->programmed_action)
13105 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
13106 int move_dir_vertical = player->effective_action & MV_VERTICAL;
13107 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
13108 int jx = player->jx, jy = player->jy;
13109 boolean player_is_moving_to_valid_field =
13110 (!player_is_snapping &&
13111 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
13112 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
13113 boolean player_can_fall_down = canFallDown(player);
13115 if (player_can_fall_down &&
13116 !player_is_moving_to_valid_field)
13117 player->programmed_action = MV_DOWN;
13121 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
13123 return CheckGravityMovement(player);
13125 if (player->gravity && !player->programmed_action)
13127 int jx = player->jx, jy = player->jy;
13128 boolean field_under_player_is_free =
13129 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
13130 boolean player_is_standing_on_valid_field =
13131 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
13132 (IS_WALKABLE(Tile[jx][jy]) &&
13133 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
13135 if (field_under_player_is_free && !player_is_standing_on_valid_field)
13136 player->programmed_action = MV_DOWN;
13141 MovePlayerOneStep()
13142 -----------------------------------------------------------------------------
13143 dx, dy: direction (non-diagonal) to try to move the player to
13144 real_dx, real_dy: direction as read from input device (can be diagonal)
13147 boolean MovePlayerOneStep(struct PlayerInfo *player,
13148 int dx, int dy, int real_dx, int real_dy)
13150 int jx = player->jx, jy = player->jy;
13151 int new_jx = jx + dx, new_jy = jy + dy;
13153 boolean player_can_move = !player->cannot_move;
13155 if (!player->active || (!dx && !dy))
13156 return MP_NO_ACTION;
13158 player->MovDir = (dx < 0 ? MV_LEFT :
13159 dx > 0 ? MV_RIGHT :
13161 dy > 0 ? MV_DOWN : MV_NONE);
13163 if (!IN_LEV_FIELD(new_jx, new_jy))
13164 return MP_NO_ACTION;
13166 if (!player_can_move)
13168 if (player->MovPos == 0)
13170 player->is_moving = FALSE;
13171 player->is_digging = FALSE;
13172 player->is_collecting = FALSE;
13173 player->is_snapping = FALSE;
13174 player->is_pushing = FALSE;
13178 if (!network.enabled && game.centered_player_nr == -1 &&
13179 !AllPlayersInSight(player, new_jx, new_jy))
13180 return MP_NO_ACTION;
13182 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG);
13183 if (can_move != MP_MOVING)
13186 // check if DigField() has caused relocation of the player
13187 if (player->jx != jx || player->jy != jy)
13188 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
13190 StorePlayer[jx][jy] = 0;
13191 player->last_jx = jx;
13192 player->last_jy = jy;
13193 player->jx = new_jx;
13194 player->jy = new_jy;
13195 StorePlayer[new_jx][new_jy] = player->element_nr;
13197 if (player->move_delay_value_next != -1)
13199 player->move_delay_value = player->move_delay_value_next;
13200 player->move_delay_value_next = -1;
13204 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
13206 player->step_counter++;
13208 PlayerVisit[jx][jy] = FrameCounter;
13210 player->is_moving = TRUE;
13213 // should better be called in MovePlayer(), but this breaks some tapes
13214 ScrollPlayer(player, SCROLL_INIT);
13220 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
13222 int jx = player->jx, jy = player->jy;
13223 int old_jx = jx, old_jy = jy;
13224 int moved = MP_NO_ACTION;
13226 if (!player->active)
13231 if (player->MovPos == 0)
13233 player->is_moving = FALSE;
13234 player->is_digging = FALSE;
13235 player->is_collecting = FALSE;
13236 player->is_snapping = FALSE;
13237 player->is_pushing = FALSE;
13243 if (player->move_delay > 0)
13246 player->move_delay = -1; // set to "uninitialized" value
13248 // store if player is automatically moved to next field
13249 player->is_auto_moving = (player->programmed_action != MV_NONE);
13251 // remove the last programmed player action
13252 player->programmed_action = 0;
13254 if (player->MovPos)
13256 // should only happen if pre-1.2 tape recordings are played
13257 // this is only for backward compatibility
13259 int original_move_delay_value = player->move_delay_value;
13262 Debug("game:playing:MovePlayer",
13263 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13267 // scroll remaining steps with finest movement resolution
13268 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13270 while (player->MovPos)
13272 ScrollPlayer(player, SCROLL_GO_ON);
13273 ScrollScreen(NULL, SCROLL_GO_ON);
13275 AdvanceFrameAndPlayerCounters(player->index_nr);
13278 BackToFront_WithFrameDelay(0);
13281 player->move_delay_value = original_move_delay_value;
13284 player->is_active = FALSE;
13286 if (player->last_move_dir & MV_HORIZONTAL)
13288 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13289 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13293 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13294 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13297 if (!moved && !player->is_active)
13299 player->is_moving = FALSE;
13300 player->is_digging = FALSE;
13301 player->is_collecting = FALSE;
13302 player->is_snapping = FALSE;
13303 player->is_pushing = FALSE;
13309 if (moved & MP_MOVING && !ScreenMovPos &&
13310 (player->index_nr == game.centered_player_nr ||
13311 game.centered_player_nr == -1))
13313 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13315 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13317 // actual player has left the screen -- scroll in that direction
13318 if (jx != old_jx) // player has moved horizontally
13319 scroll_x += (jx - old_jx);
13320 else // player has moved vertically
13321 scroll_y += (jy - old_jy);
13325 int offset_raw = game.scroll_delay_value;
13327 if (jx != old_jx) // player has moved horizontally
13329 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13330 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13331 int new_scroll_x = jx - MIDPOSX + offset_x;
13333 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13334 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13335 scroll_x = new_scroll_x;
13337 // don't scroll over playfield boundaries
13338 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13340 // don't scroll more than one field at a time
13341 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13343 // don't scroll against the player's moving direction
13344 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13345 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13346 scroll_x = old_scroll_x;
13348 else // player has moved vertically
13350 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13351 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13352 int new_scroll_y = jy - MIDPOSY + offset_y;
13354 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13355 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13356 scroll_y = new_scroll_y;
13358 // don't scroll over playfield boundaries
13359 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13361 // don't scroll more than one field at a time
13362 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13364 // don't scroll against the player's moving direction
13365 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13366 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13367 scroll_y = old_scroll_y;
13371 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13373 if (!network.enabled && game.centered_player_nr == -1 &&
13374 !AllPlayersInVisibleScreen())
13376 scroll_x = old_scroll_x;
13377 scroll_y = old_scroll_y;
13381 ScrollScreen(player, SCROLL_INIT);
13382 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13387 player->StepFrame = 0;
13389 if (moved & MP_MOVING)
13391 if (old_jx != jx && old_jy == jy)
13392 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13393 else if (old_jx == jx && old_jy != jy)
13394 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13396 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13398 player->last_move_dir = player->MovDir;
13399 player->is_moving = TRUE;
13400 player->is_snapping = FALSE;
13401 player->is_switching = FALSE;
13402 player->is_dropping = FALSE;
13403 player->is_dropping_pressed = FALSE;
13404 player->drop_pressed_delay = 0;
13407 // should better be called here than above, but this breaks some tapes
13408 ScrollPlayer(player, SCROLL_INIT);
13413 CheckGravityMovementWhenNotMoving(player);
13415 player->is_moving = FALSE;
13417 /* at this point, the player is allowed to move, but cannot move right now
13418 (e.g. because of something blocking the way) -- ensure that the player
13419 is also allowed to move in the next frame (in old versions before 3.1.1,
13420 the player was forced to wait again for eight frames before next try) */
13422 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13423 player->move_delay = 0; // allow direct movement in the next frame
13426 if (player->move_delay == -1) // not yet initialized by DigField()
13427 player->move_delay = player->move_delay_value;
13429 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13431 TestIfPlayerTouchesBadThing(jx, jy);
13432 TestIfPlayerTouchesCustomElement(jx, jy);
13435 if (!player->active)
13436 RemovePlayer(player);
13441 void ScrollPlayer(struct PlayerInfo *player, int mode)
13443 int jx = player->jx, jy = player->jy;
13444 int last_jx = player->last_jx, last_jy = player->last_jy;
13445 int move_stepsize = TILEX / player->move_delay_value;
13447 if (!player->active)
13450 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13453 if (mode == SCROLL_INIT)
13455 player->actual_frame_counter.count = FrameCounter;
13456 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13458 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13459 Tile[last_jx][last_jy] == EL_EMPTY)
13461 int last_field_block_delay = 0; // start with no blocking at all
13462 int block_delay_adjustment = player->block_delay_adjustment;
13464 // if player blocks last field, add delay for exactly one move
13465 if (player->block_last_field)
13467 last_field_block_delay += player->move_delay_value;
13469 // when blocking enabled, prevent moving up despite gravity
13470 if (player->gravity && player->MovDir == MV_UP)
13471 block_delay_adjustment = -1;
13474 // add block delay adjustment (also possible when not blocking)
13475 last_field_block_delay += block_delay_adjustment;
13477 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13478 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13481 if (player->MovPos != 0) // player has not yet reached destination
13484 else if (!FrameReached(&player->actual_frame_counter))
13487 if (player->MovPos != 0)
13489 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13490 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13492 // before DrawPlayer() to draw correct player graphic for this case
13493 if (player->MovPos == 0)
13494 CheckGravityMovement(player);
13497 if (player->MovPos == 0) // player reached destination field
13499 if (player->move_delay_reset_counter > 0)
13501 player->move_delay_reset_counter--;
13503 if (player->move_delay_reset_counter == 0)
13505 // continue with normal speed after quickly moving through gate
13506 HALVE_PLAYER_SPEED(player);
13508 // be able to make the next move without delay
13509 player->move_delay = 0;
13513 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13514 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13515 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13516 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13517 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13518 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13519 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13520 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13522 ExitPlayer(player);
13524 if (game.players_still_needed == 0 &&
13525 (game.friends_still_needed == 0 ||
13526 IS_SP_ELEMENT(Tile[jx][jy])))
13530 player->last_jx = jx;
13531 player->last_jy = jy;
13533 // this breaks one level: "machine", level 000
13535 int move_direction = player->MovDir;
13536 int enter_side = MV_DIR_OPPOSITE(move_direction);
13537 int leave_side = move_direction;
13538 int old_jx = last_jx;
13539 int old_jy = last_jy;
13540 int old_element = Tile[old_jx][old_jy];
13541 int new_element = Tile[jx][jy];
13543 if (IS_CUSTOM_ELEMENT(old_element))
13544 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13546 player->index_bit, leave_side);
13548 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13549 CE_PLAYER_LEAVES_X,
13550 player->index_bit, leave_side);
13552 // needed because pushed element has not yet reached its destination,
13553 // so it would trigger a change event at its previous field location
13554 if (!player->is_pushing)
13556 if (IS_CUSTOM_ELEMENT(new_element))
13557 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13558 player->index_bit, enter_side);
13560 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13561 CE_PLAYER_ENTERS_X,
13562 player->index_bit, enter_side);
13565 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13566 CE_MOVE_OF_X, move_direction);
13569 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13571 TestIfPlayerTouchesBadThing(jx, jy);
13572 TestIfPlayerTouchesCustomElement(jx, jy);
13574 // needed because pushed element has not yet reached its destination,
13575 // so it would trigger a change event at its previous field location
13576 if (!player->is_pushing)
13577 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13579 if (level.finish_dig_collect &&
13580 (player->is_digging || player->is_collecting))
13582 int last_element = player->last_removed_element;
13583 int move_direction = player->MovDir;
13584 int enter_side = MV_DIR_OPPOSITE(move_direction);
13585 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13586 CE_PLAYER_COLLECTS_X);
13588 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13589 player->index_bit, enter_side);
13591 player->last_removed_element = EL_UNDEFINED;
13594 if (!player->active)
13595 RemovePlayer(player);
13598 if (level.use_step_counter)
13599 CheckLevelTime_StepCounter();
13601 if (tape.single_step && tape.recording && !tape.pausing &&
13602 !player->programmed_action)
13603 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13605 if (!player->programmed_action)
13606 CheckSaveEngineSnapshot(player);
13610 void ScrollScreen(struct PlayerInfo *player, int mode)
13612 static DelayCounter screen_frame_counter = { 0 };
13614 if (mode == SCROLL_INIT)
13616 // set scrolling step size according to actual player's moving speed
13617 ScrollStepSize = TILEX / player->move_delay_value;
13619 screen_frame_counter.count = FrameCounter;
13620 screen_frame_counter.value = 1;
13622 ScreenMovDir = player->MovDir;
13623 ScreenMovPos = player->MovPos;
13624 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13627 else if (!FrameReached(&screen_frame_counter))
13632 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13633 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13634 redraw_mask |= REDRAW_FIELD;
13637 ScreenMovDir = MV_NONE;
13640 void CheckNextToConditions(int x, int y)
13642 int element = Tile[x][y];
13644 if (IS_PLAYER(x, y))
13645 TestIfPlayerNextToCustomElement(x, y);
13647 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13648 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13649 TestIfElementNextToCustomElement(x, y);
13652 void TestIfPlayerNextToCustomElement(int x, int y)
13654 struct XY *xy = xy_topdown;
13655 static int trigger_sides[4][2] =
13657 // center side border side
13658 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13659 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13660 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13661 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13665 if (!IS_PLAYER(x, y))
13668 struct PlayerInfo *player = PLAYERINFO(x, y);
13670 if (player->is_moving)
13673 for (i = 0; i < NUM_DIRECTIONS; i++)
13675 int xx = x + xy[i].x;
13676 int yy = y + xy[i].y;
13677 int border_side = trigger_sides[i][1];
13678 int border_element;
13680 if (!IN_LEV_FIELD(xx, yy))
13683 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13684 continue; // center and border element not connected
13686 border_element = Tile[xx][yy];
13688 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13689 player->index_bit, border_side);
13690 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13691 CE_PLAYER_NEXT_TO_X,
13692 player->index_bit, border_side);
13694 /* use player element that is initially defined in the level playfield,
13695 not the player element that corresponds to the runtime player number
13696 (example: a level that contains EL_PLAYER_3 as the only player would
13697 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13699 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13700 CE_NEXT_TO_X, border_side);
13704 void TestIfPlayerTouchesCustomElement(int x, int y)
13706 struct XY *xy = xy_topdown;
13707 static int trigger_sides[4][2] =
13709 // center side border side
13710 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13711 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13712 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13713 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13715 static int touch_dir[4] =
13717 MV_LEFT | MV_RIGHT,
13722 int center_element = Tile[x][y]; // should always be non-moving!
13725 for (i = 0; i < NUM_DIRECTIONS; i++)
13727 int xx = x + xy[i].x;
13728 int yy = y + xy[i].y;
13729 int center_side = trigger_sides[i][0];
13730 int border_side = trigger_sides[i][1];
13731 int border_element;
13733 if (!IN_LEV_FIELD(xx, yy))
13736 if (IS_PLAYER(x, y)) // player found at center element
13738 struct PlayerInfo *player = PLAYERINFO(x, y);
13740 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13741 border_element = Tile[xx][yy]; // may be moving!
13742 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13743 border_element = Tile[xx][yy];
13744 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13745 border_element = MovingOrBlocked2Element(xx, yy);
13747 continue; // center and border element do not touch
13749 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13750 player->index_bit, border_side);
13751 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13752 CE_PLAYER_TOUCHES_X,
13753 player->index_bit, border_side);
13756 /* use player element that is initially defined in the level playfield,
13757 not the player element that corresponds to the runtime player number
13758 (example: a level that contains EL_PLAYER_3 as the only player would
13759 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13760 int player_element = PLAYERINFO(x, y)->initial_element;
13762 // as element "X" is the player here, check opposite (center) side
13763 CheckElementChangeBySide(xx, yy, border_element, player_element,
13764 CE_TOUCHING_X, center_side);
13767 else if (IS_PLAYER(xx, yy)) // player found at border element
13769 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13771 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13773 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13774 continue; // center and border element do not touch
13777 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13778 player->index_bit, center_side);
13779 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13780 CE_PLAYER_TOUCHES_X,
13781 player->index_bit, center_side);
13784 /* use player element that is initially defined in the level playfield,
13785 not the player element that corresponds to the runtime player number
13786 (example: a level that contains EL_PLAYER_3 as the only player would
13787 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13788 int player_element = PLAYERINFO(xx, yy)->initial_element;
13790 // as element "X" is the player here, check opposite (border) side
13791 CheckElementChangeBySide(x, y, center_element, player_element,
13792 CE_TOUCHING_X, border_side);
13800 void TestIfElementNextToCustomElement(int x, int y)
13802 struct XY *xy = xy_topdown;
13803 static int trigger_sides[4][2] =
13805 // center side border side
13806 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13807 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13808 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13809 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13811 int center_element = Tile[x][y]; // should always be non-moving!
13814 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13817 for (i = 0; i < NUM_DIRECTIONS; i++)
13819 int xx = x + xy[i].x;
13820 int yy = y + xy[i].y;
13821 int border_side = trigger_sides[i][1];
13822 int border_element;
13824 if (!IN_LEV_FIELD(xx, yy))
13827 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13828 continue; // center and border element not connected
13830 border_element = Tile[xx][yy];
13832 // check for change of center element (but change it only once)
13833 if (CheckElementChangeBySide(x, y, center_element, border_element,
13834 CE_NEXT_TO_X, border_side))
13839 void TestIfElementTouchesCustomElement(int x, int y)
13841 struct XY *xy = xy_topdown;
13842 static int trigger_sides[4][2] =
13844 // center side border side
13845 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13846 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13847 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13848 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13850 static int touch_dir[4] =
13852 MV_LEFT | MV_RIGHT,
13857 boolean change_center_element = FALSE;
13858 int center_element = Tile[x][y]; // should always be non-moving!
13859 int border_element_old[NUM_DIRECTIONS];
13862 for (i = 0; i < NUM_DIRECTIONS; i++)
13864 int xx = x + xy[i].x;
13865 int yy = y + xy[i].y;
13866 int border_element;
13868 border_element_old[i] = -1;
13870 if (!IN_LEV_FIELD(xx, yy))
13873 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13874 border_element = Tile[xx][yy]; // may be moving!
13875 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13876 border_element = Tile[xx][yy];
13877 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13878 border_element = MovingOrBlocked2Element(xx, yy);
13880 continue; // center and border element do not touch
13882 border_element_old[i] = border_element;
13885 for (i = 0; i < NUM_DIRECTIONS; i++)
13887 int xx = x + xy[i].x;
13888 int yy = y + xy[i].y;
13889 int center_side = trigger_sides[i][0];
13890 int border_element = border_element_old[i];
13892 if (border_element == -1)
13895 // check for change of border element
13896 CheckElementChangeBySide(xx, yy, border_element, center_element,
13897 CE_TOUCHING_X, center_side);
13899 // (center element cannot be player, so we don't have to check this here)
13902 for (i = 0; i < NUM_DIRECTIONS; i++)
13904 int xx = x + xy[i].x;
13905 int yy = y + xy[i].y;
13906 int border_side = trigger_sides[i][1];
13907 int border_element = border_element_old[i];
13909 if (border_element == -1)
13912 // check for change of center element (but change it only once)
13913 if (!change_center_element)
13914 change_center_element =
13915 CheckElementChangeBySide(x, y, center_element, border_element,
13916 CE_TOUCHING_X, border_side);
13918 if (IS_PLAYER(xx, yy))
13920 /* use player element that is initially defined in the level playfield,
13921 not the player element that corresponds to the runtime player number
13922 (example: a level that contains EL_PLAYER_3 as the only player would
13923 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13924 int player_element = PLAYERINFO(xx, yy)->initial_element;
13926 // as element "X" is the player here, check opposite (border) side
13927 CheckElementChangeBySide(x, y, center_element, player_element,
13928 CE_TOUCHING_X, border_side);
13933 void TestIfElementHitsCustomElement(int x, int y, int direction)
13935 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13936 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13937 int hitx = x + dx, hity = y + dy;
13938 int hitting_element = Tile[x][y];
13939 int touched_element;
13941 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13944 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13945 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13947 if (IN_LEV_FIELD(hitx, hity))
13949 int opposite_direction = MV_DIR_OPPOSITE(direction);
13950 int hitting_side = direction;
13951 int touched_side = opposite_direction;
13952 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13953 MovDir[hitx][hity] != direction ||
13954 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13960 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13961 CE_HITTING_X, touched_side);
13963 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13964 CE_HIT_BY_X, hitting_side);
13966 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13967 CE_HIT_BY_SOMETHING, opposite_direction);
13969 if (IS_PLAYER(hitx, hity))
13971 /* use player element that is initially defined in the level playfield,
13972 not the player element that corresponds to the runtime player number
13973 (example: a level that contains EL_PLAYER_3 as the only player would
13974 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13975 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13977 CheckElementChangeBySide(x, y, hitting_element, player_element,
13978 CE_HITTING_X, touched_side);
13983 // "hitting something" is also true when hitting the playfield border
13984 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13985 CE_HITTING_SOMETHING, direction);
13988 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13990 int i, kill_x = -1, kill_y = -1;
13992 int bad_element = -1;
13993 struct XY *test_xy = xy_topdown;
13994 static int test_dir[4] =
14002 for (i = 0; i < NUM_DIRECTIONS; i++)
14004 int test_x, test_y, test_move_dir, test_element;
14006 test_x = good_x + test_xy[i].x;
14007 test_y = good_y + test_xy[i].y;
14009 if (!IN_LEV_FIELD(test_x, test_y))
14013 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14015 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
14017 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
14018 2nd case: DONT_TOUCH style bad thing does not move away from good thing
14020 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
14021 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
14025 bad_element = test_element;
14031 if (kill_x != -1 || kill_y != -1)
14033 if (IS_PLAYER(good_x, good_y))
14035 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
14037 if (player->shield_deadly_time_left > 0 &&
14038 !IS_INDESTRUCTIBLE(bad_element))
14039 Bang(kill_x, kill_y);
14040 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
14041 KillPlayer(player);
14044 Bang(good_x, good_y);
14048 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
14050 int i, kill_x = -1, kill_y = -1;
14051 int bad_element = Tile[bad_x][bad_y];
14052 struct XY *test_xy = xy_topdown;
14053 static int touch_dir[4] =
14055 MV_LEFT | MV_RIGHT,
14060 static int test_dir[4] =
14068 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
14071 for (i = 0; i < NUM_DIRECTIONS; i++)
14073 int test_x, test_y, test_move_dir, test_element;
14075 test_x = bad_x + test_xy[i].x;
14076 test_y = bad_y + test_xy[i].y;
14078 if (!IN_LEV_FIELD(test_x, test_y))
14082 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14084 test_element = Tile[test_x][test_y];
14086 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
14087 2nd case: DONT_TOUCH style bad thing does not move away from good thing
14089 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
14090 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
14092 // good thing is player or penguin that does not move away
14093 if (IS_PLAYER(test_x, test_y))
14095 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14097 if (bad_element == EL_ROBOT && player->is_moving)
14098 continue; // robot does not kill player if he is moving
14100 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
14102 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
14103 continue; // center and border element do not touch
14111 else if (test_element == EL_PENGUIN)
14121 if (kill_x != -1 || kill_y != -1)
14123 if (IS_PLAYER(kill_x, kill_y))
14125 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14127 if (player->shield_deadly_time_left > 0 &&
14128 !IS_INDESTRUCTIBLE(bad_element))
14129 Bang(bad_x, bad_y);
14130 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14131 KillPlayer(player);
14134 Bang(kill_x, kill_y);
14138 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
14140 int bad_element = Tile[bad_x][bad_y];
14141 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
14142 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
14143 int test_x = bad_x + dx, test_y = bad_y + dy;
14144 int test_move_dir, test_element;
14145 int kill_x = -1, kill_y = -1;
14147 if (!IN_LEV_FIELD(test_x, test_y))
14151 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14153 test_element = Tile[test_x][test_y];
14155 if (test_move_dir != bad_move_dir)
14157 // good thing can be player or penguin that does not move away
14158 if (IS_PLAYER(test_x, test_y))
14160 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14162 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
14163 player as being hit when he is moving towards the bad thing, because
14164 the "get hit by" condition would be lost after the player stops) */
14165 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
14166 return; // player moves away from bad thing
14171 else if (test_element == EL_PENGUIN)
14178 if (kill_x != -1 || kill_y != -1)
14180 if (IS_PLAYER(kill_x, kill_y))
14182 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14184 if (player->shield_deadly_time_left > 0 &&
14185 !IS_INDESTRUCTIBLE(bad_element))
14186 Bang(bad_x, bad_y);
14187 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14188 KillPlayer(player);
14191 Bang(kill_x, kill_y);
14195 void TestIfPlayerTouchesBadThing(int x, int y)
14197 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14200 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
14202 TestIfGoodThingHitsBadThing(x, y, move_dir);
14205 void TestIfBadThingTouchesPlayer(int x, int y)
14207 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14210 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
14212 TestIfBadThingHitsGoodThing(x, y, move_dir);
14215 void TestIfFriendTouchesBadThing(int x, int y)
14217 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14220 void TestIfBadThingTouchesFriend(int x, int y)
14222 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14225 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
14227 int i, kill_x = bad_x, kill_y = bad_y;
14228 struct XY *xy = xy_topdown;
14230 for (i = 0; i < NUM_DIRECTIONS; i++)
14234 x = bad_x + xy[i].x;
14235 y = bad_y + xy[i].y;
14236 if (!IN_LEV_FIELD(x, y))
14239 element = Tile[x][y];
14240 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14241 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14249 if (kill_x != bad_x || kill_y != bad_y)
14250 Bang(bad_x, bad_y);
14253 void KillPlayer(struct PlayerInfo *player)
14255 int jx = player->jx, jy = player->jy;
14257 if (!player->active)
14261 Debug("game:playing:KillPlayer",
14262 "0: killed == %d, active == %d, reanimated == %d",
14263 player->killed, player->active, player->reanimated);
14266 /* the following code was introduced to prevent an infinite loop when calling
14268 -> CheckTriggeredElementChangeExt()
14269 -> ExecuteCustomElementAction()
14271 -> (infinitely repeating the above sequence of function calls)
14272 which occurs when killing the player while having a CE with the setting
14273 "kill player X when explosion of <player X>"; the solution using a new
14274 field "player->killed" was chosen for backwards compatibility, although
14275 clever use of the fields "player->active" etc. would probably also work */
14277 if (player->killed)
14281 player->killed = TRUE;
14283 // remove accessible field at the player's position
14284 RemoveField(jx, jy);
14286 // deactivate shield (else Bang()/Explode() would not work right)
14287 player->shield_normal_time_left = 0;
14288 player->shield_deadly_time_left = 0;
14291 Debug("game:playing:KillPlayer",
14292 "1: killed == %d, active == %d, reanimated == %d",
14293 player->killed, player->active, player->reanimated);
14299 Debug("game:playing:KillPlayer",
14300 "2: killed == %d, active == %d, reanimated == %d",
14301 player->killed, player->active, player->reanimated);
14304 if (player->reanimated) // killed player may have been reanimated
14305 player->killed = player->reanimated = FALSE;
14307 BuryPlayer(player);
14310 static void KillPlayerUnlessEnemyProtected(int x, int y)
14312 if (!PLAYER_ENEMY_PROTECTED(x, y))
14313 KillPlayer(PLAYERINFO(x, y));
14316 static void KillPlayerUnlessExplosionProtected(int x, int y)
14318 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14319 KillPlayer(PLAYERINFO(x, y));
14322 void BuryPlayer(struct PlayerInfo *player)
14324 int jx = player->jx, jy = player->jy;
14326 if (!player->active)
14329 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14331 RemovePlayer(player);
14333 player->buried = TRUE;
14335 if (game.all_players_gone)
14336 game.GameOver = TRUE;
14339 void RemovePlayer(struct PlayerInfo *player)
14341 int jx = player->jx, jy = player->jy;
14342 int i, found = FALSE;
14344 player->present = FALSE;
14345 player->active = FALSE;
14347 // required for some CE actions (even if the player is not active anymore)
14348 player->MovPos = 0;
14350 if (!ExplodeField[jx][jy])
14351 StorePlayer[jx][jy] = 0;
14353 if (player->is_moving)
14354 TEST_DrawLevelField(player->last_jx, player->last_jy);
14356 for (i = 0; i < MAX_PLAYERS; i++)
14357 if (stored_player[i].active)
14362 game.all_players_gone = TRUE;
14363 game.GameOver = TRUE;
14366 game.exit_x = game.robot_wheel_x = jx;
14367 game.exit_y = game.robot_wheel_y = jy;
14370 void ExitPlayer(struct PlayerInfo *player)
14372 DrawPlayer(player); // needed here only to cleanup last field
14373 RemovePlayer(player);
14375 if (game.players_still_needed > 0)
14376 game.players_still_needed--;
14379 static void SetFieldForSnapping(int x, int y, int element, int direction,
14380 int player_index_bit)
14382 struct ElementInfo *ei = &element_info[element];
14383 int direction_bit = MV_DIR_TO_BIT(direction);
14384 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14385 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14386 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14388 Tile[x][y] = EL_ELEMENT_SNAPPING;
14389 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14390 MovDir[x][y] = direction;
14391 Store[x][y] = element;
14392 Store2[x][y] = player_index_bit;
14394 ResetGfxAnimation(x, y);
14396 GfxElement[x][y] = element;
14397 GfxAction[x][y] = action;
14398 GfxDir[x][y] = direction;
14399 GfxFrame[x][y] = -1;
14402 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14403 int player_index_bit)
14405 TestIfElementTouchesCustomElement(x, y); // for empty space
14407 if (level.finish_dig_collect)
14409 int dig_side = MV_DIR_OPPOSITE(direction);
14410 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14411 CE_PLAYER_COLLECTS_X);
14413 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14414 player_index_bit, dig_side);
14415 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14416 player_index_bit, dig_side);
14421 =============================================================================
14422 checkDiagonalPushing()
14423 -----------------------------------------------------------------------------
14424 check if diagonal input device direction results in pushing of object
14425 (by checking if the alternative direction is walkable, diggable, ...)
14426 =============================================================================
14429 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14430 int x, int y, int real_dx, int real_dy)
14432 int jx, jy, dx, dy, xx, yy;
14434 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14437 // diagonal direction: check alternative direction
14442 xx = jx + (dx == 0 ? real_dx : 0);
14443 yy = jy + (dy == 0 ? real_dy : 0);
14445 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14449 =============================================================================
14451 -----------------------------------------------------------------------------
14452 x, y: field next to player (non-diagonal) to try to dig to
14453 real_dx, real_dy: direction as read from input device (can be diagonal)
14454 =============================================================================
14457 static int DigField(struct PlayerInfo *player,
14458 int oldx, int oldy, int x, int y,
14459 int real_dx, int real_dy, int mode)
14461 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14462 boolean player_was_pushing = player->is_pushing;
14463 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14464 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14465 int jx = oldx, jy = oldy;
14466 int dx = x - jx, dy = y - jy;
14467 int nextx = x + dx, nexty = y + dy;
14468 int move_direction = (dx == -1 ? MV_LEFT :
14469 dx == +1 ? MV_RIGHT :
14471 dy == +1 ? MV_DOWN : MV_NONE);
14472 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14473 int dig_side = MV_DIR_OPPOSITE(move_direction);
14474 int old_element = Tile[jx][jy];
14475 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14478 if (is_player) // function can also be called by EL_PENGUIN
14480 if (player->MovPos == 0)
14482 player->is_digging = FALSE;
14483 player->is_collecting = FALSE;
14486 if (player->MovPos == 0) // last pushing move finished
14487 player->is_pushing = FALSE;
14489 if (mode == DF_NO_PUSH) // player just stopped pushing
14491 player->is_switching = FALSE;
14492 player->push_delay = -1;
14494 return MP_NO_ACTION;
14497 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14498 old_element = Back[jx][jy];
14500 // in case of element dropped at player position, check background
14501 else if (Back[jx][jy] != EL_EMPTY &&
14502 game.engine_version >= VERSION_IDENT(2,2,0,0))
14503 old_element = Back[jx][jy];
14505 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14506 return MP_NO_ACTION; // field has no opening in this direction
14508 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction))
14509 return MP_NO_ACTION; // field has no opening in this direction
14511 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14515 Tile[jx][jy] = player->artwork_element;
14516 InitMovingField(jx, jy, MV_DOWN);
14517 Store[jx][jy] = EL_ACID;
14518 ContinueMoving(jx, jy);
14519 BuryPlayer(player);
14521 return MP_DONT_RUN_INTO;
14524 if (player_can_move && DONT_RUN_INTO(element))
14526 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14528 return MP_DONT_RUN_INTO;
14531 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14532 return MP_NO_ACTION;
14534 collect_count = element_info[element].collect_count_initial;
14536 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14537 return MP_NO_ACTION;
14539 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14540 player_can_move = player_can_move_or_snap;
14542 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14543 game.engine_version >= VERSION_IDENT(2,2,0,0))
14545 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14546 player->index_bit, dig_side);
14547 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14548 player->index_bit, dig_side);
14550 if (element == EL_DC_LANDMINE)
14553 if (Tile[x][y] != element) // field changed by snapping
14556 return MP_NO_ACTION;
14559 if (player->gravity && is_player && !player->is_auto_moving &&
14560 canFallDown(player) && move_direction != MV_DOWN &&
14561 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14562 return MP_NO_ACTION; // player cannot walk here due to gravity
14564 if (player_can_move &&
14565 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14567 int sound_element = SND_ELEMENT(element);
14568 int sound_action = ACTION_WALKING;
14570 if (IS_RND_GATE(element))
14572 if (!player->key[RND_GATE_NR(element)])
14573 return MP_NO_ACTION;
14575 else if (IS_RND_GATE_GRAY(element))
14577 if (!player->key[RND_GATE_GRAY_NR(element)])
14578 return MP_NO_ACTION;
14580 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14582 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14583 return MP_NO_ACTION;
14585 else if (element == EL_EXIT_OPEN ||
14586 element == EL_EM_EXIT_OPEN ||
14587 element == EL_EM_EXIT_OPENING ||
14588 element == EL_STEEL_EXIT_OPEN ||
14589 element == EL_EM_STEEL_EXIT_OPEN ||
14590 element == EL_EM_STEEL_EXIT_OPENING ||
14591 element == EL_SP_EXIT_OPEN ||
14592 element == EL_SP_EXIT_OPENING)
14594 sound_action = ACTION_PASSING; // player is passing exit
14596 else if (element == EL_EMPTY)
14598 sound_action = ACTION_MOVING; // nothing to walk on
14601 // play sound from background or player, whatever is available
14602 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14603 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14605 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14607 else if (player_can_move &&
14608 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14610 if (!ACCESS_FROM(element, opposite_direction))
14611 return MP_NO_ACTION; // field not accessible from this direction
14613 if (CAN_MOVE(element)) // only fixed elements can be passed!
14614 return MP_NO_ACTION;
14616 if (IS_EM_GATE(element))
14618 if (!player->key[EM_GATE_NR(element)])
14619 return MP_NO_ACTION;
14621 else if (IS_EM_GATE_GRAY(element))
14623 if (!player->key[EM_GATE_GRAY_NR(element)])
14624 return MP_NO_ACTION;
14626 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14628 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14629 return MP_NO_ACTION;
14631 else if (IS_EMC_GATE(element))
14633 if (!player->key[EMC_GATE_NR(element)])
14634 return MP_NO_ACTION;
14636 else if (IS_EMC_GATE_GRAY(element))
14638 if (!player->key[EMC_GATE_GRAY_NR(element)])
14639 return MP_NO_ACTION;
14641 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14643 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14644 return MP_NO_ACTION;
14646 else if (element == EL_DC_GATE_WHITE ||
14647 element == EL_DC_GATE_WHITE_GRAY ||
14648 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14650 if (player->num_white_keys == 0)
14651 return MP_NO_ACTION;
14653 player->num_white_keys--;
14655 else if (IS_SP_PORT(element))
14657 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14658 element == EL_SP_GRAVITY_PORT_RIGHT ||
14659 element == EL_SP_GRAVITY_PORT_UP ||
14660 element == EL_SP_GRAVITY_PORT_DOWN)
14661 player->gravity = !player->gravity;
14662 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14663 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14664 element == EL_SP_GRAVITY_ON_PORT_UP ||
14665 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14666 player->gravity = TRUE;
14667 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14668 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14669 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14670 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14671 player->gravity = FALSE;
14674 // automatically move to the next field with double speed
14675 player->programmed_action = move_direction;
14677 if (player->move_delay_reset_counter == 0)
14679 player->move_delay_reset_counter = 2; // two double speed steps
14681 DOUBLE_PLAYER_SPEED(player);
14684 PlayLevelSoundAction(x, y, ACTION_PASSING);
14686 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14690 if (mode != DF_SNAP)
14692 GfxElement[x][y] = GFX_ELEMENT(element);
14693 player->is_digging = TRUE;
14696 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14698 // use old behaviour for old levels (digging)
14699 if (!level.finish_dig_collect)
14701 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14702 player->index_bit, dig_side);
14704 // if digging triggered player relocation, finish digging tile
14705 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14706 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14709 if (mode == DF_SNAP)
14711 if (level.block_snap_field)
14712 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14714 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14716 // use old behaviour for old levels (snapping)
14717 if (!level.finish_dig_collect)
14718 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14719 player->index_bit, dig_side);
14722 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14726 if (is_player && mode != DF_SNAP)
14728 GfxElement[x][y] = element;
14729 player->is_collecting = TRUE;
14732 if (element == EL_SPEED_PILL)
14734 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14736 else if (element == EL_EXTRA_TIME && level.time > 0)
14738 TimeLeft += level.extra_time;
14740 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14742 DisplayGameControlValues();
14744 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14746 int shield_time = (element == EL_SHIELD_DEADLY ?
14747 level.shield_deadly_time :
14748 level.shield_normal_time);
14750 player->shield_normal_time_left += shield_time;
14751 if (element == EL_SHIELD_DEADLY)
14752 player->shield_deadly_time_left += shield_time;
14754 else if (element == EL_DYNAMITE ||
14755 element == EL_EM_DYNAMITE ||
14756 element == EL_SP_DISK_RED)
14758 if (player->inventory_size < MAX_INVENTORY_SIZE)
14759 player->inventory_element[player->inventory_size++] = element;
14761 DrawGameDoorValues();
14763 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14765 player->dynabomb_count++;
14766 player->dynabombs_left++;
14768 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14770 player->dynabomb_size++;
14772 else if (element == EL_DYNABOMB_INCREASE_POWER)
14774 player->dynabomb_xl = TRUE;
14776 else if (IS_KEY(element))
14778 player->key[KEY_NR(element)] = TRUE;
14780 DrawGameDoorValues();
14782 else if (element == EL_DC_KEY_WHITE)
14784 player->num_white_keys++;
14786 // display white keys?
14787 // DrawGameDoorValues();
14789 else if (IS_ENVELOPE(element))
14791 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14793 if (!wait_for_snapping)
14794 player->show_envelope = element;
14796 else if (element == EL_EMC_LENSES)
14798 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14800 RedrawAllInvisibleElementsForLenses();
14802 else if (element == EL_EMC_MAGNIFIER)
14804 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14806 RedrawAllInvisibleElementsForMagnifier();
14808 else if (IS_DROPPABLE(element) ||
14809 IS_THROWABLE(element)) // can be collected and dropped
14813 if (collect_count == 0)
14814 player->inventory_infinite_element = element;
14816 for (i = 0; i < collect_count; i++)
14817 if (player->inventory_size < MAX_INVENTORY_SIZE)
14818 player->inventory_element[player->inventory_size++] = element;
14820 DrawGameDoorValues();
14822 else if (collect_count > 0)
14824 game.gems_still_needed -= collect_count;
14825 if (game.gems_still_needed < 0)
14826 game.gems_still_needed = 0;
14828 game.snapshot.collected_item = TRUE;
14830 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14832 DisplayGameControlValues();
14835 RaiseScoreElement(element);
14836 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14838 // use old behaviour for old levels (collecting)
14839 if (!level.finish_dig_collect && is_player)
14841 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14842 player->index_bit, dig_side);
14844 // if collecting triggered player relocation, finish collecting tile
14845 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14846 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14849 if (mode == DF_SNAP)
14851 if (level.block_snap_field)
14852 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14854 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14856 // use old behaviour for old levels (snapping)
14857 if (!level.finish_dig_collect)
14858 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14859 player->index_bit, dig_side);
14862 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14864 if (mode == DF_SNAP && element != EL_BD_ROCK)
14865 return MP_NO_ACTION;
14867 if (CAN_FALL(element) && dy)
14868 return MP_NO_ACTION;
14870 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14871 !(element == EL_SPRING && level.use_spring_bug))
14872 return MP_NO_ACTION;
14874 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14875 ((move_direction & MV_VERTICAL &&
14876 ((element_info[element].move_pattern & MV_LEFT &&
14877 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14878 (element_info[element].move_pattern & MV_RIGHT &&
14879 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14880 (move_direction & MV_HORIZONTAL &&
14881 ((element_info[element].move_pattern & MV_UP &&
14882 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14883 (element_info[element].move_pattern & MV_DOWN &&
14884 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14885 return MP_NO_ACTION;
14887 // do not push elements already moving away faster than player
14888 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14889 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14890 return MP_NO_ACTION;
14892 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14894 if (player->push_delay_value == -1 || !player_was_pushing)
14895 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14897 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14899 if (player->push_delay_value == -1)
14900 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14902 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14904 if (!player->is_pushing)
14905 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14908 player->is_pushing = TRUE;
14909 player->is_active = TRUE;
14911 if (!(IN_LEV_FIELD(nextx, nexty) &&
14912 (IS_FREE(nextx, nexty) ||
14913 (IS_SB_ELEMENT(element) &&
14914 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14915 (IS_CUSTOM_ELEMENT(element) &&
14916 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14917 return MP_NO_ACTION;
14919 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14920 return MP_NO_ACTION;
14922 if (player->push_delay == -1) // new pushing; restart delay
14923 player->push_delay = 0;
14925 if (player->push_delay < player->push_delay_value &&
14926 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14927 element != EL_SPRING && element != EL_BALLOON)
14929 // make sure that there is no move delay before next try to push
14930 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14931 player->move_delay = 0;
14933 return MP_NO_ACTION;
14936 if (IS_CUSTOM_ELEMENT(element) &&
14937 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14939 if (!DigFieldByCE(nextx, nexty, element))
14940 return MP_NO_ACTION;
14943 if (IS_SB_ELEMENT(element))
14945 boolean sokoban_task_solved = FALSE;
14947 if (element == EL_SOKOBAN_FIELD_FULL)
14949 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14951 IncrementSokobanFieldsNeeded();
14952 IncrementSokobanObjectsNeeded();
14955 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14957 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14959 DecrementSokobanFieldsNeeded();
14960 DecrementSokobanObjectsNeeded();
14962 // sokoban object was pushed from empty field to sokoban field
14963 if (Back[x][y] == EL_EMPTY)
14964 sokoban_task_solved = TRUE;
14967 Tile[x][y] = EL_SOKOBAN_OBJECT;
14969 if (Back[x][y] == Back[nextx][nexty])
14970 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14971 else if (Back[x][y] != 0)
14972 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14975 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14978 if (sokoban_task_solved &&
14979 game.sokoban_fields_still_needed == 0 &&
14980 game.sokoban_objects_still_needed == 0 &&
14981 level.auto_exit_sokoban)
14983 game.players_still_needed = 0;
14987 PlaySound(SND_GAME_SOKOBAN_SOLVING);
14991 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14993 InitMovingField(x, y, move_direction);
14994 GfxAction[x][y] = ACTION_PUSHING;
14996 if (mode == DF_SNAP)
14997 ContinueMoving(x, y);
14999 MovPos[x][y] = (dx != 0 ? dx : dy);
15001 Pushed[x][y] = TRUE;
15002 Pushed[nextx][nexty] = TRUE;
15004 if (game.engine_version < VERSION_IDENT(2,2,0,7))
15005 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
15007 player->push_delay_value = -1; // get new value later
15009 // check for element change _after_ element has been pushed
15010 if (game.use_change_when_pushing_bug)
15012 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
15013 player->index_bit, dig_side);
15014 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
15015 player->index_bit, dig_side);
15018 else if (IS_SWITCHABLE(element))
15020 if (PLAYER_SWITCHING(player, x, y))
15022 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15023 player->index_bit, dig_side);
15028 player->is_switching = TRUE;
15029 player->switch_x = x;
15030 player->switch_y = y;
15032 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15034 if (element == EL_ROBOT_WHEEL)
15036 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
15038 game.robot_wheel_x = x;
15039 game.robot_wheel_y = y;
15040 game.robot_wheel_active = TRUE;
15042 TEST_DrawLevelField(x, y);
15044 else if (element == EL_SP_TERMINAL)
15048 SCAN_PLAYFIELD(xx, yy)
15050 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
15054 else if (Tile[xx][yy] == EL_SP_TERMINAL)
15056 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
15058 ResetGfxAnimation(xx, yy);
15059 TEST_DrawLevelField(xx, yy);
15063 else if (IS_BELT_SWITCH(element))
15065 ToggleBeltSwitch(x, y);
15067 else if (element == EL_SWITCHGATE_SWITCH_UP ||
15068 element == EL_SWITCHGATE_SWITCH_DOWN ||
15069 element == EL_DC_SWITCHGATE_SWITCH_UP ||
15070 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
15072 ToggleSwitchgateSwitch();
15074 else if (element == EL_LIGHT_SWITCH ||
15075 element == EL_LIGHT_SWITCH_ACTIVE)
15077 ToggleLightSwitch(x, y);
15079 else if (element == EL_TIMEGATE_SWITCH ||
15080 element == EL_DC_TIMEGATE_SWITCH)
15082 ActivateTimegateSwitch(x, y);
15084 else if (element == EL_BALLOON_SWITCH_LEFT ||
15085 element == EL_BALLOON_SWITCH_RIGHT ||
15086 element == EL_BALLOON_SWITCH_UP ||
15087 element == EL_BALLOON_SWITCH_DOWN ||
15088 element == EL_BALLOON_SWITCH_NONE ||
15089 element == EL_BALLOON_SWITCH_ANY)
15091 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
15092 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
15093 element == EL_BALLOON_SWITCH_UP ? MV_UP :
15094 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
15095 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
15098 else if (element == EL_LAMP)
15100 Tile[x][y] = EL_LAMP_ACTIVE;
15101 game.lights_still_needed--;
15103 ResetGfxAnimation(x, y);
15104 TEST_DrawLevelField(x, y);
15106 else if (element == EL_TIME_ORB_FULL)
15108 Tile[x][y] = EL_TIME_ORB_EMPTY;
15110 if (level.time > 0 || level.use_time_orb_bug)
15112 TimeLeft += level.time_orb_time;
15113 game.no_level_time_limit = FALSE;
15115 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
15117 DisplayGameControlValues();
15120 ResetGfxAnimation(x, y);
15121 TEST_DrawLevelField(x, y);
15123 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
15124 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15128 game.ball_active = !game.ball_active;
15130 SCAN_PLAYFIELD(xx, yy)
15132 int e = Tile[xx][yy];
15134 if (game.ball_active)
15136 if (e == EL_EMC_MAGIC_BALL)
15137 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
15138 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
15139 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
15143 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
15144 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
15145 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15146 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
15151 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15152 player->index_bit, dig_side);
15154 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15155 player->index_bit, dig_side);
15157 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15158 player->index_bit, dig_side);
15164 if (!PLAYER_SWITCHING(player, x, y))
15166 player->is_switching = TRUE;
15167 player->switch_x = x;
15168 player->switch_y = y;
15170 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
15171 player->index_bit, dig_side);
15172 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15173 player->index_bit, dig_side);
15175 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
15176 player->index_bit, dig_side);
15177 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15178 player->index_bit, dig_side);
15181 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
15182 player->index_bit, dig_side);
15183 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15184 player->index_bit, dig_side);
15186 return MP_NO_ACTION;
15189 player->push_delay = -1;
15191 if (is_player) // function can also be called by EL_PENGUIN
15193 if (Tile[x][y] != element) // really digged/collected something
15195 player->is_collecting = !player->is_digging;
15196 player->is_active = TRUE;
15198 player->last_removed_element = element;
15205 static boolean DigFieldByCE(int x, int y, int digging_element)
15207 int element = Tile[x][y];
15209 if (!IS_FREE(x, y))
15211 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
15212 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
15215 // no element can dig solid indestructible elements
15216 if (IS_INDESTRUCTIBLE(element) &&
15217 !IS_DIGGABLE(element) &&
15218 !IS_COLLECTIBLE(element))
15221 if (AmoebaNr[x][y] &&
15222 (element == EL_AMOEBA_FULL ||
15223 element == EL_BD_AMOEBA ||
15224 element == EL_AMOEBA_GROWING))
15226 AmoebaCnt[AmoebaNr[x][y]]--;
15227 AmoebaCnt2[AmoebaNr[x][y]]--;
15230 if (IS_MOVING(x, y))
15231 RemoveMovingField(x, y);
15235 TEST_DrawLevelField(x, y);
15238 // if digged element was about to explode, prevent the explosion
15239 ExplodeField[x][y] = EX_TYPE_NONE;
15241 PlayLevelSoundAction(x, y, action);
15244 Store[x][y] = EL_EMPTY;
15246 // this makes it possible to leave the removed element again
15247 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15248 Store[x][y] = element;
15253 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15255 int jx = player->jx, jy = player->jy;
15256 int x = jx + dx, y = jy + dy;
15257 int snap_direction = (dx == -1 ? MV_LEFT :
15258 dx == +1 ? MV_RIGHT :
15260 dy == +1 ? MV_DOWN : MV_NONE);
15261 boolean can_continue_snapping = (level.continuous_snapping &&
15262 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15264 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15267 if (!player->active || !IN_LEV_FIELD(x, y))
15275 if (player->MovPos == 0)
15276 player->is_pushing = FALSE;
15278 player->is_snapping = FALSE;
15280 if (player->MovPos == 0)
15282 player->is_moving = FALSE;
15283 player->is_digging = FALSE;
15284 player->is_collecting = FALSE;
15290 // prevent snapping with already pressed snap key when not allowed
15291 if (player->is_snapping && !can_continue_snapping)
15294 player->MovDir = snap_direction;
15296 if (player->MovPos == 0)
15298 player->is_moving = FALSE;
15299 player->is_digging = FALSE;
15300 player->is_collecting = FALSE;
15303 player->is_dropping = FALSE;
15304 player->is_dropping_pressed = FALSE;
15305 player->drop_pressed_delay = 0;
15307 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15310 player->is_snapping = TRUE;
15311 player->is_active = TRUE;
15313 if (player->MovPos == 0)
15315 player->is_moving = FALSE;
15316 player->is_digging = FALSE;
15317 player->is_collecting = FALSE;
15320 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15321 TEST_DrawLevelField(player->last_jx, player->last_jy);
15323 TEST_DrawLevelField(x, y);
15328 static boolean DropElement(struct PlayerInfo *player)
15330 int old_element, new_element;
15331 int dropx = player->jx, dropy = player->jy;
15332 int drop_direction = player->MovDir;
15333 int drop_side = drop_direction;
15334 int drop_element = get_next_dropped_element(player);
15336 /* do not drop an element on top of another element; when holding drop key
15337 pressed without moving, dropped element must move away before the next
15338 element can be dropped (this is especially important if the next element
15339 is dynamite, which can be placed on background for historical reasons) */
15340 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15343 if (IS_THROWABLE(drop_element))
15345 dropx += GET_DX_FROM_DIR(drop_direction);
15346 dropy += GET_DY_FROM_DIR(drop_direction);
15348 if (!IN_LEV_FIELD(dropx, dropy))
15352 old_element = Tile[dropx][dropy]; // old element at dropping position
15353 new_element = drop_element; // default: no change when dropping
15355 // check if player is active, not moving and ready to drop
15356 if (!player->active || player->MovPos || player->drop_delay > 0)
15359 // check if player has anything that can be dropped
15360 if (new_element == EL_UNDEFINED)
15363 // only set if player has anything that can be dropped
15364 player->is_dropping_pressed = TRUE;
15366 // check if drop key was pressed long enough for EM style dynamite
15367 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15370 // check if anything can be dropped at the current position
15371 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15374 // collected custom elements can only be dropped on empty fields
15375 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15378 if (old_element != EL_EMPTY)
15379 Back[dropx][dropy] = old_element; // store old element on this field
15381 ResetGfxAnimation(dropx, dropy);
15382 ResetRandomAnimationValue(dropx, dropy);
15384 if (player->inventory_size > 0 ||
15385 player->inventory_infinite_element != EL_UNDEFINED)
15387 if (player->inventory_size > 0)
15389 player->inventory_size--;
15391 DrawGameDoorValues();
15393 if (new_element == EL_DYNAMITE)
15394 new_element = EL_DYNAMITE_ACTIVE;
15395 else if (new_element == EL_EM_DYNAMITE)
15396 new_element = EL_EM_DYNAMITE_ACTIVE;
15397 else if (new_element == EL_SP_DISK_RED)
15398 new_element = EL_SP_DISK_RED_ACTIVE;
15401 Tile[dropx][dropy] = new_element;
15403 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15404 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15405 el2img(Tile[dropx][dropy]), 0);
15407 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15409 // needed if previous element just changed to "empty" in the last frame
15410 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15412 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15413 player->index_bit, drop_side);
15414 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15416 player->index_bit, drop_side);
15418 TestIfElementTouchesCustomElement(dropx, dropy);
15420 else // player is dropping a dyna bomb
15422 player->dynabombs_left--;
15424 Tile[dropx][dropy] = new_element;
15426 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15427 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15428 el2img(Tile[dropx][dropy]), 0);
15430 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15433 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15434 InitField_WithBug1(dropx, dropy, FALSE);
15436 new_element = Tile[dropx][dropy]; // element might have changed
15438 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15439 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15441 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15442 MovDir[dropx][dropy] = drop_direction;
15444 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15446 // do not cause impact style collision by dropping elements that can fall
15447 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15450 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15451 player->is_dropping = TRUE;
15453 player->drop_pressed_delay = 0;
15454 player->is_dropping_pressed = FALSE;
15456 player->drop_x = dropx;
15457 player->drop_y = dropy;
15462 // ----------------------------------------------------------------------------
15463 // game sound playing functions
15464 // ----------------------------------------------------------------------------
15466 static int *loop_sound_frame = NULL;
15467 static int *loop_sound_volume = NULL;
15469 void InitPlayLevelSound(void)
15471 int num_sounds = getSoundListSize();
15473 checked_free(loop_sound_frame);
15474 checked_free(loop_sound_volume);
15476 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15477 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15480 static void PlayLevelSoundExt(int x, int y, int nr, boolean is_loop_sound)
15482 int sx = SCREENX(x), sy = SCREENY(y);
15483 int volume, stereo_position;
15484 int max_distance = 8;
15485 int type = (is_loop_sound ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15487 if ((!setup.sound_simple && !is_loop_sound) ||
15488 (!setup.sound_loops && is_loop_sound))
15491 if (!IN_LEV_FIELD(x, y) ||
15492 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15493 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15496 volume = SOUND_MAX_VOLUME;
15498 if (!IN_SCR_FIELD(sx, sy))
15500 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15501 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15503 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15506 stereo_position = (SOUND_MAX_LEFT +
15507 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15508 (SCR_FIELDX + 2 * max_distance));
15512 /* This assures that quieter loop sounds do not overwrite louder ones,
15513 while restarting sound volume comparison with each new game frame. */
15515 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15518 loop_sound_volume[nr] = volume;
15519 loop_sound_frame[nr] = FrameCounter;
15522 PlaySoundExt(nr, volume, stereo_position, type);
15525 static void PlayLevelSound(int x, int y, int nr)
15527 PlayLevelSoundExt(x, y, nr, IS_LOOP_SOUND(nr));
15530 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15532 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15533 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15534 y < LEVELY(BY1) ? LEVELY(BY1) :
15535 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15539 static void PlayLevelSoundAction(int x, int y, int action)
15541 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15544 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15546 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15548 if (sound_effect != SND_UNDEFINED)
15549 PlayLevelSound(x, y, sound_effect);
15552 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15555 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15557 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15558 PlayLevelSound(x, y, sound_effect);
15561 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15563 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15565 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15566 PlayLevelSound(x, y, sound_effect);
15569 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15571 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15573 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15574 StopSound(sound_effect);
15577 static int getLevelMusicNr(void)
15579 int level_pos = level_nr - leveldir_current->first_level;
15581 if (levelset.music[level_nr] != MUS_UNDEFINED)
15582 return levelset.music[level_nr]; // from config file
15584 return MAP_NOCONF_MUSIC(level_pos); // from music dir
15587 static void FadeLevelSounds(void)
15592 static void FadeLevelMusic(void)
15594 int music_nr = getLevelMusicNr();
15595 char *curr_music = getCurrentlyPlayingMusicFilename();
15596 char *next_music = getMusicInfoEntryFilename(music_nr);
15598 if (!strEqual(curr_music, next_music))
15602 void FadeLevelSoundsAndMusic(void)
15608 static void PlayLevelMusic(void)
15610 int music_nr = getLevelMusicNr();
15611 char *curr_music = getCurrentlyPlayingMusicFilename();
15612 char *next_music = getMusicInfoEntryFilename(music_nr);
15614 if (!strEqual(curr_music, next_music))
15615 PlayMusicLoop(music_nr);
15618 static int getSoundAction_BD(int sample)
15622 case GD_S_STONE_PUSHING:
15623 case GD_S_MEGA_STONE_PUSHING:
15624 case GD_S_FLYING_STONE_PUSHING:
15625 case GD_S_WAITING_STONE_PUSHING:
15626 case GD_S_CHASING_STONE_PUSHING:
15627 case GD_S_NUT_PUSHING:
15628 case GD_S_NITRO_PACK_PUSHING:
15629 case GD_S_BLADDER_PUSHING:
15630 case GD_S_BOX_PUSHING:
15631 return ACTION_PUSHING;
15633 case GD_S_STONE_FALLING:
15634 case GD_S_MEGA_STONE_FALLING:
15635 case GD_S_FLYING_STONE_FALLING:
15636 case GD_S_NUT_FALLING:
15637 case GD_S_DIRT_BALL_FALLING:
15638 case GD_S_DIRT_LOOSE_FALLING:
15639 case GD_S_NITRO_PACK_FALLING:
15640 case GD_S_FALLING_WALL_FALLING:
15641 return ACTION_FALLING;
15643 case GD_S_STONE_IMPACT:
15644 case GD_S_MEGA_STONE_IMPACT:
15645 case GD_S_FLYING_STONE_IMPACT:
15646 case GD_S_NUT_IMPACT:
15647 case GD_S_DIRT_BALL_IMPACT:
15648 case GD_S_DIRT_LOOSE_IMPACT:
15649 case GD_S_NITRO_PACK_IMPACT:
15650 case GD_S_FALLING_WALL_IMPACT:
15651 return ACTION_IMPACT;
15653 case GD_S_NUT_CRACKING:
15654 return ACTION_BREAKING;
15656 case GD_S_EXPANDING_WALL:
15657 case GD_S_WALL_REAPPEARING:
15660 case GD_S_ACID_SPREADING:
15661 return ACTION_GROWING;
15663 case GD_S_DIAMOND_COLLECTING:
15664 case GD_S_FLYING_DIAMOND_COLLECTING:
15665 case GD_S_SKELETON_COLLECTING:
15666 case GD_S_PNEUMATIC_COLLECTING:
15667 case GD_S_BOMB_COLLECTING:
15668 case GD_S_CLOCK_COLLECTING:
15669 case GD_S_SWEET_COLLECTING:
15670 case GD_S_KEY_COLLECTING:
15671 case GD_S_DIAMOND_KEY_COLLECTING:
15672 return ACTION_COLLECTING;
15674 case GD_S_BOMB_PLACING:
15675 case GD_S_REPLICATOR:
15676 return ACTION_DROPPING;
15678 case GD_S_BLADDER_MOVING:
15679 return ACTION_MOVING;
15681 case GD_S_BLADDER_SPENDER:
15682 case GD_S_BLADDER_CONVERTING:
15683 case GD_S_GRAVITY_CHANGING:
15684 return ACTION_CHANGING;
15686 case GD_S_BITER_EATING:
15687 return ACTION_EATING;
15689 case GD_S_DOOR_OPENING:
15690 case GD_S_CRACKING:
15691 return ACTION_OPENING;
15693 case GD_S_DIRT_WALKING:
15694 return ACTION_DIGGING;
15696 case GD_S_EMPTY_WALKING:
15697 return ACTION_WALKING;
15699 case GD_S_SWITCH_BITER:
15700 case GD_S_SWITCH_CREATURES:
15701 case GD_S_SWITCH_GRAVITY:
15702 case GD_S_SWITCH_EXPANDING:
15703 case GD_S_SWITCH_CONVEYOR:
15704 case GD_S_SWITCH_REPLICATOR:
15705 case GD_S_STIRRING:
15706 return ACTION_ACTIVATING;
15708 case GD_S_TELEPORTER:
15709 return ACTION_PASSING;
15711 case GD_S_EXPLODING:
15712 case GD_S_BOMB_EXPLODING:
15713 case GD_S_GHOST_EXPLODING:
15714 case GD_S_VOODOO_EXPLODING:
15715 case GD_S_NITRO_PACK_EXPLODING:
15716 return ACTION_EXPLODING;
15718 case GD_S_COVERING:
15720 case GD_S_MAGIC_WALL:
15721 case GD_S_PNEUMATIC_HAMMER:
15723 return ACTION_ACTIVE;
15725 case GD_S_DIAMOND_FALLING_RANDOM:
15726 case GD_S_DIAMOND_FALLING_1:
15727 case GD_S_DIAMOND_FALLING_2:
15728 case GD_S_DIAMOND_FALLING_3:
15729 case GD_S_DIAMOND_FALLING_4:
15730 case GD_S_DIAMOND_FALLING_5:
15731 case GD_S_DIAMOND_FALLING_6:
15732 case GD_S_DIAMOND_FALLING_7:
15733 case GD_S_DIAMOND_FALLING_8:
15734 case GD_S_DIAMOND_IMPACT_RANDOM:
15735 case GD_S_DIAMOND_IMPACT_1:
15736 case GD_S_DIAMOND_IMPACT_2:
15737 case GD_S_DIAMOND_IMPACT_3:
15738 case GD_S_DIAMOND_IMPACT_4:
15739 case GD_S_DIAMOND_IMPACT_5:
15740 case GD_S_DIAMOND_IMPACT_6:
15741 case GD_S_DIAMOND_IMPACT_7:
15742 case GD_S_DIAMOND_IMPACT_8:
15743 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15744 case GD_S_FLYING_DIAMOND_FALLING_1:
15745 case GD_S_FLYING_DIAMOND_FALLING_2:
15746 case GD_S_FLYING_DIAMOND_FALLING_3:
15747 case GD_S_FLYING_DIAMOND_FALLING_4:
15748 case GD_S_FLYING_DIAMOND_FALLING_5:
15749 case GD_S_FLYING_DIAMOND_FALLING_6:
15750 case GD_S_FLYING_DIAMOND_FALLING_7:
15751 case GD_S_FLYING_DIAMOND_FALLING_8:
15752 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15753 case GD_S_FLYING_DIAMOND_IMPACT_1:
15754 case GD_S_FLYING_DIAMOND_IMPACT_2:
15755 case GD_S_FLYING_DIAMOND_IMPACT_3:
15756 case GD_S_FLYING_DIAMOND_IMPACT_4:
15757 case GD_S_FLYING_DIAMOND_IMPACT_5:
15758 case GD_S_FLYING_DIAMOND_IMPACT_6:
15759 case GD_S_FLYING_DIAMOND_IMPACT_7:
15760 case GD_S_FLYING_DIAMOND_IMPACT_8:
15761 case GD_S_TIMEOUT_0:
15762 case GD_S_TIMEOUT_1:
15763 case GD_S_TIMEOUT_2:
15764 case GD_S_TIMEOUT_3:
15765 case GD_S_TIMEOUT_4:
15766 case GD_S_TIMEOUT_5:
15767 case GD_S_TIMEOUT_6:
15768 case GD_S_TIMEOUT_7:
15769 case GD_S_TIMEOUT_8:
15770 case GD_S_TIMEOUT_9:
15771 case GD_S_TIMEOUT_10:
15772 case GD_S_BONUS_LIFE:
15773 // trigger special post-processing (and force sound to be non-looping)
15774 return ACTION_OTHER;
15776 case GD_S_AMOEBA_MAGIC:
15777 case GD_S_FINISHED:
15778 // trigger special post-processing (and force sound to be looping)
15779 return ACTION_DEFAULT;
15782 return ACTION_DEFAULT;
15786 static int getSoundEffect_BD(int element_bd, int sample)
15788 int sound_action = getSoundAction_BD(sample);
15789 int sound_effect = element_info[SND_ELEMENT(element_bd)].sound[sound_action];
15793 if (sound_action != ACTION_OTHER &&
15794 sound_action != ACTION_DEFAULT)
15795 return sound_effect;
15797 // special post-processing for some sounds
15800 case GD_S_DIAMOND_FALLING_RANDOM:
15801 case GD_S_DIAMOND_FALLING_1:
15802 case GD_S_DIAMOND_FALLING_2:
15803 case GD_S_DIAMOND_FALLING_3:
15804 case GD_S_DIAMOND_FALLING_4:
15805 case GD_S_DIAMOND_FALLING_5:
15806 case GD_S_DIAMOND_FALLING_6:
15807 case GD_S_DIAMOND_FALLING_7:
15808 case GD_S_DIAMOND_FALLING_8:
15809 nr = (sample == GD_S_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15810 sample - GD_S_DIAMOND_FALLING_1);
15811 sound_effect = SND_BDX_DIAMOND_FALLING_RANDOM_1 + nr;
15813 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15814 sound_effect = SND_BDX_DIAMOND_FALLING;
15817 case GD_S_DIAMOND_IMPACT_RANDOM:
15818 case GD_S_DIAMOND_IMPACT_1:
15819 case GD_S_DIAMOND_IMPACT_2:
15820 case GD_S_DIAMOND_IMPACT_3:
15821 case GD_S_DIAMOND_IMPACT_4:
15822 case GD_S_DIAMOND_IMPACT_5:
15823 case GD_S_DIAMOND_IMPACT_6:
15824 case GD_S_DIAMOND_IMPACT_7:
15825 case GD_S_DIAMOND_IMPACT_8:
15826 nr = (sample == GD_S_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15827 sample - GD_S_DIAMOND_IMPACT_1);
15828 sound_effect = SND_BDX_DIAMOND_IMPACT_RANDOM_1 + nr;
15830 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15831 sound_effect = SND_BDX_DIAMOND_IMPACT;
15834 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15835 case GD_S_FLYING_DIAMOND_FALLING_1:
15836 case GD_S_FLYING_DIAMOND_FALLING_2:
15837 case GD_S_FLYING_DIAMOND_FALLING_3:
15838 case GD_S_FLYING_DIAMOND_FALLING_4:
15839 case GD_S_FLYING_DIAMOND_FALLING_5:
15840 case GD_S_FLYING_DIAMOND_FALLING_6:
15841 case GD_S_FLYING_DIAMOND_FALLING_7:
15842 case GD_S_FLYING_DIAMOND_FALLING_8:
15843 nr = (sample == GD_S_FLYING_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15844 sample - GD_S_FLYING_DIAMOND_FALLING_1);
15845 sound_effect = SND_BDX_FLYING_DIAMOND_FALLING_RANDOM_1 + nr;
15847 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15848 sound_effect = SND_BDX_FLYING_DIAMOND_FALLING;
15851 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15852 case GD_S_FLYING_DIAMOND_IMPACT_1:
15853 case GD_S_FLYING_DIAMOND_IMPACT_2:
15854 case GD_S_FLYING_DIAMOND_IMPACT_3:
15855 case GD_S_FLYING_DIAMOND_IMPACT_4:
15856 case GD_S_FLYING_DIAMOND_IMPACT_5:
15857 case GD_S_FLYING_DIAMOND_IMPACT_6:
15858 case GD_S_FLYING_DIAMOND_IMPACT_7:
15859 case GD_S_FLYING_DIAMOND_IMPACT_8:
15860 nr = (sample == GD_S_FLYING_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15861 sample - GD_S_FLYING_DIAMOND_IMPACT_1);
15862 sound_effect = SND_BDX_FLYING_DIAMOND_IMPACT_RANDOM_1 + nr;
15864 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15865 sound_effect = SND_BDX_FLYING_DIAMOND_IMPACT;
15868 case GD_S_TIMEOUT_0:
15869 case GD_S_TIMEOUT_1:
15870 case GD_S_TIMEOUT_2:
15871 case GD_S_TIMEOUT_3:
15872 case GD_S_TIMEOUT_4:
15873 case GD_S_TIMEOUT_5:
15874 case GD_S_TIMEOUT_6:
15875 case GD_S_TIMEOUT_7:
15876 case GD_S_TIMEOUT_8:
15877 case GD_S_TIMEOUT_9:
15878 case GD_S_TIMEOUT_10:
15879 nr = sample - GD_S_TIMEOUT_0;
15880 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME_0 + nr;
15882 if (getSoundInfoEntryFilename(sound_effect) == NULL && sample != GD_S_TIMEOUT_0)
15883 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME;
15886 case GD_S_BONUS_LIFE:
15887 sound_effect = SND_GAME_HEALTH_BONUS;
15890 case GD_S_AMOEBA_MAGIC:
15891 sound_effect = SND_BDX_AMOEBA_1_OTHER;
15894 case GD_S_FINISHED:
15895 sound_effect = SND_GAME_LEVELTIME_BONUS;
15899 sound_effect = SND_UNDEFINED;
15903 return sound_effect;
15906 void PlayLevelSound_BD(int xx, int yy, int element_bd, int sample)
15908 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15909 int sound_effect = getSoundEffect_BD(element, sample);
15910 int sound_action = getSoundAction_BD(sample);
15911 boolean is_loop_sound = IS_LOOP_SOUND(sound_effect);
15913 int x = xx - offset;
15914 int y = yy - offset;
15916 // some sound actions are always looping in native BD game engine
15917 if (sound_action == ACTION_DEFAULT)
15918 is_loop_sound = TRUE;
15920 // some sound actions are always non-looping in native BD game engine
15921 if (sound_action == ACTION_FALLING ||
15922 sound_action == ACTION_MOVING ||
15923 sound_action == ACTION_OTHER)
15924 is_loop_sound = FALSE;
15926 if (sound_effect != SND_UNDEFINED)
15927 PlayLevelSoundExt(x, y, sound_effect, is_loop_sound);
15930 void StopSound_BD(int element_bd, int sample)
15932 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15933 int sound_effect = getSoundEffect_BD(element, sample);
15935 if (sound_effect != SND_UNDEFINED)
15936 StopSound(sound_effect);
15939 boolean isSoundPlaying_BD(int element_bd, int sample)
15941 int element = (element_bd > -1 ? map_element_BD_to_RND_game(element_bd) : 0);
15942 int sound_effect = getSoundEffect_BD(element, sample);
15944 if (sound_effect != SND_UNDEFINED)
15945 return isSoundPlaying(sound_effect);
15950 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15952 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15954 int x = xx - offset;
15955 int y = yy - offset;
15960 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15964 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15968 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15972 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15976 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15980 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15984 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15987 case SOUND_android_clone:
15988 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15991 case SOUND_android_move:
15992 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15996 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
16000 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
16004 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
16007 case SOUND_eater_eat:
16008 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
16012 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
16015 case SOUND_collect:
16016 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
16019 case SOUND_diamond:
16020 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
16024 // !!! CHECK THIS !!!
16026 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
16028 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
16032 case SOUND_wonderfall:
16033 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
16037 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
16041 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
16045 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
16049 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
16053 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16057 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
16061 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16065 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16068 case SOUND_exit_open:
16069 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
16072 case SOUND_exit_leave:
16073 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16076 case SOUND_dynamite:
16077 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16081 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16085 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
16089 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16093 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
16097 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
16101 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
16105 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
16110 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
16112 int element = map_element_SP_to_RND(element_sp);
16113 int action = map_action_SP_to_RND(action_sp);
16114 int offset = (setup.sp_show_border_elements ? 0 : 1);
16115 int x = xx - offset;
16116 int y = yy - offset;
16118 PlayLevelSoundElementAction(x, y, element, action);
16121 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
16123 int element = map_element_MM_to_RND(element_mm);
16124 int action = map_action_MM_to_RND(action_mm);
16126 int x = xx - offset;
16127 int y = yy - offset;
16129 if (!IS_MM_ELEMENT(element))
16130 element = EL_MM_DEFAULT;
16132 PlayLevelSoundElementAction(x, y, element, action);
16135 void PlaySound_MM(int sound_mm)
16137 int sound = map_sound_MM_to_RND(sound_mm);
16139 if (sound == SND_UNDEFINED)
16145 void PlaySoundLoop_MM(int sound_mm)
16147 int sound = map_sound_MM_to_RND(sound_mm);
16149 if (sound == SND_UNDEFINED)
16152 PlaySoundLoop(sound);
16155 void StopSound_MM(int sound_mm)
16157 int sound = map_sound_MM_to_RND(sound_mm);
16159 if (sound == SND_UNDEFINED)
16165 void RaiseScore(int value)
16167 game.score += value;
16169 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
16171 DisplayGameControlValues();
16174 void RaiseScoreElement(int element)
16179 case EL_BD_DIAMOND:
16180 case EL_EMERALD_YELLOW:
16181 case EL_EMERALD_RED:
16182 case EL_EMERALD_PURPLE:
16183 case EL_SP_INFOTRON:
16184 RaiseScore(level.score[SC_EMERALD]);
16187 RaiseScore(level.score[SC_DIAMOND]);
16190 RaiseScore(level.score[SC_CRYSTAL]);
16193 RaiseScore(level.score[SC_PEARL]);
16196 case EL_BD_BUTTERFLY:
16197 case EL_SP_ELECTRON:
16198 RaiseScore(level.score[SC_BUG]);
16201 case EL_BD_FIREFLY:
16202 case EL_SP_SNIKSNAK:
16203 RaiseScore(level.score[SC_SPACESHIP]);
16206 case EL_DARK_YAMYAM:
16207 RaiseScore(level.score[SC_YAMYAM]);
16210 RaiseScore(level.score[SC_ROBOT]);
16213 RaiseScore(level.score[SC_PACMAN]);
16216 RaiseScore(level.score[SC_NUT]);
16219 case EL_EM_DYNAMITE:
16220 case EL_SP_DISK_RED:
16221 case EL_DYNABOMB_INCREASE_NUMBER:
16222 case EL_DYNABOMB_INCREASE_SIZE:
16223 case EL_DYNABOMB_INCREASE_POWER:
16224 RaiseScore(level.score[SC_DYNAMITE]);
16226 case EL_SHIELD_NORMAL:
16227 case EL_SHIELD_DEADLY:
16228 RaiseScore(level.score[SC_SHIELD]);
16230 case EL_EXTRA_TIME:
16231 RaiseScore(level.extra_time_score);
16245 case EL_DC_KEY_WHITE:
16246 RaiseScore(level.score[SC_KEY]);
16249 RaiseScore(element_info[element].collect_score);
16254 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
16256 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
16260 // prevent short reactivation of overlay buttons while closing door
16261 SetOverlayActive(FALSE);
16262 UnmapGameButtons();
16264 // door may still be open due to skipped or envelope style request
16265 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
16268 if (network.enabled)
16270 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
16274 // when using BD game engine, cover screen before fading out
16275 if (!quick_quit && level.game_engine_type == GAME_ENGINE_TYPE_BD)
16276 game_bd.cover_screen = TRUE;
16279 FadeSkipNextFadeIn();
16281 SetGameStatus(GAME_MODE_MAIN);
16286 else // continue playing the game
16288 if (tape.playing && tape.deactivate_display)
16289 TapeDeactivateDisplayOff(TRUE);
16291 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
16293 if (tape.playing && tape.deactivate_display)
16294 TapeDeactivateDisplayOn();
16298 void RequestQuitGame(boolean escape_key_pressed)
16300 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
16301 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
16302 level_editor_test_game);
16303 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
16304 quick_quit || score_info_tape_play);
16306 RequestQuitGameExt(skip_request, quick_quit,
16307 "Do you really want to quit the game?");
16310 static char *getRestartGameMessage(void)
16312 boolean play_again = hasStartedNetworkGame();
16313 static char message[MAX_OUTPUT_LINESIZE];
16314 char *game_over_text = "Game over!";
16315 char *play_again_text = " Play it again?";
16317 if (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
16318 game_mm.game_over_message != NULL)
16319 game_over_text = game_mm.game_over_message;
16321 snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text,
16322 (play_again ? play_again_text : ""));
16327 static void RequestRestartGame(void)
16329 char *message = getRestartGameMessage();
16330 boolean has_started_game = hasStartedNetworkGame();
16331 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
16332 int door_state = DOOR_CLOSE_1;
16334 boolean restart_wanted = (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game);
16336 // if no restart wanted, continue with next level for BD style intermission levels
16337 if (!restart_wanted && !level_editor_test_game && level.bd_intermission)
16339 boolean success = AdvanceToNextLevel();
16341 restart_wanted = (success && setup.auto_play_next_level);
16344 if (restart_wanted)
16346 CloseDoor(door_state);
16348 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16352 // if game was invoked from level editor, also close tape recorder door
16353 if (level_editor_test_game)
16354 door_state = DOOR_CLOSE_ALL;
16356 CloseDoor(door_state);
16358 SetGameStatus(GAME_MODE_MAIN);
16364 boolean CheckRestartGame(void)
16366 static int game_over_delay = 0;
16367 int game_over_delay_value = 50;
16368 boolean game_over = checkGameFailed();
16372 game_over_delay = game_over_delay_value;
16377 if (game_over_delay > 0)
16379 if (game_over_delay == game_over_delay_value / 2)
16380 PlaySound(SND_GAME_LOSING);
16387 // do not ask to play again if request dialog is already active
16388 if (checkRequestActive())
16391 // do not ask to play again if request dialog already handled
16392 if (game.RestartGameRequested)
16395 // do not ask to play again if game was never actually played
16396 if (!game.GamePlayed)
16399 // do not ask to play again if this was disabled in setup menu
16400 if (!setup.ask_on_game_over)
16403 game.RestartGameRequested = TRUE;
16405 RequestRestartGame();
16410 boolean checkGameRunning(void)
16412 if (game_status != GAME_MODE_PLAYING)
16415 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGameRunning_BD())
16421 boolean checkGamePlaying(void)
16423 if (game_status != GAME_MODE_PLAYING)
16426 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGamePlaying_BD())
16432 boolean checkGameSolved(void)
16434 // set for all game engines if level was solved
16435 return game.LevelSolved_GameEnd;
16438 boolean checkGameFailed(void)
16440 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
16441 return (game_bd.game_over && !game_bd.level_solved);
16442 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16443 return (game_em.game_over && !game_em.level_solved);
16444 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16445 return (game_sp.game_over && !game_sp.level_solved);
16446 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16447 return (game_mm.game_over && !game_mm.level_solved);
16448 else // GAME_ENGINE_TYPE_RND
16449 return (game.GameOver && !game.LevelSolved);
16452 boolean checkGameEnded(void)
16454 return (checkGameSolved() || checkGameFailed());
16457 boolean checkRequestActive(void)
16459 return (game.request_active || game.envelope_active || game.any_door_active);
16463 // ----------------------------------------------------------------------------
16464 // random generator functions
16465 // ----------------------------------------------------------------------------
16467 unsigned int InitEngineRandom_RND(int seed)
16469 game.num_random_calls = 0;
16471 return InitEngineRandom(seed);
16474 unsigned int RND(int max)
16478 game.num_random_calls++;
16480 return GetEngineRandom(max);
16487 // ----------------------------------------------------------------------------
16488 // game engine snapshot handling functions
16489 // ----------------------------------------------------------------------------
16491 struct EngineSnapshotInfo
16493 // runtime values for custom element collect score
16494 int collect_score[NUM_CUSTOM_ELEMENTS];
16496 // runtime values for group element choice position
16497 int choice_pos[NUM_GROUP_ELEMENTS];
16499 // runtime values for belt position animations
16500 int belt_graphic[4][NUM_BELT_PARTS];
16501 int belt_anim_mode[4][NUM_BELT_PARTS];
16504 static struct EngineSnapshotInfo engine_snapshot_rnd;
16505 static char *snapshot_level_identifier = NULL;
16506 static int snapshot_level_nr = -1;
16508 static void SaveEngineSnapshotValues_RND(void)
16510 static int belt_base_active_element[4] =
16512 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
16513 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
16514 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
16515 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
16519 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16521 int element = EL_CUSTOM_START + i;
16523 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
16526 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16528 int element = EL_GROUP_START + i;
16530 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
16533 for (i = 0; i < 4; i++)
16535 for (j = 0; j < NUM_BELT_PARTS; j++)
16537 int element = belt_base_active_element[i] + j;
16538 int graphic = el2img(element);
16539 int anim_mode = graphic_info[graphic].anim_mode;
16541 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
16542 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
16547 static void LoadEngineSnapshotValues_RND(void)
16549 unsigned int num_random_calls = game.num_random_calls;
16552 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16554 int element = EL_CUSTOM_START + i;
16556 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
16559 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16561 int element = EL_GROUP_START + i;
16563 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
16566 for (i = 0; i < 4; i++)
16568 for (j = 0; j < NUM_BELT_PARTS; j++)
16570 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
16571 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
16573 graphic_info[graphic].anim_mode = anim_mode;
16577 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16579 InitRND(tape.random_seed);
16580 for (i = 0; i < num_random_calls; i++)
16584 if (game.num_random_calls != num_random_calls)
16586 Error("number of random calls out of sync");
16587 Error("number of random calls should be %d", num_random_calls);
16588 Error("number of random calls is %d", game.num_random_calls);
16590 Fail("this should not happen -- please debug");
16594 void FreeEngineSnapshotSingle(void)
16596 FreeSnapshotSingle();
16598 setString(&snapshot_level_identifier, NULL);
16599 snapshot_level_nr = -1;
16602 void FreeEngineSnapshotList(void)
16604 FreeSnapshotList();
16607 static ListNode *SaveEngineSnapshotBuffers(void)
16609 ListNode *buffers = NULL;
16611 // copy some special values to a structure better suited for the snapshot
16613 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16614 SaveEngineSnapshotValues_RND();
16615 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
16616 SaveEngineSnapshotValues_BD();
16617 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16618 SaveEngineSnapshotValues_EM();
16619 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16620 SaveEngineSnapshotValues_SP(&buffers);
16621 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16622 SaveEngineSnapshotValues_MM();
16624 // save values stored in special snapshot structure
16626 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16627 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
16628 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
16629 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_bd));
16630 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16631 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
16632 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16633 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
16634 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16635 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
16637 // save further RND engine values
16639 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
16640 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
16641 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
16643 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
16644 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
16645 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
16646 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16647 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTimeFrames));
16648 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16650 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16651 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16652 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16654 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16656 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16657 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16659 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16660 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16661 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16662 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16663 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16664 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16665 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16666 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16667 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16668 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16669 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16670 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16671 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16672 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16673 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16674 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16675 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16676 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16678 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16679 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16681 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16682 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16683 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16685 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16686 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16688 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16689 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16690 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16691 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16692 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16693 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16695 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16696 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16699 ListNode *node = engine_snapshot_list_rnd;
16702 while (node != NULL)
16704 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16709 Debug("game:playing:SaveEngineSnapshotBuffers",
16710 "size of engine snapshot: %d bytes", num_bytes);
16716 void SaveEngineSnapshotSingle(void)
16718 ListNode *buffers = SaveEngineSnapshotBuffers();
16720 // finally save all snapshot buffers to single snapshot
16721 SaveSnapshotSingle(buffers);
16723 // save level identification information
16724 setString(&snapshot_level_identifier, leveldir_current->identifier);
16725 snapshot_level_nr = level_nr;
16728 boolean CheckSaveEngineSnapshotToList(void)
16730 boolean save_snapshot =
16731 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16732 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16733 game.snapshot.changed_action) ||
16734 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16735 game.snapshot.collected_item));
16737 game.snapshot.changed_action = FALSE;
16738 game.snapshot.collected_item = FALSE;
16739 game.snapshot.save_snapshot = save_snapshot;
16741 return save_snapshot;
16744 void SaveEngineSnapshotToList(void)
16746 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16750 ListNode *buffers = SaveEngineSnapshotBuffers();
16752 // finally save all snapshot buffers to snapshot list
16753 SaveSnapshotToList(buffers);
16756 void SaveEngineSnapshotToListInitial(void)
16758 FreeEngineSnapshotList();
16760 SaveEngineSnapshotToList();
16763 static void LoadEngineSnapshotValues(void)
16765 // restore special values from snapshot structure
16767 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16768 LoadEngineSnapshotValues_RND();
16769 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
16770 LoadEngineSnapshotValues_BD();
16771 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16772 LoadEngineSnapshotValues_EM();
16773 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16774 LoadEngineSnapshotValues_SP();
16775 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16776 LoadEngineSnapshotValues_MM();
16779 void LoadEngineSnapshotSingle(void)
16781 LoadSnapshotSingle();
16783 LoadEngineSnapshotValues();
16786 static void LoadEngineSnapshot_Undo(int steps)
16788 LoadSnapshotFromList_Older(steps);
16790 LoadEngineSnapshotValues();
16793 static void LoadEngineSnapshot_Redo(int steps)
16795 LoadSnapshotFromList_Newer(steps);
16797 LoadEngineSnapshotValues();
16800 boolean CheckEngineSnapshotSingle(void)
16802 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16803 snapshot_level_nr == level_nr);
16806 boolean CheckEngineSnapshotList(void)
16808 return CheckSnapshotList();
16812 // ---------- new game button stuff -------------------------------------------
16819 boolean *setup_value;
16820 boolean allowed_on_tape;
16821 boolean is_touch_button;
16823 } gamebutton_info[NUM_GAME_BUTTONS] =
16826 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16827 GAME_CTRL_ID_STOP, NULL,
16828 TRUE, FALSE, "stop game"
16831 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16832 GAME_CTRL_ID_PAUSE, NULL,
16833 TRUE, FALSE, "pause game"
16836 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16837 GAME_CTRL_ID_PLAY, NULL,
16838 TRUE, FALSE, "play game"
16841 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16842 GAME_CTRL_ID_UNDO, NULL,
16843 TRUE, FALSE, "undo step"
16846 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16847 GAME_CTRL_ID_REDO, NULL,
16848 TRUE, FALSE, "redo step"
16851 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16852 GAME_CTRL_ID_SAVE, NULL,
16853 TRUE, FALSE, "save game"
16856 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16857 GAME_CTRL_ID_PAUSE2, NULL,
16858 TRUE, FALSE, "pause game"
16861 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16862 GAME_CTRL_ID_LOAD, NULL,
16863 TRUE, FALSE, "load game"
16866 IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart,
16867 GAME_CTRL_ID_RESTART, NULL,
16868 TRUE, FALSE, "restart game"
16871 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16872 GAME_CTRL_ID_PANEL_STOP, NULL,
16873 FALSE, FALSE, "stop game"
16876 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16877 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16878 FALSE, FALSE, "pause game"
16881 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16882 GAME_CTRL_ID_PANEL_PLAY, NULL,
16883 FALSE, FALSE, "play game"
16886 IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart,
16887 GAME_CTRL_ID_PANEL_RESTART, NULL,
16888 FALSE, FALSE, "restart game"
16891 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16892 GAME_CTRL_ID_TOUCH_STOP, NULL,
16893 FALSE, TRUE, "stop game"
16896 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16897 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16898 FALSE, TRUE, "pause game"
16901 IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart,
16902 GAME_CTRL_ID_TOUCH_RESTART, NULL,
16903 FALSE, TRUE, "restart game"
16906 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16907 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16908 TRUE, FALSE, "background music on/off"
16911 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16912 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16913 TRUE, FALSE, "sound loops on/off"
16916 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16917 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16918 TRUE, FALSE, "normal sounds on/off"
16921 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16922 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16923 FALSE, FALSE, "background music on/off"
16926 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16927 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16928 FALSE, FALSE, "sound loops on/off"
16931 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16932 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16933 FALSE, FALSE, "normal sounds on/off"
16937 void CreateGameButtons(void)
16941 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16943 int graphic = gamebutton_info[i].graphic;
16944 struct GraphicInfo *gfx = &graphic_info[graphic];
16945 struct XY *pos = gamebutton_info[i].pos;
16946 struct GadgetInfo *gi;
16949 unsigned int event_mask;
16950 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16951 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16952 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16953 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16954 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16955 int gd_x = gfx->src_x;
16956 int gd_y = gfx->src_y;
16957 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16958 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16959 int gd_xa = gfx->src_x + gfx->active_xoffset;
16960 int gd_ya = gfx->src_y + gfx->active_yoffset;
16961 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16962 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16963 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16964 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16967 // do not use touch buttons if overlay touch buttons are disabled
16968 if (is_touch_button && !setup.touch.overlay_buttons)
16971 if (gfx->bitmap == NULL)
16973 game_gadget[id] = NULL;
16978 if (id == GAME_CTRL_ID_STOP ||
16979 id == GAME_CTRL_ID_PANEL_STOP ||
16980 id == GAME_CTRL_ID_TOUCH_STOP ||
16981 id == GAME_CTRL_ID_PLAY ||
16982 id == GAME_CTRL_ID_PANEL_PLAY ||
16983 id == GAME_CTRL_ID_SAVE ||
16984 id == GAME_CTRL_ID_LOAD ||
16985 id == GAME_CTRL_ID_RESTART ||
16986 id == GAME_CTRL_ID_PANEL_RESTART ||
16987 id == GAME_CTRL_ID_TOUCH_RESTART)
16989 button_type = GD_TYPE_NORMAL_BUTTON;
16991 event_mask = GD_EVENT_RELEASED;
16993 else if (id == GAME_CTRL_ID_UNDO ||
16994 id == GAME_CTRL_ID_REDO)
16996 button_type = GD_TYPE_NORMAL_BUTTON;
16998 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
17002 button_type = GD_TYPE_CHECK_BUTTON;
17003 checked = (gamebutton_info[i].setup_value != NULL ?
17004 *gamebutton_info[i].setup_value : FALSE);
17005 event_mask = GD_EVENT_PRESSED;
17008 gi = CreateGadget(GDI_CUSTOM_ID, id,
17009 GDI_IMAGE_ID, graphic,
17010 GDI_INFO_TEXT, gamebutton_info[i].infotext,
17013 GDI_WIDTH, gfx->width,
17014 GDI_HEIGHT, gfx->height,
17015 GDI_TYPE, button_type,
17016 GDI_STATE, GD_BUTTON_UNPRESSED,
17017 GDI_CHECKED, checked,
17018 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
17019 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
17020 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
17021 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
17022 GDI_DIRECT_DRAW, FALSE,
17023 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
17024 GDI_EVENT_MASK, event_mask,
17025 GDI_CALLBACK_ACTION, HandleGameButtons,
17029 Fail("cannot create gadget");
17031 game_gadget[id] = gi;
17035 void FreeGameButtons(void)
17039 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17040 FreeGadget(game_gadget[i]);
17043 static void UnmapGameButtonsAtSamePosition(int id)
17047 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17049 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
17050 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
17051 UnmapGadget(game_gadget[i]);
17054 static void UnmapGameButtonsAtSamePosition_All(void)
17056 if (setup.show_load_save_buttons)
17058 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
17059 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
17060 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
17062 else if (setup.show_undo_redo_buttons)
17064 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
17065 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
17066 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
17070 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
17071 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
17072 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
17074 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
17075 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
17076 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
17080 void MapLoadSaveButtons(void)
17082 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
17083 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
17085 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
17086 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
17089 void MapUndoRedoButtons(void)
17091 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
17092 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
17094 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
17095 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
17098 void ModifyPauseButtons(void)
17102 GAME_CTRL_ID_PAUSE,
17103 GAME_CTRL_ID_PAUSE2,
17104 GAME_CTRL_ID_PANEL_PAUSE,
17105 GAME_CTRL_ID_TOUCH_PAUSE,
17110 // do not redraw pause button on closed door (may happen when restarting game)
17111 if (!(GetDoorState() & DOOR_OPEN_1))
17114 for (i = 0; ids[i] > -1; i++)
17115 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
17118 static void MapGameButtonsExt(boolean on_tape)
17122 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17124 if ((i == GAME_CTRL_ID_UNDO ||
17125 i == GAME_CTRL_ID_REDO) &&
17126 game_status != GAME_MODE_PLAYING)
17129 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17130 MapGadget(game_gadget[i]);
17133 UnmapGameButtonsAtSamePosition_All();
17135 RedrawGameButtons();
17138 static void UnmapGameButtonsExt(boolean on_tape)
17142 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17143 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17144 UnmapGadget(game_gadget[i]);
17147 static void RedrawGameButtonsExt(boolean on_tape)
17151 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17152 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17153 RedrawGadget(game_gadget[i]);
17156 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
17161 gi->checked = state;
17164 static void RedrawSoundButtonGadget(int id)
17166 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
17167 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
17168 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
17169 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
17170 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
17171 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
17174 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
17175 RedrawGadget(game_gadget[id2]);
17178 void MapGameButtons(void)
17180 MapGameButtonsExt(FALSE);
17183 void UnmapGameButtons(void)
17185 UnmapGameButtonsExt(FALSE);
17188 void RedrawGameButtons(void)
17190 RedrawGameButtonsExt(FALSE);
17193 void MapGameButtonsOnTape(void)
17195 MapGameButtonsExt(TRUE);
17198 void UnmapGameButtonsOnTape(void)
17200 UnmapGameButtonsExt(TRUE);
17203 void RedrawGameButtonsOnTape(void)
17205 RedrawGameButtonsExt(TRUE);
17208 static void GameUndoRedoExt(void)
17210 ClearPlayerAction();
17212 tape.pausing = TRUE;
17215 UpdateAndDisplayGameControlValues();
17217 DrawCompleteVideoDisplay();
17218 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
17219 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
17220 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
17222 ModifyPauseButtons();
17227 static void GameUndo(int steps)
17229 if (!CheckEngineSnapshotList())
17232 int tape_property_bits = tape.property_bits;
17234 LoadEngineSnapshot_Undo(steps);
17236 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17241 static void GameRedo(int steps)
17243 if (!CheckEngineSnapshotList())
17246 int tape_property_bits = tape.property_bits;
17248 LoadEngineSnapshot_Redo(steps);
17250 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17255 static void HandleGameButtonsExt(int id, int button)
17257 static boolean game_undo_executed = FALSE;
17258 int steps = BUTTON_STEPSIZE(button);
17259 boolean handle_game_buttons =
17260 (game_status == GAME_MODE_PLAYING ||
17261 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
17263 if (!handle_game_buttons)
17268 case GAME_CTRL_ID_STOP:
17269 case GAME_CTRL_ID_PANEL_STOP:
17270 case GAME_CTRL_ID_TOUCH_STOP:
17275 case GAME_CTRL_ID_PAUSE:
17276 case GAME_CTRL_ID_PAUSE2:
17277 case GAME_CTRL_ID_PANEL_PAUSE:
17278 case GAME_CTRL_ID_TOUCH_PAUSE:
17279 if (network.enabled && game_status == GAME_MODE_PLAYING)
17282 SendToServer_ContinuePlaying();
17284 SendToServer_PausePlaying();
17287 TapeTogglePause(TAPE_TOGGLE_MANUAL);
17289 game_undo_executed = FALSE;
17293 case GAME_CTRL_ID_PLAY:
17294 case GAME_CTRL_ID_PANEL_PLAY:
17295 if (game_status == GAME_MODE_MAIN)
17297 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
17299 else if (tape.pausing)
17301 if (network.enabled)
17302 SendToServer_ContinuePlaying();
17304 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
17308 case GAME_CTRL_ID_UNDO:
17309 // Important: When using "save snapshot when collecting an item" mode,
17310 // load last (current) snapshot for first "undo" after pressing "pause"
17311 // (else the last-but-one snapshot would be loaded, because the snapshot
17312 // pointer already points to the last snapshot when pressing "pause",
17313 // which is fine for "every step/move" mode, but not for "every collect")
17314 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
17315 !game_undo_executed)
17318 game_undo_executed = TRUE;
17323 case GAME_CTRL_ID_REDO:
17327 case GAME_CTRL_ID_SAVE:
17331 case GAME_CTRL_ID_LOAD:
17335 case GAME_CTRL_ID_RESTART:
17336 case GAME_CTRL_ID_PANEL_RESTART:
17337 case GAME_CTRL_ID_TOUCH_RESTART:
17342 case SOUND_CTRL_ID_MUSIC:
17343 case SOUND_CTRL_ID_PANEL_MUSIC:
17344 if (setup.sound_music)
17346 setup.sound_music = FALSE;
17350 else if (audio.music_available)
17352 setup.sound = setup.sound_music = TRUE;
17354 SetAudioMode(setup.sound);
17356 if (game_status == GAME_MODE_PLAYING)
17360 RedrawSoundButtonGadget(id);
17364 case SOUND_CTRL_ID_LOOPS:
17365 case SOUND_CTRL_ID_PANEL_LOOPS:
17366 if (setup.sound_loops)
17367 setup.sound_loops = FALSE;
17368 else if (audio.loops_available)
17370 setup.sound = setup.sound_loops = TRUE;
17372 SetAudioMode(setup.sound);
17375 RedrawSoundButtonGadget(id);
17379 case SOUND_CTRL_ID_SIMPLE:
17380 case SOUND_CTRL_ID_PANEL_SIMPLE:
17381 if (setup.sound_simple)
17382 setup.sound_simple = FALSE;
17383 else if (audio.sound_available)
17385 setup.sound = setup.sound_simple = TRUE;
17387 SetAudioMode(setup.sound);
17390 RedrawSoundButtonGadget(id);
17399 static void HandleGameButtons(struct GadgetInfo *gi)
17401 HandleGameButtonsExt(gi->custom_id, gi->event.button);
17404 void HandleSoundButtonKeys(Key key)
17406 if (key == setup.shortcut.sound_simple)
17407 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
17408 else if (key == setup.shortcut.sound_loops)
17409 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
17410 else if (key == setup.shortcut.sound_music)
17411 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);