1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_INIT_PLAYER 1
27 #define DEBUG_PLAYER_ACTIONS 0
30 #define USE_NEW_AMOEBA_CODE FALSE
33 #define USE_QUICKSAND_BD_ROCK_BUGFIX 0
34 #define USE_QUICKSAND_IMPACT_BUGFIX 0
35 #define USE_DELAYED_GFX_REDRAW 0
36 #define USE_NEW_PLAYER_ASSIGNMENTS 1
38 #if USE_DELAYED_GFX_REDRAW
39 #define TEST_DrawLevelField(x, y) \
40 GfxRedraw[x][y] |= GFX_REDRAW_TILE
41 #define TEST_DrawLevelFieldCrumbled(x, y) \
42 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED
43 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
44 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS
45 #define TEST_DrawTwinkleOnField(x, y) \
46 GfxRedraw[x][y] |= GFX_REDRAW_TILE_TWINKLED
48 #define TEST_DrawLevelField(x, y) \
50 #define TEST_DrawLevelFieldCrumbled(x, y) \
51 DrawLevelFieldCrumbled(x, y)
52 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
53 DrawLevelFieldCrumbledNeighbours(x, y)
54 #define TEST_DrawTwinkleOnField(x, y) \
55 DrawTwinkleOnField(x, y)
65 #define MP_NO_ACTION 0
68 #define MP_DONT_RUN_INTO (MP_MOVING | MP_ACTION)
72 #define SCROLL_GO_ON 1
74 // for Bang()/Explode()
75 #define EX_PHASE_START 0
76 #define EX_TYPE_NONE 0
77 #define EX_TYPE_NORMAL (1 << 0)
78 #define EX_TYPE_CENTER (1 << 1)
79 #define EX_TYPE_BORDER (1 << 2)
80 #define EX_TYPE_CROSS (1 << 3)
81 #define EX_TYPE_DYNA (1 << 4)
82 #define EX_TYPE_SINGLE_TILE (EX_TYPE_CENTER | EX_TYPE_BORDER)
84 #define PANEL_OFF() (game.panel.active == FALSE)
85 #define PANEL_DEACTIVATED(p) ((p)->x < 0 || (p)->y < 0 || PANEL_OFF())
86 #define PANEL_XPOS(p) (DX + ALIGNED_TEXT_XPOS(p))
87 #define PANEL_YPOS(p) (DY + ALIGNED_TEXT_YPOS(p))
89 // game panel display and control definitions
90 #define GAME_PANEL_LEVEL_NUMBER 0
91 #define GAME_PANEL_GEMS 1
92 #define GAME_PANEL_GEMS_NEEDED 2
93 #define GAME_PANEL_GEMS_COLLECTED 3
94 #define GAME_PANEL_GEMS_SCORE 4
95 #define GAME_PANEL_INVENTORY_COUNT 5
96 #define GAME_PANEL_INVENTORY_FIRST_1 6
97 #define GAME_PANEL_INVENTORY_FIRST_2 7
98 #define GAME_PANEL_INVENTORY_FIRST_3 8
99 #define GAME_PANEL_INVENTORY_FIRST_4 9
100 #define GAME_PANEL_INVENTORY_FIRST_5 10
101 #define GAME_PANEL_INVENTORY_FIRST_6 11
102 #define GAME_PANEL_INVENTORY_FIRST_7 12
103 #define GAME_PANEL_INVENTORY_FIRST_8 13
104 #define GAME_PANEL_INVENTORY_LAST_1 14
105 #define GAME_PANEL_INVENTORY_LAST_2 15
106 #define GAME_PANEL_INVENTORY_LAST_3 16
107 #define GAME_PANEL_INVENTORY_LAST_4 17
108 #define GAME_PANEL_INVENTORY_LAST_5 18
109 #define GAME_PANEL_INVENTORY_LAST_6 19
110 #define GAME_PANEL_INVENTORY_LAST_7 20
111 #define GAME_PANEL_INVENTORY_LAST_8 21
112 #define GAME_PANEL_KEY_1 22
113 #define GAME_PANEL_KEY_2 23
114 #define GAME_PANEL_KEY_3 24
115 #define GAME_PANEL_KEY_4 25
116 #define GAME_PANEL_KEY_5 26
117 #define GAME_PANEL_KEY_6 27
118 #define GAME_PANEL_KEY_7 28
119 #define GAME_PANEL_KEY_8 29
120 #define GAME_PANEL_KEY_WHITE 30
121 #define GAME_PANEL_KEY_WHITE_COUNT 31
122 #define GAME_PANEL_SCORE 32
123 #define GAME_PANEL_HIGHSCORE 33
124 #define GAME_PANEL_TIME 34
125 #define GAME_PANEL_TIME_HH 35
126 #define GAME_PANEL_TIME_MM 36
127 #define GAME_PANEL_TIME_SS 37
128 #define GAME_PANEL_TIME_ANIM 38
129 #define GAME_PANEL_HEALTH 39
130 #define GAME_PANEL_HEALTH_ANIM 40
131 #define GAME_PANEL_FRAME 41
132 #define GAME_PANEL_SHIELD_NORMAL 42
133 #define GAME_PANEL_SHIELD_NORMAL_TIME 43
134 #define GAME_PANEL_SHIELD_DEADLY 44
135 #define GAME_PANEL_SHIELD_DEADLY_TIME 45
136 #define GAME_PANEL_EXIT 46
137 #define GAME_PANEL_EMC_MAGIC_BALL 47
138 #define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 48
139 #define GAME_PANEL_LIGHT_SWITCH 49
140 #define GAME_PANEL_LIGHT_SWITCH_TIME 50
141 #define GAME_PANEL_TIMEGATE_SWITCH 51
142 #define GAME_PANEL_TIMEGATE_SWITCH_TIME 52
143 #define GAME_PANEL_SWITCHGATE_SWITCH 53
144 #define GAME_PANEL_EMC_LENSES 54
145 #define GAME_PANEL_EMC_LENSES_TIME 55
146 #define GAME_PANEL_EMC_MAGNIFIER 56
147 #define GAME_PANEL_EMC_MAGNIFIER_TIME 57
148 #define GAME_PANEL_BALLOON_SWITCH 58
149 #define GAME_PANEL_DYNABOMB_NUMBER 59
150 #define GAME_PANEL_DYNABOMB_SIZE 60
151 #define GAME_PANEL_DYNABOMB_POWER 61
152 #define GAME_PANEL_PENGUINS 62
153 #define GAME_PANEL_SOKOBAN_OBJECTS 63
154 #define GAME_PANEL_SOKOBAN_FIELDS 64
155 #define GAME_PANEL_ROBOT_WHEEL 65
156 #define GAME_PANEL_CONVEYOR_BELT_1 66
157 #define GAME_PANEL_CONVEYOR_BELT_2 67
158 #define GAME_PANEL_CONVEYOR_BELT_3 68
159 #define GAME_PANEL_CONVEYOR_BELT_4 69
160 #define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 70
161 #define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 71
162 #define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 72
163 #define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 73
164 #define GAME_PANEL_MAGIC_WALL 74
165 #define GAME_PANEL_MAGIC_WALL_TIME 75
166 #define GAME_PANEL_GRAVITY_STATE 76
167 #define GAME_PANEL_GRAPHIC_1 77
168 #define GAME_PANEL_GRAPHIC_2 78
169 #define GAME_PANEL_GRAPHIC_3 79
170 #define GAME_PANEL_GRAPHIC_4 80
171 #define GAME_PANEL_GRAPHIC_5 81
172 #define GAME_PANEL_GRAPHIC_6 82
173 #define GAME_PANEL_GRAPHIC_7 83
174 #define GAME_PANEL_GRAPHIC_8 84
175 #define GAME_PANEL_ELEMENT_1 85
176 #define GAME_PANEL_ELEMENT_2 86
177 #define GAME_PANEL_ELEMENT_3 87
178 #define GAME_PANEL_ELEMENT_4 88
179 #define GAME_PANEL_ELEMENT_5 89
180 #define GAME_PANEL_ELEMENT_6 90
181 #define GAME_PANEL_ELEMENT_7 91
182 #define GAME_PANEL_ELEMENT_8 92
183 #define GAME_PANEL_ELEMENT_COUNT_1 93
184 #define GAME_PANEL_ELEMENT_COUNT_2 94
185 #define GAME_PANEL_ELEMENT_COUNT_3 95
186 #define GAME_PANEL_ELEMENT_COUNT_4 96
187 #define GAME_PANEL_ELEMENT_COUNT_5 97
188 #define GAME_PANEL_ELEMENT_COUNT_6 98
189 #define GAME_PANEL_ELEMENT_COUNT_7 99
190 #define GAME_PANEL_ELEMENT_COUNT_8 100
191 #define GAME_PANEL_CE_SCORE_1 101
192 #define GAME_PANEL_CE_SCORE_2 102
193 #define GAME_PANEL_CE_SCORE_3 103
194 #define GAME_PANEL_CE_SCORE_4 104
195 #define GAME_PANEL_CE_SCORE_5 105
196 #define GAME_PANEL_CE_SCORE_6 106
197 #define GAME_PANEL_CE_SCORE_7 107
198 #define GAME_PANEL_CE_SCORE_8 108
199 #define GAME_PANEL_CE_SCORE_1_ELEMENT 109
200 #define GAME_PANEL_CE_SCORE_2_ELEMENT 110
201 #define GAME_PANEL_CE_SCORE_3_ELEMENT 111
202 #define GAME_PANEL_CE_SCORE_4_ELEMENT 112
203 #define GAME_PANEL_CE_SCORE_5_ELEMENT 113
204 #define GAME_PANEL_CE_SCORE_6_ELEMENT 114
205 #define GAME_PANEL_CE_SCORE_7_ELEMENT 115
206 #define GAME_PANEL_CE_SCORE_8_ELEMENT 116
207 #define GAME_PANEL_PLAYER_NAME 117
208 #define GAME_PANEL_LEVEL_NAME 118
209 #define GAME_PANEL_LEVEL_AUTHOR 119
211 #define NUM_GAME_PANEL_CONTROLS 120
213 struct GamePanelOrderInfo
219 static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
221 struct GamePanelControlInfo
225 struct TextPosInfo *pos;
228 int graphic, graphic_active;
230 int value, last_value;
231 int frame, last_frame;
236 static struct GamePanelControlInfo game_panel_controls[] =
239 GAME_PANEL_LEVEL_NUMBER,
240 &game.panel.level_number,
249 GAME_PANEL_GEMS_NEEDED,
250 &game.panel.gems_needed,
254 GAME_PANEL_GEMS_COLLECTED,
255 &game.panel.gems_collected,
259 GAME_PANEL_GEMS_SCORE,
260 &game.panel.gems_score,
264 GAME_PANEL_INVENTORY_COUNT,
265 &game.panel.inventory_count,
269 GAME_PANEL_INVENTORY_FIRST_1,
270 &game.panel.inventory_first[0],
274 GAME_PANEL_INVENTORY_FIRST_2,
275 &game.panel.inventory_first[1],
279 GAME_PANEL_INVENTORY_FIRST_3,
280 &game.panel.inventory_first[2],
284 GAME_PANEL_INVENTORY_FIRST_4,
285 &game.panel.inventory_first[3],
289 GAME_PANEL_INVENTORY_FIRST_5,
290 &game.panel.inventory_first[4],
294 GAME_PANEL_INVENTORY_FIRST_6,
295 &game.panel.inventory_first[5],
299 GAME_PANEL_INVENTORY_FIRST_7,
300 &game.panel.inventory_first[6],
304 GAME_PANEL_INVENTORY_FIRST_8,
305 &game.panel.inventory_first[7],
309 GAME_PANEL_INVENTORY_LAST_1,
310 &game.panel.inventory_last[0],
314 GAME_PANEL_INVENTORY_LAST_2,
315 &game.panel.inventory_last[1],
319 GAME_PANEL_INVENTORY_LAST_3,
320 &game.panel.inventory_last[2],
324 GAME_PANEL_INVENTORY_LAST_4,
325 &game.panel.inventory_last[3],
329 GAME_PANEL_INVENTORY_LAST_5,
330 &game.panel.inventory_last[4],
334 GAME_PANEL_INVENTORY_LAST_6,
335 &game.panel.inventory_last[5],
339 GAME_PANEL_INVENTORY_LAST_7,
340 &game.panel.inventory_last[6],
344 GAME_PANEL_INVENTORY_LAST_8,
345 &game.panel.inventory_last[7],
389 GAME_PANEL_KEY_WHITE,
390 &game.panel.key_white,
394 GAME_PANEL_KEY_WHITE_COUNT,
395 &game.panel.key_white_count,
404 GAME_PANEL_HIGHSCORE,
405 &game.panel.highscore,
429 GAME_PANEL_TIME_ANIM,
430 &game.panel.time_anim,
433 IMG_GFX_GAME_PANEL_TIME_ANIM,
434 IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
442 GAME_PANEL_HEALTH_ANIM,
443 &game.panel.health_anim,
446 IMG_GFX_GAME_PANEL_HEALTH_ANIM,
447 IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
455 GAME_PANEL_SHIELD_NORMAL,
456 &game.panel.shield_normal,
460 GAME_PANEL_SHIELD_NORMAL_TIME,
461 &game.panel.shield_normal_time,
465 GAME_PANEL_SHIELD_DEADLY,
466 &game.panel.shield_deadly,
470 GAME_PANEL_SHIELD_DEADLY_TIME,
471 &game.panel.shield_deadly_time,
480 GAME_PANEL_EMC_MAGIC_BALL,
481 &game.panel.emc_magic_ball,
485 GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
486 &game.panel.emc_magic_ball_switch,
490 GAME_PANEL_LIGHT_SWITCH,
491 &game.panel.light_switch,
495 GAME_PANEL_LIGHT_SWITCH_TIME,
496 &game.panel.light_switch_time,
500 GAME_PANEL_TIMEGATE_SWITCH,
501 &game.panel.timegate_switch,
505 GAME_PANEL_TIMEGATE_SWITCH_TIME,
506 &game.panel.timegate_switch_time,
510 GAME_PANEL_SWITCHGATE_SWITCH,
511 &game.panel.switchgate_switch,
515 GAME_PANEL_EMC_LENSES,
516 &game.panel.emc_lenses,
520 GAME_PANEL_EMC_LENSES_TIME,
521 &game.panel.emc_lenses_time,
525 GAME_PANEL_EMC_MAGNIFIER,
526 &game.panel.emc_magnifier,
530 GAME_PANEL_EMC_MAGNIFIER_TIME,
531 &game.panel.emc_magnifier_time,
535 GAME_PANEL_BALLOON_SWITCH,
536 &game.panel.balloon_switch,
540 GAME_PANEL_DYNABOMB_NUMBER,
541 &game.panel.dynabomb_number,
545 GAME_PANEL_DYNABOMB_SIZE,
546 &game.panel.dynabomb_size,
550 GAME_PANEL_DYNABOMB_POWER,
551 &game.panel.dynabomb_power,
556 &game.panel.penguins,
560 GAME_PANEL_SOKOBAN_OBJECTS,
561 &game.panel.sokoban_objects,
565 GAME_PANEL_SOKOBAN_FIELDS,
566 &game.panel.sokoban_fields,
570 GAME_PANEL_ROBOT_WHEEL,
571 &game.panel.robot_wheel,
575 GAME_PANEL_CONVEYOR_BELT_1,
576 &game.panel.conveyor_belt[0],
580 GAME_PANEL_CONVEYOR_BELT_2,
581 &game.panel.conveyor_belt[1],
585 GAME_PANEL_CONVEYOR_BELT_3,
586 &game.panel.conveyor_belt[2],
590 GAME_PANEL_CONVEYOR_BELT_4,
591 &game.panel.conveyor_belt[3],
595 GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
596 &game.panel.conveyor_belt_switch[0],
600 GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
601 &game.panel.conveyor_belt_switch[1],
605 GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
606 &game.panel.conveyor_belt_switch[2],
610 GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
611 &game.panel.conveyor_belt_switch[3],
615 GAME_PANEL_MAGIC_WALL,
616 &game.panel.magic_wall,
620 GAME_PANEL_MAGIC_WALL_TIME,
621 &game.panel.magic_wall_time,
625 GAME_PANEL_GRAVITY_STATE,
626 &game.panel.gravity_state,
630 GAME_PANEL_GRAPHIC_1,
631 &game.panel.graphic[0],
635 GAME_PANEL_GRAPHIC_2,
636 &game.panel.graphic[1],
640 GAME_PANEL_GRAPHIC_3,
641 &game.panel.graphic[2],
645 GAME_PANEL_GRAPHIC_4,
646 &game.panel.graphic[3],
650 GAME_PANEL_GRAPHIC_5,
651 &game.panel.graphic[4],
655 GAME_PANEL_GRAPHIC_6,
656 &game.panel.graphic[5],
660 GAME_PANEL_GRAPHIC_7,
661 &game.panel.graphic[6],
665 GAME_PANEL_GRAPHIC_8,
666 &game.panel.graphic[7],
670 GAME_PANEL_ELEMENT_1,
671 &game.panel.element[0],
675 GAME_PANEL_ELEMENT_2,
676 &game.panel.element[1],
680 GAME_PANEL_ELEMENT_3,
681 &game.panel.element[2],
685 GAME_PANEL_ELEMENT_4,
686 &game.panel.element[3],
690 GAME_PANEL_ELEMENT_5,
691 &game.panel.element[4],
695 GAME_PANEL_ELEMENT_6,
696 &game.panel.element[5],
700 GAME_PANEL_ELEMENT_7,
701 &game.panel.element[6],
705 GAME_PANEL_ELEMENT_8,
706 &game.panel.element[7],
710 GAME_PANEL_ELEMENT_COUNT_1,
711 &game.panel.element_count[0],
715 GAME_PANEL_ELEMENT_COUNT_2,
716 &game.panel.element_count[1],
720 GAME_PANEL_ELEMENT_COUNT_3,
721 &game.panel.element_count[2],
725 GAME_PANEL_ELEMENT_COUNT_4,
726 &game.panel.element_count[3],
730 GAME_PANEL_ELEMENT_COUNT_5,
731 &game.panel.element_count[4],
735 GAME_PANEL_ELEMENT_COUNT_6,
736 &game.panel.element_count[5],
740 GAME_PANEL_ELEMENT_COUNT_7,
741 &game.panel.element_count[6],
745 GAME_PANEL_ELEMENT_COUNT_8,
746 &game.panel.element_count[7],
750 GAME_PANEL_CE_SCORE_1,
751 &game.panel.ce_score[0],
755 GAME_PANEL_CE_SCORE_2,
756 &game.panel.ce_score[1],
760 GAME_PANEL_CE_SCORE_3,
761 &game.panel.ce_score[2],
765 GAME_PANEL_CE_SCORE_4,
766 &game.panel.ce_score[3],
770 GAME_PANEL_CE_SCORE_5,
771 &game.panel.ce_score[4],
775 GAME_PANEL_CE_SCORE_6,
776 &game.panel.ce_score[5],
780 GAME_PANEL_CE_SCORE_7,
781 &game.panel.ce_score[6],
785 GAME_PANEL_CE_SCORE_8,
786 &game.panel.ce_score[7],
790 GAME_PANEL_CE_SCORE_1_ELEMENT,
791 &game.panel.ce_score_element[0],
795 GAME_PANEL_CE_SCORE_2_ELEMENT,
796 &game.panel.ce_score_element[1],
800 GAME_PANEL_CE_SCORE_3_ELEMENT,
801 &game.panel.ce_score_element[2],
805 GAME_PANEL_CE_SCORE_4_ELEMENT,
806 &game.panel.ce_score_element[3],
810 GAME_PANEL_CE_SCORE_5_ELEMENT,
811 &game.panel.ce_score_element[4],
815 GAME_PANEL_CE_SCORE_6_ELEMENT,
816 &game.panel.ce_score_element[5],
820 GAME_PANEL_CE_SCORE_7_ELEMENT,
821 &game.panel.ce_score_element[6],
825 GAME_PANEL_CE_SCORE_8_ELEMENT,
826 &game.panel.ce_score_element[7],
830 GAME_PANEL_PLAYER_NAME,
831 &game.panel.player_name,
835 GAME_PANEL_LEVEL_NAME,
836 &game.panel.level_name,
840 GAME_PANEL_LEVEL_AUTHOR,
841 &game.panel.level_author,
852 // values for delayed check of falling and moving elements and for collision
853 #define CHECK_DELAY_MOVING 3
854 #define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
855 #define CHECK_DELAY_COLLISION 2
856 #define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
858 // values for initial player move delay (initial delay counter value)
859 #define INITIAL_MOVE_DELAY_OFF -1
860 #define INITIAL_MOVE_DELAY_ON 0
862 // values for player movement speed (which is in fact a delay value)
863 #define MOVE_DELAY_MIN_SPEED 32
864 #define MOVE_DELAY_NORMAL_SPEED 8
865 #define MOVE_DELAY_HIGH_SPEED 4
866 #define MOVE_DELAY_MAX_SPEED 1
868 #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
869 #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
871 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
872 #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
874 // values for scroll positions
875 #define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
876 (x) > SBX_Right + MIDPOSX ? SBX_Right :\
878 #define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
879 (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
882 // values for other actions
883 #define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
884 #define MOVE_STEPSIZE_MIN (1)
885 #define MOVE_STEPSIZE_MAX (TILEX)
887 #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
888 #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
890 #define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
892 #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
893 RND(element_info[e].push_delay_random))
894 #define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
895 RND(element_info[e].drop_delay_random))
896 #define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
897 RND(element_info[e].move_delay_random))
898 #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
899 (element_info[e].move_delay_random))
900 #define GET_NEW_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
901 RND(element_info[e].step_delay_random))
902 #define GET_MAX_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
903 (element_info[e].step_delay_random))
904 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
905 RND(element_info[e].ce_value_random_initial))
906 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
907 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
908 RND((c)->delay_random * (c)->delay_frames))
909 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
910 RND((c)->delay_random))
913 #define GET_VALID_RUNTIME_ELEMENT(e) \
914 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
916 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
917 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
918 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
919 (be) + (e) - EL_SELF)
921 #define GET_PLAYER_FROM_BITS(p) \
922 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
924 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
925 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
926 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
927 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
928 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
929 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
930 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
931 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
932 RESOLVED_REFERENCE_ELEMENT(be, e) : \
935 #define CAN_GROW_INTO(e) \
936 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
938 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
939 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
942 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
943 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
944 (CAN_MOVE_INTO_ACID(e) && \
945 Tile[x][y] == EL_ACID) || \
948 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
949 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
950 (CAN_MOVE_INTO_ACID(e) && \
951 Tile[x][y] == EL_ACID) || \
954 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
955 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
957 (CAN_MOVE_INTO_ACID(e) && \
958 Tile[x][y] == EL_ACID) || \
959 (DONT_COLLIDE_WITH(e) && \
961 !PLAYER_ENEMY_PROTECTED(x, y))))
963 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
964 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
966 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
967 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
969 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
970 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
972 #define ANDROID_CAN_CLONE_FIELD(x, y) \
973 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
974 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
976 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
977 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
979 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
980 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
982 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
983 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
985 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
986 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
988 #define PIG_CAN_ENTER_FIELD(e, x, y) \
989 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
991 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
992 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
993 Tile[x][y] == EL_EM_EXIT_OPEN || \
994 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
995 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
996 IS_FOOD_PENGUIN(Tile[x][y])))
997 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
998 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
1000 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
1001 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
1003 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
1004 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
1006 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
1007 (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
1008 Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
1010 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
1012 #define CE_ENTER_FIELD_COND(e, x, y) \
1013 (!IS_PLAYER(x, y) && \
1014 IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
1016 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
1017 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
1019 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
1020 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1022 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1023 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1024 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1025 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1027 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1029 // game button identifiers
1030 #define GAME_CTRL_ID_STOP 0
1031 #define GAME_CTRL_ID_PAUSE 1
1032 #define GAME_CTRL_ID_PLAY 2
1033 #define GAME_CTRL_ID_UNDO 3
1034 #define GAME_CTRL_ID_REDO 4
1035 #define GAME_CTRL_ID_SAVE 5
1036 #define GAME_CTRL_ID_PAUSE2 6
1037 #define GAME_CTRL_ID_LOAD 7
1038 #define GAME_CTRL_ID_RESTART 8
1039 #define GAME_CTRL_ID_PANEL_STOP 9
1040 #define GAME_CTRL_ID_PANEL_PAUSE 10
1041 #define GAME_CTRL_ID_PANEL_PLAY 11
1042 #define GAME_CTRL_ID_PANEL_RESTART 12
1043 #define GAME_CTRL_ID_TOUCH_STOP 13
1044 #define GAME_CTRL_ID_TOUCH_PAUSE 14
1045 #define GAME_CTRL_ID_TOUCH_RESTART 15
1046 #define SOUND_CTRL_ID_MUSIC 16
1047 #define SOUND_CTRL_ID_LOOPS 17
1048 #define SOUND_CTRL_ID_SIMPLE 18
1049 #define SOUND_CTRL_ID_PANEL_MUSIC 19
1050 #define SOUND_CTRL_ID_PANEL_LOOPS 20
1051 #define SOUND_CTRL_ID_PANEL_SIMPLE 21
1053 #define NUM_GAME_BUTTONS 22
1056 // forward declaration for internal use
1058 static void CreateField(int, int, int);
1060 static void ResetGfxAnimation(int, int);
1062 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1063 static void AdvanceFrameAndPlayerCounters(int);
1065 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1066 static boolean MovePlayer(struct PlayerInfo *, int, int);
1067 static void ScrollPlayer(struct PlayerInfo *, int);
1068 static void ScrollScreen(struct PlayerInfo *, int);
1070 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1071 static boolean DigFieldByCE(int, int, int);
1072 static boolean SnapField(struct PlayerInfo *, int, int);
1073 static boolean DropElement(struct PlayerInfo *);
1075 static void InitBeltMovement(void);
1076 static void CloseAllOpenTimegates(void);
1077 static void CheckGravityMovement(struct PlayerInfo *);
1078 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1079 static void KillPlayerUnlessEnemyProtected(int, int);
1080 static void KillPlayerUnlessExplosionProtected(int, int);
1082 static void CheckNextToConditions(int, int);
1083 static void TestIfPlayerNextToCustomElement(int, int);
1084 static void TestIfPlayerTouchesCustomElement(int, int);
1085 static void TestIfElementNextToCustomElement(int, int);
1086 static void TestIfElementTouchesCustomElement(int, int);
1087 static void TestIfElementHitsCustomElement(int, int, int);
1089 static void HandleElementChange(int, int, int);
1090 static void ExecuteCustomElementAction(int, int, int, int);
1091 static boolean ChangeElement(int, int, int, int);
1093 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int, int, int);
1094 #define CheckTriggeredElementChange(x, y, e, ev) \
1095 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, -1)
1096 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1097 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1098 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1099 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1100 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1101 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1102 #define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
1103 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1105 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1106 #define CheckElementChange(x, y, e, te, ev) \
1107 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1108 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1109 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1110 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1111 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1112 #define CheckElementChangeByMouse(x, y, e, ev, s) \
1113 CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
1115 static void PlayLevelSound(int, int, int);
1116 static void PlayLevelSoundNearest(int, int, int);
1117 static void PlayLevelSoundAction(int, int, int);
1118 static void PlayLevelSoundElementAction(int, int, int, int);
1119 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1120 static void PlayLevelSoundActionIfLoop(int, int, int);
1121 static void StopLevelSoundActionIfLoop(int, int, int);
1122 static void PlayLevelMusic(void);
1123 static void FadeLevelSoundsAndMusic(void);
1125 static void HandleGameButtons(struct GadgetInfo *);
1127 int AmoebaNeighbourNr(int, int);
1128 void AmoebaToDiamond(int, int);
1129 void ContinueMoving(int, int);
1130 void Bang(int, int);
1131 void InitMovDir(int, int);
1132 void InitAmoebaNr(int, int);
1133 void NewHighScore(int, boolean);
1135 void TestIfGoodThingHitsBadThing(int, int, int);
1136 void TestIfBadThingHitsGoodThing(int, int, int);
1137 void TestIfPlayerTouchesBadThing(int, int);
1138 void TestIfPlayerRunsIntoBadThing(int, int, int);
1139 void TestIfBadThingTouchesPlayer(int, int);
1140 void TestIfBadThingRunsIntoPlayer(int, int, int);
1141 void TestIfFriendTouchesBadThing(int, int);
1142 void TestIfBadThingTouchesFriend(int, int);
1143 void TestIfBadThingTouchesOtherBadThing(int, int);
1144 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1146 void KillPlayer(struct PlayerInfo *);
1147 void BuryPlayer(struct PlayerInfo *);
1148 void RemovePlayer(struct PlayerInfo *);
1149 void ExitPlayer(struct PlayerInfo *);
1151 static int getInvisibleActiveFromInvisibleElement(int);
1152 static int getInvisibleFromInvisibleActiveElement(int);
1154 static void TestFieldAfterSnapping(int, int, int, int, int);
1156 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1158 // for detection of endless loops, caused by custom element programming
1159 // (using maximal playfield width x 10 is just a rough approximation)
1160 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1162 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1164 if (recursion_loop_detected) \
1167 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1169 recursion_loop_detected = TRUE; \
1170 recursion_loop_element = (e); \
1173 recursion_loop_depth++; \
1176 #define RECURSION_LOOP_DETECTION_END() \
1178 recursion_loop_depth--; \
1181 static int recursion_loop_depth;
1182 static boolean recursion_loop_detected;
1183 static boolean recursion_loop_element;
1185 static int map_player_action[MAX_PLAYERS];
1188 // ----------------------------------------------------------------------------
1189 // definition of elements that automatically change to other elements after
1190 // a specified time, eventually calling a function when changing
1191 // ----------------------------------------------------------------------------
1193 // forward declaration for changer functions
1194 static void InitBuggyBase(int, int);
1195 static void WarnBuggyBase(int, int);
1197 static void InitTrap(int, int);
1198 static void ActivateTrap(int, int);
1199 static void ChangeActiveTrap(int, int);
1201 static void InitRobotWheel(int, int);
1202 static void RunRobotWheel(int, int);
1203 static void StopRobotWheel(int, int);
1205 static void InitTimegateWheel(int, int);
1206 static void RunTimegateWheel(int, int);
1208 static void InitMagicBallDelay(int, int);
1209 static void ActivateMagicBall(int, int);
1211 struct ChangingElementInfo
1216 void (*pre_change_function)(int x, int y);
1217 void (*change_function)(int x, int y);
1218 void (*post_change_function)(int x, int y);
1221 static struct ChangingElementInfo change_delay_list[] =
1256 EL_STEEL_EXIT_OPENING,
1264 EL_STEEL_EXIT_CLOSING,
1265 EL_STEEL_EXIT_CLOSED,
1288 EL_EM_STEEL_EXIT_OPENING,
1289 EL_EM_STEEL_EXIT_OPEN,
1296 EL_EM_STEEL_EXIT_CLOSING,
1320 EL_SWITCHGATE_OPENING,
1328 EL_SWITCHGATE_CLOSING,
1329 EL_SWITCHGATE_CLOSED,
1336 EL_TIMEGATE_OPENING,
1344 EL_TIMEGATE_CLOSING,
1353 EL_ACID_SPLASH_LEFT,
1361 EL_ACID_SPLASH_RIGHT,
1370 EL_SP_BUGGY_BASE_ACTIVATING,
1377 EL_SP_BUGGY_BASE_ACTIVATING,
1378 EL_SP_BUGGY_BASE_ACTIVE,
1385 EL_SP_BUGGY_BASE_ACTIVE,
1409 EL_ROBOT_WHEEL_ACTIVE,
1417 EL_TIMEGATE_SWITCH_ACTIVE,
1425 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1426 EL_DC_TIMEGATE_SWITCH,
1433 EL_EMC_MAGIC_BALL_ACTIVE,
1434 EL_EMC_MAGIC_BALL_ACTIVE,
1441 EL_EMC_SPRING_BUMPER_ACTIVE,
1442 EL_EMC_SPRING_BUMPER,
1449 EL_DIAGONAL_SHRINKING,
1457 EL_DIAGONAL_GROWING,
1478 int push_delay_fixed, push_delay_random;
1482 { EL_SPRING, 0, 0 },
1483 { EL_BALLOON, 0, 0 },
1485 { EL_SOKOBAN_OBJECT, 2, 0 },
1486 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1487 { EL_SATELLITE, 2, 0 },
1488 { EL_SP_DISK_YELLOW, 2, 0 },
1490 { EL_UNDEFINED, 0, 0 },
1498 move_stepsize_list[] =
1500 { EL_AMOEBA_DROP, 2 },
1501 { EL_AMOEBA_DROPPING, 2 },
1502 { EL_QUICKSAND_FILLING, 1 },
1503 { EL_QUICKSAND_EMPTYING, 1 },
1504 { EL_QUICKSAND_FAST_FILLING, 2 },
1505 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1506 { EL_MAGIC_WALL_FILLING, 2 },
1507 { EL_MAGIC_WALL_EMPTYING, 2 },
1508 { EL_BD_MAGIC_WALL_FILLING, 2 },
1509 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1510 { EL_DC_MAGIC_WALL_FILLING, 2 },
1511 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1513 { EL_UNDEFINED, 0 },
1521 collect_count_list[] =
1524 { EL_BD_DIAMOND, 1 },
1525 { EL_EMERALD_YELLOW, 1 },
1526 { EL_EMERALD_RED, 1 },
1527 { EL_EMERALD_PURPLE, 1 },
1529 { EL_SP_INFOTRON, 1 },
1533 { EL_UNDEFINED, 0 },
1541 access_direction_list[] =
1543 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1544 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1545 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1546 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1547 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1548 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1549 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1550 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1551 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1552 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1553 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1555 { EL_SP_PORT_LEFT, MV_RIGHT },
1556 { EL_SP_PORT_RIGHT, MV_LEFT },
1557 { EL_SP_PORT_UP, MV_DOWN },
1558 { EL_SP_PORT_DOWN, MV_UP },
1559 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1560 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1561 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1562 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1563 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1564 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1565 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1566 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1567 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1568 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1569 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1570 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1571 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1572 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1573 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1575 { EL_UNDEFINED, MV_NONE }
1578 static struct XY xy_topdown[] =
1586 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1588 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1589 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1590 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1591 IS_JUST_CHANGING(x, y))
1593 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1595 // static variables for playfield scan mode (scanning forward or backward)
1596 static int playfield_scan_start_x = 0;
1597 static int playfield_scan_start_y = 0;
1598 static int playfield_scan_delta_x = 1;
1599 static int playfield_scan_delta_y = 1;
1601 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1602 (y) >= 0 && (y) <= lev_fieldy - 1; \
1603 (y) += playfield_scan_delta_y) \
1604 for ((x) = playfield_scan_start_x; \
1605 (x) >= 0 && (x) <= lev_fieldx - 1; \
1606 (x) += playfield_scan_delta_x)
1609 void DEBUG_SetMaximumDynamite(void)
1613 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1614 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1615 local_player->inventory_element[local_player->inventory_size++] =
1620 static void InitPlayfieldScanModeVars(void)
1622 if (game.use_reverse_scan_direction)
1624 playfield_scan_start_x = lev_fieldx - 1;
1625 playfield_scan_start_y = lev_fieldy - 1;
1627 playfield_scan_delta_x = -1;
1628 playfield_scan_delta_y = -1;
1632 playfield_scan_start_x = 0;
1633 playfield_scan_start_y = 0;
1635 playfield_scan_delta_x = 1;
1636 playfield_scan_delta_y = 1;
1640 static void InitPlayfieldScanMode(int mode)
1642 game.use_reverse_scan_direction =
1643 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1645 InitPlayfieldScanModeVars();
1648 static int get_move_delay_from_stepsize(int move_stepsize)
1651 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1653 // make sure that stepsize value is always a power of 2
1654 move_stepsize = (1 << log_2(move_stepsize));
1656 return TILEX / move_stepsize;
1659 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1662 int player_nr = player->index_nr;
1663 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1664 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1666 // do no immediately change move delay -- the player might just be moving
1667 player->move_delay_value_next = move_delay;
1669 // information if player can move must be set separately
1670 player->cannot_move = cannot_move;
1674 player->move_delay = game.initial_move_delay[player_nr];
1675 player->move_delay_value = game.initial_move_delay_value[player_nr];
1677 player->move_delay_value_next = -1;
1679 player->move_delay_reset_counter = 0;
1683 void GetPlayerConfig(void)
1685 GameFrameDelay = setup.game_frame_delay;
1687 if (!audio.sound_available)
1688 setup.sound_simple = FALSE;
1690 if (!audio.loops_available)
1691 setup.sound_loops = FALSE;
1693 if (!audio.music_available)
1694 setup.sound_music = FALSE;
1696 if (!video.fullscreen_available)
1697 setup.fullscreen = FALSE;
1699 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1701 SetAudioMode(setup.sound);
1704 int GetElementFromGroupElement(int element)
1706 if (IS_GROUP_ELEMENT(element))
1708 struct ElementGroupInfo *group = element_info[element].group;
1709 int last_anim_random_frame = gfx.anim_random_frame;
1712 if (group->choice_mode == ANIM_RANDOM)
1713 gfx.anim_random_frame = RND(group->num_elements_resolved);
1715 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1716 group->choice_mode, 0,
1719 if (group->choice_mode == ANIM_RANDOM)
1720 gfx.anim_random_frame = last_anim_random_frame;
1722 group->choice_pos++;
1724 element = group->element_resolved[element_pos];
1730 static void IncrementSokobanFieldsNeeded(void)
1732 if (level.sb_fields_needed)
1733 game.sokoban_fields_still_needed++;
1736 static void IncrementSokobanObjectsNeeded(void)
1738 if (level.sb_objects_needed)
1739 game.sokoban_objects_still_needed++;
1742 static void DecrementSokobanFieldsNeeded(void)
1744 if (game.sokoban_fields_still_needed > 0)
1745 game.sokoban_fields_still_needed--;
1748 static void DecrementSokobanObjectsNeeded(void)
1750 if (game.sokoban_objects_still_needed > 0)
1751 game.sokoban_objects_still_needed--;
1754 static void InitPlayerField(int x, int y, int element, boolean init_game)
1756 if (element == EL_SP_MURPHY)
1760 if (stored_player[0].present)
1762 Tile[x][y] = EL_SP_MURPHY_CLONE;
1768 stored_player[0].initial_element = element;
1769 stored_player[0].use_murphy = TRUE;
1771 if (!level.use_artwork_element[0])
1772 stored_player[0].artwork_element = EL_SP_MURPHY;
1775 Tile[x][y] = EL_PLAYER_1;
1781 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1782 int jx = player->jx, jy = player->jy;
1784 player->present = TRUE;
1786 player->block_last_field = (element == EL_SP_MURPHY ?
1787 level.sp_block_last_field :
1788 level.block_last_field);
1790 // ---------- initialize player's last field block delay ------------------
1792 // always start with reliable default value (no adjustment needed)
1793 player->block_delay_adjustment = 0;
1795 // special case 1: in Supaplex, Murphy blocks last field one more frame
1796 if (player->block_last_field && element == EL_SP_MURPHY)
1797 player->block_delay_adjustment = 1;
1799 // special case 2: in game engines before 3.1.1, blocking was different
1800 if (game.use_block_last_field_bug)
1801 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1803 if (!network.enabled || player->connected_network)
1805 player->active = TRUE;
1807 // remove potentially duplicate players
1808 if (IN_LEV_FIELD(jx, jy) && StorePlayer[jx][jy] == Tile[x][y])
1809 StorePlayer[jx][jy] = 0;
1811 StorePlayer[x][y] = Tile[x][y];
1813 #if DEBUG_INIT_PLAYER
1814 Debug("game:init:player", "- player element %d activated",
1815 player->element_nr);
1816 Debug("game:init:player", " (local player is %d and currently %s)",
1817 local_player->element_nr,
1818 local_player->active ? "active" : "not active");
1822 Tile[x][y] = EL_EMPTY;
1824 player->jx = player->last_jx = x;
1825 player->jy = player->last_jy = y;
1828 // always check if player was just killed and should be reanimated
1830 int player_nr = GET_PLAYER_NR(element);
1831 struct PlayerInfo *player = &stored_player[player_nr];
1833 if (player->active && player->killed)
1834 player->reanimated = TRUE; // if player was just killed, reanimate him
1838 static void InitFieldForEngine_RND(int x, int y)
1840 int element = Tile[x][y];
1842 // convert BD engine elements to corresponding R'n'D engine elements
1843 element = (element == EL_BD_EMPTY ? EL_EMPTY :
1844 element == EL_BD_PLAYER ? EL_PLAYER_1 :
1845 element == EL_BD_INBOX ? EL_PLAYER_1 :
1846 element == EL_BD_SAND ? EL_SAND :
1847 element == EL_BD_STEELWALL ? EL_STEELWALL :
1848 element == EL_BD_EXIT_CLOSED ? EL_EXIT_CLOSED :
1849 element == EL_BD_EXIT_OPEN ? EL_EXIT_OPEN :
1852 Tile[x][y] = element;
1855 static void InitFieldForEngine(int x, int y)
1857 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
1858 InitFieldForEngine_RND(x, y);
1861 static void InitField(int x, int y, boolean init_game)
1863 int element = Tile[x][y];
1872 InitPlayerField(x, y, element, init_game);
1875 case EL_SOKOBAN_FIELD_PLAYER:
1876 element = Tile[x][y] = EL_PLAYER_1;
1877 InitField(x, y, init_game);
1879 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1880 InitField(x, y, init_game);
1883 case EL_SOKOBAN_FIELD_EMPTY:
1884 IncrementSokobanFieldsNeeded();
1887 case EL_SOKOBAN_OBJECT:
1888 IncrementSokobanObjectsNeeded();
1892 if (x < lev_fieldx - 1 && Tile[x + 1][y] == EL_ACID)
1893 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1894 else if (x > 0 && Tile[x - 1][y] == EL_ACID)
1895 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1896 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPLEFT)
1897 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1898 else if (y > 0 && Tile[x][y - 1] == EL_ACID)
1899 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1900 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPRIGHT)
1901 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1910 case EL_SPACESHIP_RIGHT:
1911 case EL_SPACESHIP_UP:
1912 case EL_SPACESHIP_LEFT:
1913 case EL_SPACESHIP_DOWN:
1914 case EL_BD_BUTTERFLY:
1915 case EL_BD_BUTTERFLY_RIGHT:
1916 case EL_BD_BUTTERFLY_UP:
1917 case EL_BD_BUTTERFLY_LEFT:
1918 case EL_BD_BUTTERFLY_DOWN:
1920 case EL_BD_FIREFLY_RIGHT:
1921 case EL_BD_FIREFLY_UP:
1922 case EL_BD_FIREFLY_LEFT:
1923 case EL_BD_FIREFLY_DOWN:
1924 case EL_PACMAN_RIGHT:
1926 case EL_PACMAN_LEFT:
1927 case EL_PACMAN_DOWN:
1929 case EL_YAMYAM_LEFT:
1930 case EL_YAMYAM_RIGHT:
1932 case EL_YAMYAM_DOWN:
1933 case EL_DARK_YAMYAM:
1936 case EL_SP_SNIKSNAK:
1937 case EL_SP_ELECTRON:
1943 case EL_SPRING_LEFT:
1944 case EL_SPRING_RIGHT:
1948 case EL_AMOEBA_FULL:
1953 case EL_AMOEBA_DROP:
1954 if (y == lev_fieldy - 1)
1956 Tile[x][y] = EL_AMOEBA_GROWING;
1957 Store[x][y] = EL_AMOEBA_WET;
1961 case EL_DYNAMITE_ACTIVE:
1962 case EL_SP_DISK_RED_ACTIVE:
1963 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1964 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1965 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1966 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1967 MovDelay[x][y] = 96;
1970 case EL_EM_DYNAMITE_ACTIVE:
1971 MovDelay[x][y] = 32;
1975 game.lights_still_needed++;
1979 game.friends_still_needed++;
1984 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1987 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1988 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1989 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1990 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1991 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1992 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1993 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1994 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1995 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1996 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1997 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1998 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
2001 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
2002 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
2003 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
2005 if (game.belt_dir_nr[belt_nr] == 3) // initial value
2007 game.belt_dir[belt_nr] = belt_dir;
2008 game.belt_dir_nr[belt_nr] = belt_dir_nr;
2010 else // more than one switch -- set it like the first switch
2012 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
2017 case EL_LIGHT_SWITCH_ACTIVE:
2019 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
2022 case EL_INVISIBLE_STEELWALL:
2023 case EL_INVISIBLE_WALL:
2024 case EL_INVISIBLE_SAND:
2025 if (game.light_time_left > 0 ||
2026 game.lenses_time_left > 0)
2027 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
2030 case EL_EMC_MAGIC_BALL:
2031 if (game.ball_active)
2032 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
2035 case EL_EMC_MAGIC_BALL_SWITCH:
2036 if (game.ball_active)
2037 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
2040 case EL_TRIGGER_PLAYER:
2041 case EL_TRIGGER_ELEMENT:
2042 case EL_TRIGGER_CE_VALUE:
2043 case EL_TRIGGER_CE_SCORE:
2045 case EL_ANY_ELEMENT:
2046 case EL_CURRENT_CE_VALUE:
2047 case EL_CURRENT_CE_SCORE:
2064 // reference elements should not be used on the playfield
2065 Tile[x][y] = EL_EMPTY;
2069 if (IS_CUSTOM_ELEMENT(element))
2071 if (CAN_MOVE(element))
2074 if (!element_info[element].use_last_ce_value || init_game)
2075 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2077 else if (IS_GROUP_ELEMENT(element))
2079 Tile[x][y] = GetElementFromGroupElement(element);
2081 InitField(x, y, init_game);
2083 else if (IS_EMPTY_ELEMENT(element))
2085 GfxElementEmpty[x][y] = element;
2086 Tile[x][y] = EL_EMPTY;
2088 if (element_info[element].use_gfx_element)
2089 game.use_masked_elements = TRUE;
2096 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2099 static void InitField_WithBug1(int x, int y, boolean init_game)
2101 InitField(x, y, init_game);
2103 // not needed to call InitMovDir() -- already done by InitField()!
2104 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2105 CAN_MOVE(Tile[x][y]))
2109 static void InitField_WithBug2(int x, int y, boolean init_game)
2111 int old_element = Tile[x][y];
2113 InitField(x, y, init_game);
2115 // not needed to call InitMovDir() -- already done by InitField()!
2116 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2117 CAN_MOVE(old_element) &&
2118 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2121 /* this case is in fact a combination of not less than three bugs:
2122 first, it calls InitMovDir() for elements that can move, although this is
2123 already done by InitField(); then, it checks the element that was at this
2124 field _before_ the call to InitField() (which can change it); lastly, it
2125 was not called for "mole with direction" elements, which were treated as
2126 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2130 static int get_key_element_from_nr(int key_nr)
2132 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2133 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2134 EL_EM_KEY_1 : EL_KEY_1);
2136 return key_base_element + key_nr;
2139 static int get_next_dropped_element(struct PlayerInfo *player)
2141 return (player->inventory_size > 0 ?
2142 player->inventory_element[player->inventory_size - 1] :
2143 player->inventory_infinite_element != EL_UNDEFINED ?
2144 player->inventory_infinite_element :
2145 player->dynabombs_left > 0 ?
2146 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2150 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2152 // pos >= 0: get element from bottom of the stack;
2153 // pos < 0: get element from top of the stack
2157 int min_inventory_size = -pos;
2158 int inventory_pos = player->inventory_size - min_inventory_size;
2159 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2161 return (player->inventory_size >= min_inventory_size ?
2162 player->inventory_element[inventory_pos] :
2163 player->inventory_infinite_element != EL_UNDEFINED ?
2164 player->inventory_infinite_element :
2165 player->dynabombs_left >= min_dynabombs_left ?
2166 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2171 int min_dynabombs_left = pos + 1;
2172 int min_inventory_size = pos + 1 - player->dynabombs_left;
2173 int inventory_pos = pos - player->dynabombs_left;
2175 return (player->inventory_infinite_element != EL_UNDEFINED ?
2176 player->inventory_infinite_element :
2177 player->dynabombs_left >= min_dynabombs_left ?
2178 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2179 player->inventory_size >= min_inventory_size ?
2180 player->inventory_element[inventory_pos] :
2185 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2187 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2188 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2191 if (gpo1->sort_priority != gpo2->sort_priority)
2192 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2194 compare_result = gpo1->nr - gpo2->nr;
2196 return compare_result;
2199 int getPlayerInventorySize(int player_nr)
2201 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2202 return game_em.ply[player_nr]->dynamite;
2203 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2204 return game_sp.red_disk_count;
2206 return stored_player[player_nr].inventory_size;
2209 static void InitGameControlValues(void)
2213 for (i = 0; game_panel_controls[i].nr != -1; i++)
2215 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2216 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2217 struct TextPosInfo *pos = gpc->pos;
2219 int type = gpc->type;
2223 Error("'game_panel_controls' structure corrupted at %d", i);
2225 Fail("this should not happen -- please debug");
2228 // force update of game controls after initialization
2229 gpc->value = gpc->last_value = -1;
2230 gpc->frame = gpc->last_frame = -1;
2231 gpc->gfx_frame = -1;
2233 // determine panel value width for later calculation of alignment
2234 if (type == TYPE_INTEGER || type == TYPE_STRING)
2236 pos->width = pos->size * getFontWidth(pos->font);
2237 pos->height = getFontHeight(pos->font);
2239 else if (type == TYPE_ELEMENT)
2241 pos->width = pos->size;
2242 pos->height = pos->size;
2245 // fill structure for game panel draw order
2247 gpo->sort_priority = pos->sort_priority;
2250 // sort game panel controls according to sort_priority and control number
2251 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2252 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2255 static void UpdatePlayfieldElementCount(void)
2257 boolean use_element_count = FALSE;
2260 // first check if it is needed at all to calculate playfield element count
2261 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2262 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2263 use_element_count = TRUE;
2265 if (!use_element_count)
2268 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2269 element_info[i].element_count = 0;
2271 SCAN_PLAYFIELD(x, y)
2273 element_info[Tile[x][y]].element_count++;
2276 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2277 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2278 if (IS_IN_GROUP(j, i))
2279 element_info[EL_GROUP_START + i].element_count +=
2280 element_info[j].element_count;
2283 static void UpdateGameControlValues(void)
2286 int time = (game.LevelSolved ?
2287 game.LevelSolved_CountingTime :
2288 level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2289 game_bd.time_played :
2290 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2292 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2293 game_sp.time_played :
2294 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2295 game_mm.energy_left :
2296 game.no_level_time_limit ? TimePlayed : TimeLeft);
2297 int score = (game.LevelSolved ?
2298 game.LevelSolved_CountingScore :
2299 level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2301 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2302 game_em.lev->score :
2303 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2305 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2308 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2309 game_bd.gems_still_needed :
2310 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2311 game_em.lev->gems_needed :
2312 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2313 game_sp.infotrons_still_needed :
2314 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2315 game_mm.kettles_still_needed :
2316 game.gems_still_needed);
2317 int gems_needed = level.gems_needed;
2318 int gems_collected = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2319 game_bd.game->cave->diamonds_collected :
2320 gems_needed - gems);
2321 int gems_score = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2322 game_bd.game->cave->diamond_value :
2323 level.score[SC_EMERALD]);
2324 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_BD ?
2325 game_bd.gems_still_needed > 0 :
2326 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2327 game_em.lev->gems_needed > 0 :
2328 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2329 game_sp.infotrons_still_needed > 0 :
2330 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2331 game_mm.kettles_still_needed > 0 ||
2332 game_mm.lights_still_needed > 0 :
2333 game.gems_still_needed > 0 ||
2334 game.sokoban_fields_still_needed > 0 ||
2335 game.sokoban_objects_still_needed > 0 ||
2336 game.lights_still_needed > 0);
2337 int health = (game.LevelSolved ?
2338 game.LevelSolved_CountingHealth :
2339 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2340 MM_HEALTH(game_mm.laser_overload_value) :
2342 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2344 UpdatePlayfieldElementCount();
2346 // update game panel control values
2348 // used instead of "level_nr" (for network games)
2349 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2350 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2351 game_panel_controls[GAME_PANEL_GEMS_NEEDED].value = gems_needed;
2352 game_panel_controls[GAME_PANEL_GEMS_COLLECTED].value = gems_collected;
2353 game_panel_controls[GAME_PANEL_GEMS_SCORE].value = gems_score;
2355 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2356 for (i = 0; i < MAX_NUM_KEYS; i++)
2357 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2358 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2359 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2361 if (game.centered_player_nr == -1)
2363 for (i = 0; i < MAX_PLAYERS; i++)
2365 // only one player in Supaplex game engine
2366 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2369 for (k = 0; k < MAX_NUM_KEYS; k++)
2371 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2373 if (game_em.ply[i]->keys & (1 << k))
2374 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2375 get_key_element_from_nr(k);
2377 else if (stored_player[i].key[k])
2378 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2379 get_key_element_from_nr(k);
2382 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2383 getPlayerInventorySize(i);
2385 if (stored_player[i].num_white_keys > 0)
2386 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2389 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2390 stored_player[i].num_white_keys;
2395 int player_nr = game.centered_player_nr;
2397 for (k = 0; k < MAX_NUM_KEYS; k++)
2399 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2401 if (game_em.ply[player_nr]->keys & (1 << k))
2402 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2403 get_key_element_from_nr(k);
2405 else if (stored_player[player_nr].key[k])
2406 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2407 get_key_element_from_nr(k);
2410 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2411 getPlayerInventorySize(player_nr);
2413 if (stored_player[player_nr].num_white_keys > 0)
2414 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2416 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2417 stored_player[player_nr].num_white_keys;
2420 // re-arrange keys on game panel, if needed or if defined by style settings
2421 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2423 int nr = GAME_PANEL_KEY_1 + i;
2424 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2425 struct TextPosInfo *pos = gpc->pos;
2427 // skip check if key is not in the player's inventory
2428 if (gpc->value == EL_EMPTY)
2431 // check if keys should be arranged on panel from left to right
2432 if (pos->style == STYLE_LEFTMOST_POSITION)
2434 // check previous key positions (left from current key)
2435 for (k = 0; k < i; k++)
2437 int nr_new = GAME_PANEL_KEY_1 + k;
2439 if (game_panel_controls[nr_new].value == EL_EMPTY)
2441 game_panel_controls[nr_new].value = gpc->value;
2442 gpc->value = EL_EMPTY;
2449 // check if "undefined" keys can be placed at some other position
2450 if (pos->x == -1 && pos->y == -1)
2452 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2454 // 1st try: display key at the same position as normal or EM keys
2455 if (game_panel_controls[nr_new].value == EL_EMPTY)
2457 game_panel_controls[nr_new].value = gpc->value;
2461 // 2nd try: display key at the next free position in the key panel
2462 for (k = 0; k < STD_NUM_KEYS; k++)
2464 nr_new = GAME_PANEL_KEY_1 + k;
2466 if (game_panel_controls[nr_new].value == EL_EMPTY)
2468 game_panel_controls[nr_new].value = gpc->value;
2477 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2479 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2480 get_inventory_element_from_pos(local_player, i);
2481 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2482 get_inventory_element_from_pos(local_player, -i - 1);
2485 game_panel_controls[GAME_PANEL_SCORE].value = score;
2486 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2488 game_panel_controls[GAME_PANEL_TIME].value = time;
2490 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2491 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2492 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2494 if (level.time == 0)
2495 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2497 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2499 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2500 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2502 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2504 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2505 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2507 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2508 local_player->shield_normal_time_left;
2509 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2510 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2512 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2513 local_player->shield_deadly_time_left;
2515 game_panel_controls[GAME_PANEL_EXIT].value =
2516 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2518 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2519 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2520 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2521 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2522 EL_EMC_MAGIC_BALL_SWITCH);
2524 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2525 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2526 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2527 game.light_time_left;
2529 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2530 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2531 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2532 game.timegate_time_left;
2534 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2535 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2537 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2538 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2539 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2540 game.lenses_time_left;
2542 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2543 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2544 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2545 game.magnify_time_left;
2547 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2548 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2549 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2550 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2551 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2552 EL_BALLOON_SWITCH_NONE);
2554 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2555 local_player->dynabomb_count;
2556 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2557 local_player->dynabomb_size;
2558 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2559 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2561 game_panel_controls[GAME_PANEL_PENGUINS].value =
2562 game.friends_still_needed;
2564 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2565 game.sokoban_objects_still_needed;
2566 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2567 game.sokoban_fields_still_needed;
2569 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2570 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2572 for (i = 0; i < NUM_BELTS; i++)
2574 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2575 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2576 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2577 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2578 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2581 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2582 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2583 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2584 game.magic_wall_time_left;
2586 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2587 local_player->gravity;
2589 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2590 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2592 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2593 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2594 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2595 game.panel.element[i].id : EL_UNDEFINED);
2597 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2598 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2599 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2600 element_info[game.panel.element_count[i].id].element_count : 0);
2602 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2603 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2604 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2605 element_info[game.panel.ce_score[i].id].collect_score : 0);
2607 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2608 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2609 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2610 element_info[game.panel.ce_score_element[i].id].collect_score :
2613 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2614 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2615 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2617 // update game panel control frames
2619 for (i = 0; game_panel_controls[i].nr != -1; i++)
2621 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2623 if (gpc->type == TYPE_ELEMENT)
2625 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2627 int last_anim_random_frame = gfx.anim_random_frame;
2628 int element = gpc->value;
2629 int graphic = el2panelimg(element);
2630 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2632 graphic_info[graphic].anim_global_anim_sync ?
2633 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2635 if (gpc->value != gpc->last_value)
2638 gpc->gfx_random = init_gfx_random;
2644 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2645 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2646 gpc->gfx_random = init_gfx_random;
2649 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2650 gfx.anim_random_frame = gpc->gfx_random;
2652 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2653 gpc->gfx_frame = element_info[element].collect_score;
2655 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2657 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2658 gfx.anim_random_frame = last_anim_random_frame;
2661 else if (gpc->type == TYPE_GRAPHIC)
2663 if (gpc->graphic != IMG_UNDEFINED)
2665 int last_anim_random_frame = gfx.anim_random_frame;
2666 int graphic = gpc->graphic;
2667 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2669 graphic_info[graphic].anim_global_anim_sync ?
2670 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2672 if (gpc->value != gpc->last_value)
2675 gpc->gfx_random = init_gfx_random;
2681 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2682 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2683 gpc->gfx_random = init_gfx_random;
2686 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2687 gfx.anim_random_frame = gpc->gfx_random;
2689 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2691 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2692 gfx.anim_random_frame = last_anim_random_frame;
2698 static void DisplayGameControlValues(void)
2700 boolean redraw_panel = FALSE;
2703 for (i = 0; game_panel_controls[i].nr != -1; i++)
2705 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2707 if (PANEL_DEACTIVATED(gpc->pos))
2710 if (gpc->value == gpc->last_value &&
2711 gpc->frame == gpc->last_frame)
2714 redraw_panel = TRUE;
2720 // copy default game door content to main double buffer
2722 // !!! CHECK AGAIN !!!
2723 SetPanelBackground();
2724 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2725 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2727 // redraw game control buttons
2728 RedrawGameButtons();
2730 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2732 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2734 int nr = game_panel_order[i].nr;
2735 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2736 struct TextPosInfo *pos = gpc->pos;
2737 int type = gpc->type;
2738 int value = gpc->value;
2739 int frame = gpc->frame;
2740 int size = pos->size;
2741 int font = pos->font;
2742 boolean draw_masked = pos->draw_masked;
2743 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2745 if (PANEL_DEACTIVATED(pos))
2748 if (pos->class == get_hash_from_string("extra_panel_items") &&
2749 !setup.prefer_extra_panel_items)
2752 gpc->last_value = value;
2753 gpc->last_frame = frame;
2755 if (type == TYPE_INTEGER)
2757 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2758 nr == GAME_PANEL_INVENTORY_COUNT ||
2759 nr == GAME_PANEL_SCORE ||
2760 nr == GAME_PANEL_HIGHSCORE ||
2761 nr == GAME_PANEL_TIME)
2763 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2765 if (use_dynamic_size) // use dynamic number of digits
2767 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 :
2768 nr == GAME_PANEL_INVENTORY_COUNT ||
2769 nr == GAME_PANEL_TIME ? 1000 : 100000);
2770 int size_add = (nr == GAME_PANEL_LEVEL_NUMBER ||
2771 nr == GAME_PANEL_INVENTORY_COUNT ||
2772 nr == GAME_PANEL_TIME ? 1 : 2);
2773 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 :
2774 nr == GAME_PANEL_INVENTORY_COUNT ||
2775 nr == GAME_PANEL_TIME ? 3 : 5);
2776 int size2 = size1 + size_add;
2777 int font1 = pos->font;
2778 int font2 = pos->font_alt;
2780 size = (value < value_change ? size1 : size2);
2781 font = (value < value_change ? font1 : font2);
2785 // correct text size if "digits" is zero or less
2787 size = strlen(int2str(value, size));
2789 // dynamically correct text alignment
2790 pos->width = size * getFontWidth(font);
2792 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2793 int2str(value, size), font, mask_mode);
2795 else if (type == TYPE_ELEMENT)
2797 int element, graphic;
2801 int dst_x = PANEL_XPOS(pos);
2802 int dst_y = PANEL_YPOS(pos);
2804 if (value != EL_UNDEFINED && value != EL_EMPTY)
2807 graphic = el2panelimg(value);
2810 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2811 element, EL_NAME(element), size);
2814 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2817 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2820 width = graphic_info[graphic].width * size / TILESIZE;
2821 height = graphic_info[graphic].height * size / TILESIZE;
2824 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2827 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2831 else if (type == TYPE_GRAPHIC)
2833 int graphic = gpc->graphic;
2834 int graphic_active = gpc->graphic_active;
2838 int dst_x = PANEL_XPOS(pos);
2839 int dst_y = PANEL_YPOS(pos);
2840 boolean skip = (pos->class == get_hash_from_string("mm_engine_only") &&
2841 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2843 if (graphic != IMG_UNDEFINED && !skip)
2845 if (pos->style == STYLE_REVERSE)
2846 value = 100 - value;
2848 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2850 if (pos->direction & MV_HORIZONTAL)
2852 width = graphic_info[graphic_active].width * value / 100;
2853 height = graphic_info[graphic_active].height;
2855 if (pos->direction == MV_LEFT)
2857 src_x += graphic_info[graphic_active].width - width;
2858 dst_x += graphic_info[graphic_active].width - width;
2863 width = graphic_info[graphic_active].width;
2864 height = graphic_info[graphic_active].height * value / 100;
2866 if (pos->direction == MV_UP)
2868 src_y += graphic_info[graphic_active].height - height;
2869 dst_y += graphic_info[graphic_active].height - height;
2874 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2877 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2880 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2882 if (pos->direction & MV_HORIZONTAL)
2884 if (pos->direction == MV_RIGHT)
2891 dst_x = PANEL_XPOS(pos);
2894 width = graphic_info[graphic].width - width;
2898 if (pos->direction == MV_DOWN)
2905 dst_y = PANEL_YPOS(pos);
2908 height = graphic_info[graphic].height - height;
2912 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2915 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2919 else if (type == TYPE_STRING)
2921 boolean active = (value != 0);
2922 char *state_normal = "off";
2923 char *state_active = "on";
2924 char *state = (active ? state_active : state_normal);
2925 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2926 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2927 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2928 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2930 if (nr == GAME_PANEL_GRAVITY_STATE)
2932 int font1 = pos->font; // (used for normal state)
2933 int font2 = pos->font_alt; // (used for active state)
2935 font = (active ? font2 : font1);
2944 // don't truncate output if "chars" is zero or less
2947 // dynamically correct text alignment
2948 pos->width = size * getFontWidth(font);
2951 s_cut = getStringCopyN(s, size);
2953 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2954 s_cut, font, mask_mode);
2960 redraw_mask |= REDRAW_DOOR_1;
2963 SetGameStatus(GAME_MODE_PLAYING);
2966 void UpdateAndDisplayGameControlValues(void)
2968 if (tape.deactivate_display)
2971 UpdateGameControlValues();
2972 DisplayGameControlValues();
2975 void UpdateGameDoorValues(void)
2977 UpdateGameControlValues();
2980 void DrawGameDoorValues(void)
2982 DisplayGameControlValues();
2986 // ============================================================================
2988 // ----------------------------------------------------------------------------
2989 // initialize game engine due to level / tape version number
2990 // ============================================================================
2992 static void InitGameEngine(void)
2994 int i, j, k, l, x, y;
2996 // set game engine from tape file when re-playing, else from level file
2997 game.engine_version = (tape.playing ? tape.engine_version :
2998 level.game_version);
3000 // set single or multi-player game mode (needed for re-playing tapes)
3001 game.team_mode = setup.team_mode;
3005 int num_players = 0;
3007 for (i = 0; i < MAX_PLAYERS; i++)
3008 if (tape.player_participates[i])
3011 // multi-player tapes contain input data for more than one player
3012 game.team_mode = (num_players > 1);
3016 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
3017 level.game_version);
3018 Debug("game:init:level", " tape.file_version == %06d",
3020 Debug("game:init:level", " tape.game_version == %06d",
3022 Debug("game:init:level", " tape.engine_version == %06d",
3023 tape.engine_version);
3024 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
3025 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
3028 // --------------------------------------------------------------------------
3029 // set flags for bugs and changes according to active game engine version
3030 // --------------------------------------------------------------------------
3034 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
3036 Bug was introduced in version:
3039 Bug was fixed in version:
3043 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
3044 but the property "can fall" was missing, which caused some levels to be
3045 unsolvable. This was fixed in version 4.2.0.0.
3047 Affected levels/tapes:
3048 An example for a tape that was fixed by this bugfix is tape 029 from the
3049 level set "rnd_sam_bateman".
3050 The wrong behaviour will still be used for all levels or tapes that were
3051 created/recorded with it. An example for this is tape 023 from the level
3052 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
3055 boolean use_amoeba_dropping_cannot_fall_bug =
3056 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
3057 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
3059 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3060 tape.game_version < VERSION_IDENT(4,2,0,0)));
3063 Summary of bugfix/change:
3064 Fixed move speed of elements entering or leaving magic wall.
3066 Fixed/changed in version:
3070 Before 2.0.1, move speed of elements entering or leaving magic wall was
3071 twice as fast as it is now.
3072 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
3074 Affected levels/tapes:
3075 The first condition is generally needed for all levels/tapes before version
3076 2.0.1, which might use the old behaviour before it was changed; known tapes
3077 that are affected: Tape 014 from the level set "rnd_conor_mancone".
3078 The second condition is an exception from the above case and is needed for
3079 the special case of tapes recorded with game (not engine!) version 2.0.1 or
3080 above, but before it was known that this change would break tapes like the
3081 above and was fixed in 4.2.0.0, so that the changed behaviour was active
3082 although the engine version while recording maybe was before 2.0.1. There
3083 are a lot of tapes that are affected by this exception, like tape 006 from
3084 the level set "rnd_conor_mancone".
3087 boolean use_old_move_stepsize_for_magic_wall =
3088 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
3090 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3091 tape.game_version < VERSION_IDENT(4,2,0,0)));
3094 Summary of bugfix/change:
3095 Fixed handling for custom elements that change when pushed by the player.
3097 Fixed/changed in version:
3101 Before 3.1.0, custom elements that "change when pushing" changed directly
3102 after the player started pushing them (until then handled in "DigField()").
3103 Since 3.1.0, these custom elements are not changed until the "pushing"
3104 move of the element is finished (now handled in "ContinueMoving()").
3106 Affected levels/tapes:
3107 The first condition is generally needed for all levels/tapes before version
3108 3.1.0, which might use the old behaviour before it was changed; known tapes
3109 that are affected are some tapes from the level set "Walpurgis Gardens" by
3111 The second condition is an exception from the above case and is needed for
3112 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3113 above (including some development versions of 3.1.0), but before it was
3114 known that this change would break tapes like the above and was fixed in
3115 3.1.1, so that the changed behaviour was active although the engine version
3116 while recording maybe was before 3.1.0. There is at least one tape that is
3117 affected by this exception, which is the tape for the one-level set "Bug
3118 Machine" by Juergen Bonhagen.
3121 game.use_change_when_pushing_bug =
3122 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3124 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3125 tape.game_version < VERSION_IDENT(3,1,1,0)));
3128 Summary of bugfix/change:
3129 Fixed handling for blocking the field the player leaves when moving.
3131 Fixed/changed in version:
3135 Before 3.1.1, when "block last field when moving" was enabled, the field
3136 the player is leaving when moving was blocked for the time of the move,
3137 and was directly unblocked afterwards. This resulted in the last field
3138 being blocked for exactly one less than the number of frames of one player
3139 move. Additionally, even when blocking was disabled, the last field was
3140 blocked for exactly one frame.
3141 Since 3.1.1, due to changes in player movement handling, the last field
3142 is not blocked at all when blocking is disabled. When blocking is enabled,
3143 the last field is blocked for exactly the number of frames of one player
3144 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3145 last field is blocked for exactly one more than the number of frames of
3148 Affected levels/tapes:
3149 (!!! yet to be determined -- probably many !!!)
3152 game.use_block_last_field_bug =
3153 (game.engine_version < VERSION_IDENT(3,1,1,0));
3155 /* various special flags and settings for native Emerald Mine game engine */
3157 game_em.use_single_button =
3158 (game.engine_version > VERSION_IDENT(4,0,0,2));
3160 game_em.use_push_delay =
3161 (game.engine_version > VERSION_IDENT(4,3,7,1));
3163 game_em.use_snap_key_bug =
3164 (game.engine_version < VERSION_IDENT(4,0,1,0));
3166 game_em.use_random_bug =
3167 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3169 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3171 game_em.use_old_explosions = use_old_em_engine;
3172 game_em.use_old_android = use_old_em_engine;
3173 game_em.use_old_push_elements = use_old_em_engine;
3174 game_em.use_old_push_into_acid = use_old_em_engine;
3176 game_em.use_wrap_around = !use_old_em_engine;
3178 // --------------------------------------------------------------------------
3180 // set maximal allowed number of custom element changes per game frame
3181 game.max_num_changes_per_frame = 1;
3183 // default scan direction: scan playfield from top/left to bottom/right
3184 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3186 // dynamically adjust element properties according to game engine version
3187 InitElementPropertiesEngine(game.engine_version);
3189 // ---------- initialize special element properties -------------------------
3191 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3192 if (use_amoeba_dropping_cannot_fall_bug)
3193 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3195 // ---------- initialize player's initial move delay ------------------------
3197 // dynamically adjust player properties according to level information
3198 for (i = 0; i < MAX_PLAYERS; i++)
3199 game.initial_move_delay_value[i] =
3200 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3202 // dynamically adjust player properties according to game engine version
3203 for (i = 0; i < MAX_PLAYERS; i++)
3204 game.initial_move_delay[i] =
3205 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3206 game.initial_move_delay_value[i] : 0);
3208 // ---------- initialize player's initial push delay ------------------------
3210 // dynamically adjust player properties according to game engine version
3211 game.initial_push_delay_value =
3212 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3214 // ---------- initialize changing elements ----------------------------------
3216 // initialize changing elements information
3217 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3219 struct ElementInfo *ei = &element_info[i];
3221 // this pointer might have been changed in the level editor
3222 ei->change = &ei->change_page[0];
3224 if (!IS_CUSTOM_ELEMENT(i))
3226 ei->change->target_element = EL_EMPTY_SPACE;
3227 ei->change->delay_fixed = 0;
3228 ei->change->delay_random = 0;
3229 ei->change->delay_frames = 1;
3232 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3234 ei->has_change_event[j] = FALSE;
3236 ei->event_page_nr[j] = 0;
3237 ei->event_page[j] = &ei->change_page[0];
3241 // add changing elements from pre-defined list
3242 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3244 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3245 struct ElementInfo *ei = &element_info[ch_delay->element];
3247 ei->change->target_element = ch_delay->target_element;
3248 ei->change->delay_fixed = ch_delay->change_delay;
3250 ei->change->pre_change_function = ch_delay->pre_change_function;
3251 ei->change->change_function = ch_delay->change_function;
3252 ei->change->post_change_function = ch_delay->post_change_function;
3254 ei->change->can_change = TRUE;
3255 ei->change->can_change_or_has_action = TRUE;
3257 ei->has_change_event[CE_DELAY] = TRUE;
3259 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3260 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3263 // ---------- initialize if element can trigger global animations -----------
3265 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3267 struct ElementInfo *ei = &element_info[i];
3269 ei->has_anim_event = FALSE;
3272 InitGlobalAnimEventsForCustomElements();
3274 // ---------- initialize internal run-time variables ------------------------
3276 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3278 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3280 for (j = 0; j < ei->num_change_pages; j++)
3282 ei->change_page[j].can_change_or_has_action =
3283 (ei->change_page[j].can_change |
3284 ei->change_page[j].has_action);
3288 // add change events from custom element configuration
3289 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3291 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3293 for (j = 0; j < ei->num_change_pages; j++)
3295 if (!ei->change_page[j].can_change_or_has_action)
3298 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3300 // only add event page for the first page found with this event
3301 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3303 ei->has_change_event[k] = TRUE;
3305 ei->event_page_nr[k] = j;
3306 ei->event_page[k] = &ei->change_page[j];
3312 // ---------- initialize reference elements in change conditions ------------
3314 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3316 int element = EL_CUSTOM_START + i;
3317 struct ElementInfo *ei = &element_info[element];
3319 for (j = 0; j < ei->num_change_pages; j++)
3321 int trigger_element = ei->change_page[j].initial_trigger_element;
3323 if (trigger_element >= EL_PREV_CE_8 &&
3324 trigger_element <= EL_NEXT_CE_8)
3325 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3327 ei->change_page[j].trigger_element = trigger_element;
3331 // ---------- initialize run-time trigger player and element ----------------
3333 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3335 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3337 for (j = 0; j < ei->num_change_pages; j++)
3339 struct ElementChangeInfo *change = &ei->change_page[j];
3341 change->actual_trigger_element = EL_EMPTY;
3342 change->actual_trigger_player = EL_EMPTY;
3343 change->actual_trigger_player_bits = CH_PLAYER_NONE;
3344 change->actual_trigger_side = CH_SIDE_NONE;
3345 change->actual_trigger_ce_value = 0;
3346 change->actual_trigger_ce_score = 0;
3347 change->actual_trigger_x = -1;
3348 change->actual_trigger_y = -1;
3352 // ---------- initialize trigger events -------------------------------------
3354 // initialize trigger events information
3355 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3356 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3357 trigger_events[i][j] = FALSE;
3359 // add trigger events from element change event properties
3360 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3362 struct ElementInfo *ei = &element_info[i];
3364 for (j = 0; j < ei->num_change_pages; j++)
3366 struct ElementChangeInfo *change = &ei->change_page[j];
3368 if (!change->can_change_or_has_action)
3371 if (change->has_event[CE_BY_OTHER_ACTION])
3373 int trigger_element = change->trigger_element;
3375 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3377 if (change->has_event[k])
3379 if (IS_GROUP_ELEMENT(trigger_element))
3381 struct ElementGroupInfo *group =
3382 element_info[trigger_element].group;
3384 for (l = 0; l < group->num_elements_resolved; l++)
3385 trigger_events[group->element_resolved[l]][k] = TRUE;
3387 else if (trigger_element == EL_ANY_ELEMENT)
3388 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3389 trigger_events[l][k] = TRUE;
3391 trigger_events[trigger_element][k] = TRUE;
3398 // ---------- initialize push delay -----------------------------------------
3400 // initialize push delay values to default
3401 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3403 if (!IS_CUSTOM_ELEMENT(i))
3405 // set default push delay values (corrected since version 3.0.7-1)
3406 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3408 element_info[i].push_delay_fixed = 2;
3409 element_info[i].push_delay_random = 8;
3413 element_info[i].push_delay_fixed = 8;
3414 element_info[i].push_delay_random = 8;
3419 // set push delay value for certain elements from pre-defined list
3420 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3422 int e = push_delay_list[i].element;
3424 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3425 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3428 // set push delay value for Supaplex elements for newer engine versions
3429 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3431 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3433 if (IS_SP_ELEMENT(i))
3435 // set SP push delay to just enough to push under a falling zonk
3436 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3438 element_info[i].push_delay_fixed = delay;
3439 element_info[i].push_delay_random = 0;
3444 // ---------- initialize move stepsize --------------------------------------
3446 // initialize move stepsize values to default
3447 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3448 if (!IS_CUSTOM_ELEMENT(i))
3449 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3451 // set move stepsize value for certain elements from pre-defined list
3452 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3454 int e = move_stepsize_list[i].element;
3456 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3458 // set move stepsize value for certain elements for older engine versions
3459 if (use_old_move_stepsize_for_magic_wall)
3461 if (e == EL_MAGIC_WALL_FILLING ||
3462 e == EL_MAGIC_WALL_EMPTYING ||
3463 e == EL_BD_MAGIC_WALL_FILLING ||
3464 e == EL_BD_MAGIC_WALL_EMPTYING)
3465 element_info[e].move_stepsize *= 2;
3469 // ---------- initialize collect score --------------------------------------
3471 // initialize collect score values for custom elements from initial value
3472 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3473 if (IS_CUSTOM_ELEMENT(i))
3474 element_info[i].collect_score = element_info[i].collect_score_initial;
3476 // ---------- initialize collect count --------------------------------------
3478 // initialize collect count values for non-custom elements
3479 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3480 if (!IS_CUSTOM_ELEMENT(i))
3481 element_info[i].collect_count_initial = 0;
3483 // add collect count values for all elements from pre-defined list
3484 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3485 element_info[collect_count_list[i].element].collect_count_initial =
3486 collect_count_list[i].count;
3488 // ---------- initialize access direction -----------------------------------
3490 // initialize access direction values to default (access from every side)
3491 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3492 if (!IS_CUSTOM_ELEMENT(i))
3493 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3495 // set access direction value for certain elements from pre-defined list
3496 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3497 element_info[access_direction_list[i].element].access_direction =
3498 access_direction_list[i].direction;
3500 // ---------- initialize explosion content ----------------------------------
3501 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3503 if (IS_CUSTOM_ELEMENT(i))
3506 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3508 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3510 element_info[i].content.e[x][y] =
3511 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3512 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3513 i == EL_PLAYER_3 ? EL_EMERALD :
3514 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3515 i == EL_MOLE ? EL_EMERALD_RED :
3516 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3517 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3518 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3519 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3520 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3521 i == EL_WALL_EMERALD ? EL_EMERALD :
3522 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3523 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3524 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3525 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3526 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3527 i == EL_WALL_PEARL ? EL_PEARL :
3528 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3533 // ---------- initialize recursion detection --------------------------------
3534 recursion_loop_depth = 0;
3535 recursion_loop_detected = FALSE;
3536 recursion_loop_element = EL_UNDEFINED;
3538 // ---------- initialize graphics engine ------------------------------------
3539 game.scroll_delay_value =
3540 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3541 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3542 !setup.forced_scroll_delay ? 0 :
3543 setup.scroll_delay ? setup.scroll_delay_value : 0);
3544 if (game.forced_scroll_delay_value == -1)
3545 game.scroll_delay_value =
3546 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3548 // ---------- initialize game engine snapshots ------------------------------
3549 for (i = 0; i < MAX_PLAYERS; i++)
3550 game.snapshot.last_action[i] = 0;
3551 game.snapshot.changed_action = FALSE;
3552 game.snapshot.collected_item = FALSE;
3553 game.snapshot.mode =
3554 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3555 SNAPSHOT_MODE_EVERY_STEP :
3556 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3557 SNAPSHOT_MODE_EVERY_MOVE :
3558 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3559 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3560 game.snapshot.save_snapshot = FALSE;
3562 // ---------- initialize level time for Supaplex engine ---------------------
3563 // Supaplex levels with time limit currently unsupported -- should be added
3564 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3567 // ---------- initialize flags for handling game actions --------------------
3569 // set flags for game actions to default values
3570 game.use_key_actions = TRUE;
3571 game.use_mouse_actions = FALSE;
3573 // when using Mirror Magic game engine, handle mouse events only
3574 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3576 game.use_key_actions = FALSE;
3577 game.use_mouse_actions = TRUE;
3580 // check for custom elements with mouse click events
3581 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3583 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3585 int element = EL_CUSTOM_START + i;
3587 if (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3588 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3589 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3590 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3591 game.use_mouse_actions = TRUE;
3596 static int get_num_special_action(int element, int action_first,
3599 int num_special_action = 0;
3602 for (i = action_first; i <= action_last; i++)
3604 boolean found = FALSE;
3606 for (j = 0; j < NUM_DIRECTIONS; j++)
3607 if (el_act_dir2img(element, i, j) !=
3608 el_act_dir2img(element, ACTION_DEFAULT, j))
3612 num_special_action++;
3617 return num_special_action;
3621 // ============================================================================
3623 // ----------------------------------------------------------------------------
3624 // initialize and start new game
3625 // ============================================================================
3627 #if DEBUG_INIT_PLAYER
3628 static void DebugPrintPlayerStatus(char *message)
3635 Debug("game:init:player", "%s:", message);
3637 for (i = 0; i < MAX_PLAYERS; i++)
3639 struct PlayerInfo *player = &stored_player[i];
3641 Debug("game:init:player",
3642 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3646 player->connected_locally,
3647 player->connected_network,
3649 (local_player == player ? " (local player)" : ""));
3656 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3657 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3658 int fade_mask = REDRAW_FIELD;
3659 boolean restarting = (game_status == GAME_MODE_PLAYING);
3660 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3661 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3662 int initial_move_dir = MV_DOWN;
3665 // required here to update video display before fading (FIX THIS)
3666 DrawMaskedBorder(REDRAW_DOOR_2);
3668 if (!game.restart_level)
3669 CloseDoor(DOOR_CLOSE_1);
3673 // force fading out global animations displayed during game play
3674 SetGameStatus(GAME_MODE_PSEUDO_RESTARTING);
3678 SetGameStatus(GAME_MODE_PLAYING);
3681 if (level_editor_test_game)
3682 FadeSkipNextFadeOut();
3684 FadeSetEnterScreen();
3687 fade_mask = REDRAW_ALL;
3689 FadeLevelSoundsAndMusic();
3691 ExpireSoundLoops(TRUE);
3697 // force restarting global animations displayed during game play
3698 RestartGlobalAnimsByStatus(GAME_MODE_PSEUDO_RESTARTING);
3700 // this is required for "transforming" fade modes like cross-fading
3701 // (else global animations will be stopped, but not restarted here)
3702 SetAnimStatusBeforeFading(GAME_MODE_PSEUDO_RESTARTING);
3704 SetGameStatus(GAME_MODE_PLAYING);
3707 if (level_editor_test_game)
3708 FadeSkipNextFadeIn();
3710 // needed if different viewport properties defined for playing
3711 ChangeViewportPropertiesIfNeeded();
3715 DrawCompleteVideoDisplay();
3717 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3720 InitGameControlValues();
3724 // initialize tape actions from game when recording tape
3725 tape.use_key_actions = game.use_key_actions;
3726 tape.use_mouse_actions = game.use_mouse_actions;
3728 // initialize visible playfield size when recording tape (for team mode)
3729 tape.scr_fieldx = SCR_FIELDX;
3730 tape.scr_fieldy = SCR_FIELDY;
3733 // don't play tapes over network
3734 network_playing = (network.enabled && !tape.playing);
3736 for (i = 0; i < MAX_PLAYERS; i++)
3738 struct PlayerInfo *player = &stored_player[i];
3740 player->index_nr = i;
3741 player->index_bit = (1 << i);
3742 player->element_nr = EL_PLAYER_1 + i;
3744 player->present = FALSE;
3745 player->active = FALSE;
3746 player->mapped = FALSE;
3748 player->killed = FALSE;
3749 player->reanimated = FALSE;
3750 player->buried = FALSE;
3753 player->effective_action = 0;
3754 player->programmed_action = 0;
3755 player->snap_action = 0;
3757 player->mouse_action.lx = 0;
3758 player->mouse_action.ly = 0;
3759 player->mouse_action.button = 0;
3760 player->mouse_action.button_hint = 0;
3762 player->effective_mouse_action.lx = 0;
3763 player->effective_mouse_action.ly = 0;
3764 player->effective_mouse_action.button = 0;
3765 player->effective_mouse_action.button_hint = 0;
3767 for (j = 0; j < MAX_NUM_KEYS; j++)
3768 player->key[j] = FALSE;
3770 player->num_white_keys = 0;
3772 player->dynabomb_count = 0;
3773 player->dynabomb_size = 1;
3774 player->dynabombs_left = 0;
3775 player->dynabomb_xl = FALSE;
3777 player->MovDir = initial_move_dir;
3780 player->GfxDir = initial_move_dir;
3781 player->GfxAction = ACTION_DEFAULT;
3783 player->StepFrame = 0;
3785 player->initial_element = player->element_nr;
3786 player->artwork_element =
3787 (level.use_artwork_element[i] ? level.artwork_element[i] :
3788 player->element_nr);
3789 player->use_murphy = FALSE;
3791 player->block_last_field = FALSE; // initialized in InitPlayerField()
3792 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3794 player->gravity = level.initial_player_gravity[i];
3796 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3798 player->actual_frame_counter.count = 0;
3799 player->actual_frame_counter.value = 1;
3801 player->step_counter = 0;
3803 player->last_move_dir = initial_move_dir;
3805 player->is_active = FALSE;
3807 player->is_waiting = FALSE;
3808 player->is_moving = FALSE;
3809 player->is_auto_moving = FALSE;
3810 player->is_digging = FALSE;
3811 player->is_snapping = FALSE;
3812 player->is_collecting = FALSE;
3813 player->is_pushing = FALSE;
3814 player->is_switching = FALSE;
3815 player->is_dropping = FALSE;
3816 player->is_dropping_pressed = FALSE;
3818 player->is_bored = FALSE;
3819 player->is_sleeping = FALSE;
3821 player->was_waiting = TRUE;
3822 player->was_moving = FALSE;
3823 player->was_snapping = FALSE;
3824 player->was_dropping = FALSE;
3826 player->force_dropping = FALSE;
3828 player->frame_counter_bored = -1;
3829 player->frame_counter_sleeping = -1;
3831 player->anim_delay_counter = 0;
3832 player->post_delay_counter = 0;
3834 player->dir_waiting = initial_move_dir;
3835 player->action_waiting = ACTION_DEFAULT;
3836 player->last_action_waiting = ACTION_DEFAULT;
3837 player->special_action_bored = ACTION_DEFAULT;
3838 player->special_action_sleeping = ACTION_DEFAULT;
3840 player->switch_x = -1;
3841 player->switch_y = -1;
3843 player->drop_x = -1;
3844 player->drop_y = -1;
3846 player->show_envelope = 0;
3848 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3850 player->push_delay = -1; // initialized when pushing starts
3851 player->push_delay_value = game.initial_push_delay_value;
3853 player->drop_delay = 0;
3854 player->drop_pressed_delay = 0;
3856 player->last_jx = -1;
3857 player->last_jy = -1;
3861 player->shield_normal_time_left = 0;
3862 player->shield_deadly_time_left = 0;
3864 player->last_removed_element = EL_UNDEFINED;
3866 player->inventory_infinite_element = EL_UNDEFINED;
3867 player->inventory_size = 0;
3869 if (level.use_initial_inventory[i])
3871 for (j = 0; j < level.initial_inventory_size[i]; j++)
3873 int element = level.initial_inventory_content[i][j];
3874 int collect_count = element_info[element].collect_count_initial;
3877 if (!IS_CUSTOM_ELEMENT(element))
3880 if (collect_count == 0)
3881 player->inventory_infinite_element = element;
3883 for (k = 0; k < collect_count; k++)
3884 if (player->inventory_size < MAX_INVENTORY_SIZE)
3885 player->inventory_element[player->inventory_size++] = element;
3889 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3890 SnapField(player, 0, 0);
3892 map_player_action[i] = i;
3895 network_player_action_received = FALSE;
3897 // initial null action
3898 if (network_playing)
3899 SendToServer_MovePlayer(MV_NONE);
3904 TimeLeft = level.time;
3909 ScreenMovDir = MV_NONE;
3913 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3915 game.robot_wheel_x = -1;
3916 game.robot_wheel_y = -1;
3921 game.all_players_gone = FALSE;
3923 game.LevelSolved = FALSE;
3924 game.GameOver = FALSE;
3926 game.GamePlayed = !tape.playing;
3928 game.LevelSolved_GameWon = FALSE;
3929 game.LevelSolved_GameEnd = FALSE;
3930 game.LevelSolved_SaveTape = FALSE;
3931 game.LevelSolved_SaveScore = FALSE;
3933 game.LevelSolved_CountingTime = 0;
3934 game.LevelSolved_CountingScore = 0;
3935 game.LevelSolved_CountingHealth = 0;
3937 game.RestartGameRequested = FALSE;
3939 game.panel.active = TRUE;
3941 game.no_level_time_limit = (level.time == 0);
3942 game.time_limit = (leveldir_current->time_limit && setup.time_limit);
3944 game.yamyam_content_nr = 0;
3945 game.robot_wheel_active = FALSE;
3946 game.magic_wall_active = FALSE;
3947 game.magic_wall_time_left = 0;
3948 game.light_time_left = 0;
3949 game.timegate_time_left = 0;
3950 game.switchgate_pos = 0;
3951 game.wind_direction = level.wind_direction_initial;
3953 game.time_final = 0;
3954 game.score_time_final = 0;
3957 game.score_final = 0;
3959 game.health = MAX_HEALTH;
3960 game.health_final = MAX_HEALTH;
3962 game.gems_still_needed = level.gems_needed;
3963 game.sokoban_fields_still_needed = 0;
3964 game.sokoban_objects_still_needed = 0;
3965 game.lights_still_needed = 0;
3966 game.players_still_needed = 0;
3967 game.friends_still_needed = 0;
3969 game.lenses_time_left = 0;
3970 game.magnify_time_left = 0;
3972 game.ball_active = level.ball_active_initial;
3973 game.ball_content_nr = 0;
3975 game.explosions_delayed = TRUE;
3977 game.envelope_active = FALSE;
3979 // special case: set custom artwork setting to initial value
3980 game.use_masked_elements = game.use_masked_elements_initial;
3982 for (i = 0; i < NUM_BELTS; i++)
3984 game.belt_dir[i] = MV_NONE;
3985 game.belt_dir_nr[i] = 3; // not moving, next moving left
3988 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3989 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3991 #if DEBUG_INIT_PLAYER
3992 DebugPrintPlayerStatus("Player status at level initialization");
3995 SCAN_PLAYFIELD(x, y)
3997 Tile[x][y] = Last[x][y] = level.field[x][y];
3998 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3999 ChangeDelay[x][y] = 0;
4000 ChangePage[x][y] = -1;
4001 CustomValue[x][y] = 0; // initialized in InitField()
4002 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
4004 WasJustMoving[x][y] = 0;
4005 WasJustFalling[x][y] = 0;
4006 CheckCollision[x][y] = 0;
4007 CheckImpact[x][y] = 0;
4009 Pushed[x][y] = FALSE;
4011 ChangeCount[x][y] = 0;
4012 ChangeEvent[x][y] = -1;
4014 ExplodePhase[x][y] = 0;
4015 ExplodeDelay[x][y] = 0;
4016 ExplodeField[x][y] = EX_TYPE_NONE;
4018 RunnerVisit[x][y] = 0;
4019 PlayerVisit[x][y] = 0;
4022 GfxRandom[x][y] = INIT_GFX_RANDOM();
4023 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
4024 GfxElement[x][y] = EL_UNDEFINED;
4025 GfxElementEmpty[x][y] = EL_EMPTY;
4026 GfxAction[x][y] = ACTION_DEFAULT;
4027 GfxDir[x][y] = MV_NONE;
4028 GfxRedraw[x][y] = GFX_REDRAW_NONE;
4031 SCAN_PLAYFIELD(x, y)
4033 InitFieldForEngine(x, y);
4035 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
4037 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
4040 InitField(x, y, TRUE);
4042 ResetGfxAnimation(x, y);
4047 // required if level does not contain any "empty space" element
4048 if (element_info[EL_EMPTY].use_gfx_element)
4049 game.use_masked_elements = TRUE;
4051 for (i = 0; i < MAX_PLAYERS; i++)
4053 struct PlayerInfo *player = &stored_player[i];
4055 // set number of special actions for bored and sleeping animation
4056 player->num_special_action_bored =
4057 get_num_special_action(player->artwork_element,
4058 ACTION_BORING_1, ACTION_BORING_LAST);
4059 player->num_special_action_sleeping =
4060 get_num_special_action(player->artwork_element,
4061 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
4064 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
4065 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
4067 // initialize type of slippery elements
4068 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4070 if (!IS_CUSTOM_ELEMENT(i))
4072 // default: elements slip down either to the left or right randomly
4073 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
4075 // SP style elements prefer to slip down on the left side
4076 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
4077 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4079 // BD style elements prefer to slip down on the left side
4080 if (game.emulation == EMU_BOULDERDASH)
4081 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4085 // initialize explosion and ignition delay
4086 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4088 if (!IS_CUSTOM_ELEMENT(i))
4091 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
4092 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
4093 game.emulation == EMU_SUPAPLEX ? 3 : 2);
4094 int last_phase = (num_phase + 1) * delay;
4095 int half_phase = (num_phase / 2) * delay;
4097 element_info[i].explosion_delay = last_phase - 1;
4098 element_info[i].ignition_delay = half_phase;
4100 if (i == EL_BLACK_ORB)
4101 element_info[i].ignition_delay = 1;
4105 // correct non-moving belts to start moving left
4106 for (i = 0; i < NUM_BELTS; i++)
4107 if (game.belt_dir[i] == MV_NONE)
4108 game.belt_dir_nr[i] = 3; // not moving, next moving left
4110 #if USE_NEW_PLAYER_ASSIGNMENTS
4111 // use preferred player also in local single-player mode
4112 if (!network.enabled && !game.team_mode)
4114 int new_index_nr = setup.network_player_nr;
4116 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
4118 for (i = 0; i < MAX_PLAYERS; i++)
4119 stored_player[i].connected_locally = FALSE;
4121 stored_player[new_index_nr].connected_locally = TRUE;
4125 for (i = 0; i < MAX_PLAYERS; i++)
4127 stored_player[i].connected = FALSE;
4129 // in network game mode, the local player might not be the first player
4130 if (stored_player[i].connected_locally)
4131 local_player = &stored_player[i];
4134 if (!network.enabled)
4135 local_player->connected = TRUE;
4139 for (i = 0; i < MAX_PLAYERS; i++)
4140 stored_player[i].connected = tape.player_participates[i];
4142 else if (network.enabled)
4144 // add team mode players connected over the network (needed for correct
4145 // assignment of player figures from level to locally playing players)
4147 for (i = 0; i < MAX_PLAYERS; i++)
4148 if (stored_player[i].connected_network)
4149 stored_player[i].connected = TRUE;
4151 else if (game.team_mode)
4153 // try to guess locally connected team mode players (needed for correct
4154 // assignment of player figures from level to locally playing players)
4156 for (i = 0; i < MAX_PLAYERS; i++)
4157 if (setup.input[i].use_joystick ||
4158 setup.input[i].key.left != KSYM_UNDEFINED)
4159 stored_player[i].connected = TRUE;
4162 #if DEBUG_INIT_PLAYER
4163 DebugPrintPlayerStatus("Player status after level initialization");
4166 #if DEBUG_INIT_PLAYER
4167 Debug("game:init:player", "Reassigning players ...");
4170 // check if any connected player was not found in playfield
4171 for (i = 0; i < MAX_PLAYERS; i++)
4173 struct PlayerInfo *player = &stored_player[i];
4175 if (player->connected && !player->present)
4177 struct PlayerInfo *field_player = NULL;
4179 #if DEBUG_INIT_PLAYER
4180 Debug("game:init:player",
4181 "- looking for field player for player %d ...", i + 1);
4184 // assign first free player found that is present in the playfield
4186 // first try: look for unmapped playfield player that is not connected
4187 for (j = 0; j < MAX_PLAYERS; j++)
4188 if (field_player == NULL &&
4189 stored_player[j].present &&
4190 !stored_player[j].mapped &&
4191 !stored_player[j].connected)
4192 field_player = &stored_player[j];
4194 // second try: look for *any* unmapped playfield player
4195 for (j = 0; j < MAX_PLAYERS; j++)
4196 if (field_player == NULL &&
4197 stored_player[j].present &&
4198 !stored_player[j].mapped)
4199 field_player = &stored_player[j];
4201 if (field_player != NULL)
4203 int jx = field_player->jx, jy = field_player->jy;
4205 #if DEBUG_INIT_PLAYER
4206 Debug("game:init:player", "- found player %d",
4207 field_player->index_nr + 1);
4210 player->present = FALSE;
4211 player->active = FALSE;
4213 field_player->present = TRUE;
4214 field_player->active = TRUE;
4217 player->initial_element = field_player->initial_element;
4218 player->artwork_element = field_player->artwork_element;
4220 player->block_last_field = field_player->block_last_field;
4221 player->block_delay_adjustment = field_player->block_delay_adjustment;
4224 StorePlayer[jx][jy] = field_player->element_nr;
4226 field_player->jx = field_player->last_jx = jx;
4227 field_player->jy = field_player->last_jy = jy;
4229 if (local_player == player)
4230 local_player = field_player;
4232 map_player_action[field_player->index_nr] = i;
4234 field_player->mapped = TRUE;
4236 #if DEBUG_INIT_PLAYER
4237 Debug("game:init:player", "- map_player_action[%d] == %d",
4238 field_player->index_nr + 1, i + 1);
4243 if (player->connected && player->present)
4244 player->mapped = TRUE;
4247 #if DEBUG_INIT_PLAYER
4248 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4253 // check if any connected player was not found in playfield
4254 for (i = 0; i < MAX_PLAYERS; i++)
4256 struct PlayerInfo *player = &stored_player[i];
4258 if (player->connected && !player->present)
4260 for (j = 0; j < MAX_PLAYERS; j++)
4262 struct PlayerInfo *field_player = &stored_player[j];
4263 int jx = field_player->jx, jy = field_player->jy;
4265 // assign first free player found that is present in the playfield
4266 if (field_player->present && !field_player->connected)
4268 player->present = TRUE;
4269 player->active = TRUE;
4271 field_player->present = FALSE;
4272 field_player->active = FALSE;
4274 player->initial_element = field_player->initial_element;
4275 player->artwork_element = field_player->artwork_element;
4277 player->block_last_field = field_player->block_last_field;
4278 player->block_delay_adjustment = field_player->block_delay_adjustment;
4280 StorePlayer[jx][jy] = player->element_nr;
4282 player->jx = player->last_jx = jx;
4283 player->jy = player->last_jy = jy;
4293 Debug("game:init:player", "local_player->present == %d",
4294 local_player->present);
4297 // set focus to local player for network games, else to all players
4298 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4299 game.centered_player_nr_next = game.centered_player_nr;
4300 game.set_centered_player = FALSE;
4301 game.set_centered_player_wrap = FALSE;
4303 if (network_playing && tape.recording)
4305 // store client dependent player focus when recording network games
4306 tape.centered_player_nr_next = game.centered_player_nr_next;
4307 tape.set_centered_player = TRUE;
4312 // when playing a tape, eliminate all players who do not participate
4314 #if USE_NEW_PLAYER_ASSIGNMENTS
4316 if (!game.team_mode)
4318 for (i = 0; i < MAX_PLAYERS; i++)
4320 if (stored_player[i].active &&
4321 !tape.player_participates[map_player_action[i]])
4323 struct PlayerInfo *player = &stored_player[i];
4324 int jx = player->jx, jy = player->jy;
4326 #if DEBUG_INIT_PLAYER
4327 Debug("game:init:player", "Removing player %d at (%d, %d)",
4331 player->active = FALSE;
4332 StorePlayer[jx][jy] = 0;
4333 Tile[jx][jy] = EL_EMPTY;
4340 for (i = 0; i < MAX_PLAYERS; i++)
4342 if (stored_player[i].active &&
4343 !tape.player_participates[i])
4345 struct PlayerInfo *player = &stored_player[i];
4346 int jx = player->jx, jy = player->jy;
4348 player->active = FALSE;
4349 StorePlayer[jx][jy] = 0;
4350 Tile[jx][jy] = EL_EMPTY;
4355 else if (!network.enabled && !game.team_mode) // && !tape.playing
4357 // when in single player mode, eliminate all but the local player
4359 for (i = 0; i < MAX_PLAYERS; i++)
4361 struct PlayerInfo *player = &stored_player[i];
4363 if (player->active && player != local_player)
4365 int jx = player->jx, jy = player->jy;
4367 player->active = FALSE;
4368 player->present = FALSE;
4370 StorePlayer[jx][jy] = 0;
4371 Tile[jx][jy] = EL_EMPTY;
4376 for (i = 0; i < MAX_PLAYERS; i++)
4377 if (stored_player[i].active)
4378 game.players_still_needed++;
4380 if (level.solved_by_one_player)
4381 game.players_still_needed = 1;
4383 // when recording the game, store which players take part in the game
4386 #if USE_NEW_PLAYER_ASSIGNMENTS
4387 for (i = 0; i < MAX_PLAYERS; i++)
4388 if (stored_player[i].connected)
4389 tape.player_participates[i] = TRUE;
4391 for (i = 0; i < MAX_PLAYERS; i++)
4392 if (stored_player[i].active)
4393 tape.player_participates[i] = TRUE;
4397 #if DEBUG_INIT_PLAYER
4398 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4401 if (BorderElement == EL_EMPTY)
4404 SBX_Right = lev_fieldx - SCR_FIELDX;
4406 SBY_Lower = lev_fieldy - SCR_FIELDY;
4411 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4413 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4416 if (full_lev_fieldx <= SCR_FIELDX)
4417 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4418 if (full_lev_fieldy <= SCR_FIELDY)
4419 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4421 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4423 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4426 // if local player not found, look for custom element that might create
4427 // the player (make some assumptions about the right custom element)
4428 if (!local_player->present)
4430 int start_x = 0, start_y = 0;
4431 int found_rating = 0;
4432 int found_element = EL_UNDEFINED;
4433 int player_nr = local_player->index_nr;
4435 SCAN_PLAYFIELD(x, y)
4437 int element = Tile[x][y];
4442 if (level.use_start_element[player_nr] &&
4443 level.start_element[player_nr] == element &&
4450 found_element = element;
4453 if (!IS_CUSTOM_ELEMENT(element))
4456 if (CAN_CHANGE(element))
4458 for (i = 0; i < element_info[element].num_change_pages; i++)
4460 // check for player created from custom element as single target
4461 content = element_info[element].change_page[i].target_element;
4462 is_player = IS_PLAYER_ELEMENT(content);
4464 if (is_player && (found_rating < 3 ||
4465 (found_rating == 3 && element < found_element)))
4471 found_element = element;
4476 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4478 // check for player created from custom element as explosion content
4479 content = element_info[element].content.e[xx][yy];
4480 is_player = IS_PLAYER_ELEMENT(content);
4482 if (is_player && (found_rating < 2 ||
4483 (found_rating == 2 && element < found_element)))
4485 start_x = x + xx - 1;
4486 start_y = y + yy - 1;
4489 found_element = element;
4492 if (!CAN_CHANGE(element))
4495 for (i = 0; i < element_info[element].num_change_pages; i++)
4497 // check for player created from custom element as extended target
4499 element_info[element].change_page[i].target_content.e[xx][yy];
4501 is_player = IS_PLAYER_ELEMENT(content);
4503 if (is_player && (found_rating < 1 ||
4504 (found_rating == 1 && element < found_element)))
4506 start_x = x + xx - 1;
4507 start_y = y + yy - 1;
4510 found_element = element;
4516 scroll_x = SCROLL_POSITION_X(start_x);
4517 scroll_y = SCROLL_POSITION_Y(start_y);
4521 scroll_x = SCROLL_POSITION_X(local_player->jx);
4522 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4525 if (game.forced_scroll_x != ARG_UNDEFINED_VALUE)
4526 scroll_x = game.forced_scroll_x;
4527 if (game.forced_scroll_y != ARG_UNDEFINED_VALUE)
4528 scroll_y = game.forced_scroll_y;
4530 // !!! FIX THIS (START) !!!
4531 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
4533 InitGameEngine_BD();
4535 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4537 InitGameEngine_EM();
4539 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4541 InitGameEngine_SP();
4543 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4545 InitGameEngine_MM();
4549 DrawLevel(REDRAW_FIELD);
4552 // after drawing the level, correct some elements
4553 if (game.timegate_time_left == 0)
4554 CloseAllOpenTimegates();
4557 // blit playfield from scroll buffer to normal back buffer for fading in
4558 BlitScreenToBitmap(backbuffer);
4559 // !!! FIX THIS (END) !!!
4561 DrawMaskedBorder(fade_mask);
4566 // full screen redraw is required at this point in the following cases:
4567 // - special editor door undrawn when game was started from level editor
4568 // - drawing area (playfield) was changed and has to be removed completely
4569 redraw_mask = REDRAW_ALL;
4573 if (!game.restart_level)
4575 // copy default game door content to main double buffer
4577 // !!! CHECK AGAIN !!!
4578 SetPanelBackground();
4579 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4580 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4583 SetPanelBackground();
4584 SetDrawBackgroundMask(REDRAW_DOOR_1);
4586 UpdateAndDisplayGameControlValues();
4588 if (!game.restart_level)
4594 CreateGameButtons();
4599 // copy actual game door content to door double buffer for OpenDoor()
4600 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4602 OpenDoor(DOOR_OPEN_ALL);
4604 KeyboardAutoRepeatOffUnlessAutoplay();
4606 #if DEBUG_INIT_PLAYER
4607 DebugPrintPlayerStatus("Player status (final)");
4616 if (!game.restart_level && !tape.playing)
4618 LevelStats_incPlayed(level_nr);
4620 SaveLevelSetup_SeriesInfo();
4623 game.restart_level = FALSE;
4624 game.request_active = FALSE;
4626 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4627 InitGameActions_MM();
4629 SaveEngineSnapshotToListInitial();
4631 if (!game.restart_level)
4633 PlaySound(SND_GAME_STARTING);
4635 if (setup.sound_music)
4639 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4642 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4643 int actual_player_x, int actual_player_y)
4645 // this is used for non-R'n'D game engines to update certain engine values
4647 // needed to determine if sounds are played within the visible screen area
4648 scroll_x = actual_scroll_x;
4649 scroll_y = actual_scroll_y;
4651 // needed to get player position for "follow finger" playing input method
4652 local_player->jx = actual_player_x;
4653 local_player->jy = actual_player_y;
4656 void InitMovDir(int x, int y)
4658 int i, element = Tile[x][y];
4659 static int xy[4][2] =
4666 static int direction[3][4] =
4668 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4669 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4670 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4679 Tile[x][y] = EL_BUG;
4680 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4683 case EL_SPACESHIP_RIGHT:
4684 case EL_SPACESHIP_UP:
4685 case EL_SPACESHIP_LEFT:
4686 case EL_SPACESHIP_DOWN:
4687 Tile[x][y] = EL_SPACESHIP;
4688 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4691 case EL_BD_BUTTERFLY_RIGHT:
4692 case EL_BD_BUTTERFLY_UP:
4693 case EL_BD_BUTTERFLY_LEFT:
4694 case EL_BD_BUTTERFLY_DOWN:
4695 Tile[x][y] = EL_BD_BUTTERFLY;
4696 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4699 case EL_BD_FIREFLY_RIGHT:
4700 case EL_BD_FIREFLY_UP:
4701 case EL_BD_FIREFLY_LEFT:
4702 case EL_BD_FIREFLY_DOWN:
4703 Tile[x][y] = EL_BD_FIREFLY;
4704 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4707 case EL_PACMAN_RIGHT:
4709 case EL_PACMAN_LEFT:
4710 case EL_PACMAN_DOWN:
4711 Tile[x][y] = EL_PACMAN;
4712 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4715 case EL_YAMYAM_LEFT:
4716 case EL_YAMYAM_RIGHT:
4718 case EL_YAMYAM_DOWN:
4719 Tile[x][y] = EL_YAMYAM;
4720 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4723 case EL_SP_SNIKSNAK:
4724 MovDir[x][y] = MV_UP;
4727 case EL_SP_ELECTRON:
4728 MovDir[x][y] = MV_LEFT;
4735 Tile[x][y] = EL_MOLE;
4736 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4739 case EL_SPRING_LEFT:
4740 case EL_SPRING_RIGHT:
4741 Tile[x][y] = EL_SPRING;
4742 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4746 if (IS_CUSTOM_ELEMENT(element))
4748 struct ElementInfo *ei = &element_info[element];
4749 int move_direction_initial = ei->move_direction_initial;
4750 int move_pattern = ei->move_pattern;
4752 if (move_direction_initial == MV_START_PREVIOUS)
4754 if (MovDir[x][y] != MV_NONE)
4757 move_direction_initial = MV_START_AUTOMATIC;
4760 if (move_direction_initial == MV_START_RANDOM)
4761 MovDir[x][y] = 1 << RND(4);
4762 else if (move_direction_initial & MV_ANY_DIRECTION)
4763 MovDir[x][y] = move_direction_initial;
4764 else if (move_pattern == MV_ALL_DIRECTIONS ||
4765 move_pattern == MV_TURNING_LEFT ||
4766 move_pattern == MV_TURNING_RIGHT ||
4767 move_pattern == MV_TURNING_LEFT_RIGHT ||
4768 move_pattern == MV_TURNING_RIGHT_LEFT ||
4769 move_pattern == MV_TURNING_RANDOM)
4770 MovDir[x][y] = 1 << RND(4);
4771 else if (move_pattern == MV_HORIZONTAL)
4772 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4773 else if (move_pattern == MV_VERTICAL)
4774 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4775 else if (move_pattern & MV_ANY_DIRECTION)
4776 MovDir[x][y] = element_info[element].move_pattern;
4777 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4778 move_pattern == MV_ALONG_RIGHT_SIDE)
4780 // use random direction as default start direction
4781 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4782 MovDir[x][y] = 1 << RND(4);
4784 for (i = 0; i < NUM_DIRECTIONS; i++)
4786 int x1 = x + xy[i][0];
4787 int y1 = y + xy[i][1];
4789 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4791 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4792 MovDir[x][y] = direction[0][i];
4794 MovDir[x][y] = direction[1][i];
4803 MovDir[x][y] = 1 << RND(4);
4805 if (element != EL_BUG &&
4806 element != EL_SPACESHIP &&
4807 element != EL_BD_BUTTERFLY &&
4808 element != EL_BD_FIREFLY)
4811 for (i = 0; i < NUM_DIRECTIONS; i++)
4813 int x1 = x + xy[i][0];
4814 int y1 = y + xy[i][1];
4816 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4818 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4820 MovDir[x][y] = direction[0][i];
4823 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4824 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4826 MovDir[x][y] = direction[1][i];
4835 GfxDir[x][y] = MovDir[x][y];
4838 void InitAmoebaNr(int x, int y)
4841 int group_nr = AmoebaNeighbourNr(x, y);
4845 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4847 if (AmoebaCnt[i] == 0)
4855 AmoebaNr[x][y] = group_nr;
4856 AmoebaCnt[group_nr]++;
4857 AmoebaCnt2[group_nr]++;
4860 static void LevelSolved_SetFinalGameValues(void)
4862 game.time_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.time_played :
4863 game.no_level_time_limit ? TimePlayed : TimeLeft);
4864 game.score_time_final = (level.use_step_counter ? TimePlayed :
4865 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4867 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.score :
4868 level.game_engine_type == GAME_ENGINE_TYPE_EM ? game_em.lev->score :
4869 level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.score :
4872 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4873 MM_HEALTH(game_mm.laser_overload_value) :
4876 game.LevelSolved_CountingTime = game.time_final;
4877 game.LevelSolved_CountingScore = game.score_final;
4878 game.LevelSolved_CountingHealth = game.health_final;
4881 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4883 game.LevelSolved_CountingTime = time;
4884 game.LevelSolved_CountingScore = score;
4885 game.LevelSolved_CountingHealth = health;
4887 game_panel_controls[GAME_PANEL_TIME].value = time;
4888 game_panel_controls[GAME_PANEL_SCORE].value = score;
4889 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4891 DisplayGameControlValues();
4894 static void LevelSolved(void)
4896 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4897 game.players_still_needed > 0)
4900 game.LevelSolved = TRUE;
4901 game.GameOver = TRUE;
4905 // needed here to display correct panel values while player walks into exit
4906 LevelSolved_SetFinalGameValues();
4909 static void AdvanceToNextLevel(void)
4911 if (setup.increment_levels &&
4912 level_nr < leveldir_current->last_level &&
4915 level_nr++; // advance to next level
4916 TapeErase(); // start with empty tape
4918 if (setup.auto_play_next_level)
4920 scores.continue_playing = TRUE;
4921 scores.next_level_nr = level_nr;
4923 LoadLevel(level_nr);
4925 SaveLevelSetup_SeriesInfo();
4932 static int time_count_steps;
4933 static int time, time_final;
4934 static float score, score_final; // needed for time score < 10 for 10 seconds
4935 static int health, health_final;
4936 static int game_over_delay_1 = 0;
4937 static int game_over_delay_2 = 0;
4938 static int game_over_delay_3 = 0;
4939 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4940 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4942 if (!game.LevelSolved_GameWon)
4946 // do not start end game actions before the player stops moving (to exit)
4947 if (local_player->active && local_player->MovPos)
4950 // calculate final game values after player finished walking into exit
4951 LevelSolved_SetFinalGameValues();
4953 game.LevelSolved_GameWon = TRUE;
4954 game.LevelSolved_SaveTape = tape.recording;
4955 game.LevelSolved_SaveScore = !tape.playing;
4959 LevelStats_incSolved(level_nr);
4961 SaveLevelSetup_SeriesInfo();
4964 if (tape.auto_play) // tape might already be stopped here
4965 tape.auto_play_level_solved = TRUE;
4969 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4970 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4971 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4973 time = time_final = game.time_final;
4974 score = score_final = game.score_final;
4975 health = health_final = game.health_final;
4977 // update game panel values before (delayed) counting of score (if any)
4978 LevelSolved_DisplayFinalGameValues(time, score, health);
4980 // if level has time score defined, calculate new final game values
4983 int time_final_max = 999;
4984 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4985 int time_frames = 0;
4986 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4987 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4992 time_frames = time_frames_left;
4994 else if (game.no_level_time_limit && TimePlayed < time_final_max)
4996 time_final = time_final_max;
4997 time_frames = time_frames_final_max - time_frames_played;
5000 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
5002 time_count_steps = MAX(1, ABS(time_final - time) / 100);
5004 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
5006 // keep previous values (final values already processed here)
5008 score_final = score;
5010 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
5013 score_final += health * time_score;
5016 game.score_final = score_final;
5017 game.health_final = health_final;
5020 // if not counting score after game, immediately update game panel values
5021 if (level_editor_test_game || !setup.count_score_after_game ||
5022 level.game_engine_type == GAME_ENGINE_TYPE_BD)
5025 score = score_final;
5027 LevelSolved_DisplayFinalGameValues(time, score, health);
5030 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
5032 // check if last player has left the level
5033 if (game.exit_x >= 0 &&
5036 int x = game.exit_x;
5037 int y = game.exit_y;
5038 int element = Tile[x][y];
5040 // close exit door after last player
5041 if ((game.all_players_gone &&
5042 (element == EL_EXIT_OPEN ||
5043 element == EL_SP_EXIT_OPEN ||
5044 element == EL_STEEL_EXIT_OPEN)) ||
5045 element == EL_EM_EXIT_OPEN ||
5046 element == EL_EM_STEEL_EXIT_OPEN)
5050 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
5051 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
5052 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
5053 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
5054 EL_EM_STEEL_EXIT_CLOSING);
5056 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
5059 // player disappears
5060 DrawLevelField(x, y);
5063 for (i = 0; i < MAX_PLAYERS; i++)
5065 struct PlayerInfo *player = &stored_player[i];
5067 if (player->present)
5069 RemovePlayer(player);
5071 // player disappears
5072 DrawLevelField(player->jx, player->jy);
5077 PlaySound(SND_GAME_WINNING);
5080 if (setup.count_score_after_game)
5082 if (time != time_final)
5084 if (game_over_delay_1 > 0)
5086 game_over_delay_1--;
5091 int time_to_go = ABS(time_final - time);
5092 int time_count_dir = (time < time_final ? +1 : -1);
5094 if (time_to_go < time_count_steps)
5095 time_count_steps = 1;
5097 time += time_count_steps * time_count_dir;
5098 score += time_count_steps * time_score;
5100 // set final score to correct rounding differences after counting score
5101 if (time == time_final)
5102 score = score_final;
5104 LevelSolved_DisplayFinalGameValues(time, score, health);
5106 if (time == time_final)
5107 StopSound(SND_GAME_LEVELTIME_BONUS);
5108 else if (setup.sound_loops)
5109 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5111 PlaySound(SND_GAME_LEVELTIME_BONUS);
5116 if (health != health_final)
5118 if (game_over_delay_2 > 0)
5120 game_over_delay_2--;
5125 int health_count_dir = (health < health_final ? +1 : -1);
5127 health += health_count_dir;
5128 score += time_score;
5130 LevelSolved_DisplayFinalGameValues(time, score, health);
5132 if (health == health_final)
5133 StopSound(SND_GAME_LEVELTIME_BONUS);
5134 else if (setup.sound_loops)
5135 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5137 PlaySound(SND_GAME_LEVELTIME_BONUS);
5143 game.panel.active = FALSE;
5145 if (game_over_delay_3 > 0)
5147 game_over_delay_3--;
5157 // used instead of "level_nr" (needed for network games)
5158 int last_level_nr = levelset.level_nr;
5159 boolean tape_saved = FALSE;
5161 game.LevelSolved_GameEnd = TRUE;
5163 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5165 // make sure that request dialog to save tape does not open door again
5166 if (!global.use_envelope_request)
5167 CloseDoor(DOOR_CLOSE_1);
5170 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5172 // set unique basename for score tape (also saved in high score table)
5173 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5176 // if no tape is to be saved, close both doors simultaneously
5177 CloseDoor(DOOR_CLOSE_ALL);
5179 if (level_editor_test_game || score_info_tape_play)
5181 SetGameStatus(GAME_MODE_MAIN);
5188 if (!game.LevelSolved_SaveScore)
5190 SetGameStatus(GAME_MODE_MAIN);
5197 if (level_nr == leveldir_current->handicap_level)
5199 leveldir_current->handicap_level++;
5201 SaveLevelSetup_SeriesInfo();
5204 // save score and score tape before potentially erasing tape below
5205 NewHighScore(last_level_nr, tape_saved);
5207 // increment and load next level (if possible and not configured otherwise)
5208 AdvanceToNextLevel();
5210 if (scores.last_added >= 0 && setup.show_scores_after_game)
5212 SetGameStatus(GAME_MODE_SCORES);
5214 DrawHallOfFame(last_level_nr);
5216 else if (scores.continue_playing)
5218 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5222 SetGameStatus(GAME_MODE_MAIN);
5228 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5229 boolean one_score_entry_per_name)
5233 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5236 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5238 struct ScoreEntry *entry = &list->entry[i];
5239 boolean score_is_better = (new_entry->score > entry->score);
5240 boolean score_is_equal = (new_entry->score == entry->score);
5241 boolean time_is_better = (new_entry->time < entry->time);
5242 boolean time_is_equal = (new_entry->time == entry->time);
5243 boolean better_by_score = (score_is_better ||
5244 (score_is_equal && time_is_better));
5245 boolean better_by_time = (time_is_better ||
5246 (time_is_equal && score_is_better));
5247 boolean is_better = (level.rate_time_over_score ? better_by_time :
5249 boolean entry_is_empty = (entry->score == 0 &&
5252 // prevent adding server score entries if also existing in local score file
5253 // (special case: historic score entries have an empty tape basename entry)
5254 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5255 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5257 // add fields from server score entry not stored in local score entry
5258 // (currently, this means setting platform, version and country fields;
5259 // in rare cases, this may also correct an invalid score value, as
5260 // historic scores might have been truncated to 16-bit values locally)
5261 *entry = *new_entry;
5266 if (is_better || entry_is_empty)
5268 // player has made it to the hall of fame
5270 if (i < MAX_SCORE_ENTRIES - 1)
5272 int m = MAX_SCORE_ENTRIES - 1;
5275 if (one_score_entry_per_name)
5277 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5278 if (strEqual(list->entry[l].name, new_entry->name))
5281 if (m == i) // player's new highscore overwrites his old one
5285 for (l = m; l > i; l--)
5286 list->entry[l] = list->entry[l - 1];
5291 *entry = *new_entry;
5295 else if (one_score_entry_per_name &&
5296 strEqual(entry->name, new_entry->name))
5298 // player already in high score list with better score or time
5304 // special case: new score is beyond the last high score list position
5305 return MAX_SCORE_ENTRIES;
5308 void NewHighScore(int level_nr, boolean tape_saved)
5310 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5311 boolean one_per_name = FALSE;
5313 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5314 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5316 new_entry.score = game.score_final;
5317 new_entry.time = game.score_time_final;
5319 LoadScore(level_nr);
5321 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5323 if (scores.last_added >= MAX_SCORE_ENTRIES)
5325 scores.last_added = MAX_SCORE_ENTRIES - 1;
5326 scores.force_last_added = TRUE;
5328 scores.entry[scores.last_added] = new_entry;
5330 // store last added local score entry (before merging server scores)
5331 scores.last_added_local = scores.last_added;
5336 if (scores.last_added < 0)
5339 SaveScore(level_nr);
5341 // store last added local score entry (before merging server scores)
5342 scores.last_added_local = scores.last_added;
5344 if (!game.LevelSolved_SaveTape)
5347 SaveScoreTape(level_nr);
5349 if (setup.ask_for_using_api_server)
5351 setup.use_api_server =
5352 Request("Upload your score and tape to the high score server?", REQ_ASK);
5354 if (!setup.use_api_server)
5355 Request("Not using high score server! Use setup menu to enable again!",
5358 runtime.use_api_server = setup.use_api_server;
5360 // after asking for using API server once, do not ask again
5361 setup.ask_for_using_api_server = FALSE;
5363 SaveSetup_ServerSetup();
5366 SaveServerScore(level_nr, tape_saved);
5369 void MergeServerScore(void)
5371 struct ScoreEntry last_added_entry;
5372 boolean one_per_name = FALSE;
5375 if (scores.last_added >= 0)
5376 last_added_entry = scores.entry[scores.last_added];
5378 for (i = 0; i < server_scores.num_entries; i++)
5380 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5382 if (pos >= 0 && pos <= scores.last_added)
5383 scores.last_added++;
5386 if (scores.last_added >= MAX_SCORE_ENTRIES)
5388 scores.last_added = MAX_SCORE_ENTRIES - 1;
5389 scores.force_last_added = TRUE;
5391 scores.entry[scores.last_added] = last_added_entry;
5395 static int getElementMoveStepsizeExt(int x, int y, int direction)
5397 int element = Tile[x][y];
5398 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5399 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5400 int horiz_move = (dx != 0);
5401 int sign = (horiz_move ? dx : dy);
5402 int step = sign * element_info[element].move_stepsize;
5404 // special values for move stepsize for spring and things on conveyor belt
5407 if (CAN_FALL(element) &&
5408 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5409 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5410 else if (element == EL_SPRING)
5411 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5417 static int getElementMoveStepsize(int x, int y)
5419 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5422 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5424 if (player->GfxAction != action || player->GfxDir != dir)
5426 player->GfxAction = action;
5427 player->GfxDir = dir;
5429 player->StepFrame = 0;
5433 static void ResetGfxFrame(int x, int y)
5435 // profiling showed that "autotest" spends 10~20% of its time in this function
5436 if (DrawingDeactivatedField())
5439 int element = Tile[x][y];
5440 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5442 if (graphic_info[graphic].anim_global_sync)
5443 GfxFrame[x][y] = FrameCounter;
5444 else if (graphic_info[graphic].anim_global_anim_sync)
5445 GfxFrame[x][y] = getGlobalAnimSyncFrame();
5446 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5447 GfxFrame[x][y] = CustomValue[x][y];
5448 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5449 GfxFrame[x][y] = element_info[element].collect_score;
5450 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5451 GfxFrame[x][y] = ChangeDelay[x][y];
5454 static void ResetGfxAnimation(int x, int y)
5456 GfxAction[x][y] = ACTION_DEFAULT;
5457 GfxDir[x][y] = MovDir[x][y];
5460 ResetGfxFrame(x, y);
5463 static void ResetRandomAnimationValue(int x, int y)
5465 GfxRandom[x][y] = INIT_GFX_RANDOM();
5468 static void InitMovingField(int x, int y, int direction)
5470 int element = Tile[x][y];
5471 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5472 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5475 boolean is_moving_before, is_moving_after;
5477 // check if element was/is moving or being moved before/after mode change
5478 is_moving_before = (WasJustMoving[x][y] != 0);
5479 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5481 // reset animation only for moving elements which change direction of moving
5482 // or which just started or stopped moving
5483 // (else CEs with property "can move" / "not moving" are reset each frame)
5484 if (is_moving_before != is_moving_after ||
5485 direction != MovDir[x][y])
5486 ResetGfxAnimation(x, y);
5488 MovDir[x][y] = direction;
5489 GfxDir[x][y] = direction;
5491 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5492 direction == MV_DOWN && CAN_FALL(element) ?
5493 ACTION_FALLING : ACTION_MOVING);
5495 // this is needed for CEs with property "can move" / "not moving"
5497 if (is_moving_after)
5499 if (Tile[newx][newy] == EL_EMPTY)
5500 Tile[newx][newy] = EL_BLOCKED;
5502 MovDir[newx][newy] = MovDir[x][y];
5504 CustomValue[newx][newy] = CustomValue[x][y];
5506 GfxFrame[newx][newy] = GfxFrame[x][y];
5507 GfxRandom[newx][newy] = GfxRandom[x][y];
5508 GfxAction[newx][newy] = GfxAction[x][y];
5509 GfxDir[newx][newy] = GfxDir[x][y];
5513 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5515 int direction = MovDir[x][y];
5516 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5517 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5523 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5525 int direction = MovDir[x][y];
5526 int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0);
5527 int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0);
5529 *comes_from_x = oldx;
5530 *comes_from_y = oldy;
5533 static int MovingOrBlocked2Element(int x, int y)
5535 int element = Tile[x][y];
5537 if (element == EL_BLOCKED)
5541 Blocked2Moving(x, y, &oldx, &oldy);
5543 return Tile[oldx][oldy];
5549 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5551 // like MovingOrBlocked2Element(), but if element is moving
5552 // and (x, y) is the field the moving element is just leaving,
5553 // return EL_BLOCKED instead of the element value
5554 int element = Tile[x][y];
5556 if (IS_MOVING(x, y))
5558 if (element == EL_BLOCKED)
5562 Blocked2Moving(x, y, &oldx, &oldy);
5563 return Tile[oldx][oldy];
5572 static void RemoveField(int x, int y)
5574 Tile[x][y] = EL_EMPTY;
5580 CustomValue[x][y] = 0;
5583 ChangeDelay[x][y] = 0;
5584 ChangePage[x][y] = -1;
5585 Pushed[x][y] = FALSE;
5587 GfxElement[x][y] = EL_UNDEFINED;
5588 GfxAction[x][y] = ACTION_DEFAULT;
5589 GfxDir[x][y] = MV_NONE;
5592 static void RemoveMovingField(int x, int y)
5594 int oldx = x, oldy = y, newx = x, newy = y;
5595 int element = Tile[x][y];
5596 int next_element = EL_UNDEFINED;
5598 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5601 if (IS_MOVING(x, y))
5603 Moving2Blocked(x, y, &newx, &newy);
5605 if (Tile[newx][newy] != EL_BLOCKED)
5607 // element is moving, but target field is not free (blocked), but
5608 // already occupied by something different (example: acid pool);
5609 // in this case, only remove the moving field, but not the target
5611 RemoveField(oldx, oldy);
5613 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5615 TEST_DrawLevelField(oldx, oldy);
5620 else if (element == EL_BLOCKED)
5622 Blocked2Moving(x, y, &oldx, &oldy);
5623 if (!IS_MOVING(oldx, oldy))
5627 if (element == EL_BLOCKED &&
5628 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5629 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5630 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5631 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5632 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5633 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5634 next_element = get_next_element(Tile[oldx][oldy]);
5636 RemoveField(oldx, oldy);
5637 RemoveField(newx, newy);
5639 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5641 if (next_element != EL_UNDEFINED)
5642 Tile[oldx][oldy] = next_element;
5644 TEST_DrawLevelField(oldx, oldy);
5645 TEST_DrawLevelField(newx, newy);
5648 void DrawDynamite(int x, int y)
5650 int sx = SCREENX(x), sy = SCREENY(y);
5651 int graphic = el2img(Tile[x][y]);
5654 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5657 if (IS_WALKABLE_INSIDE(Back[x][y]))
5661 DrawLevelElement(x, y, Back[x][y]);
5662 else if (Store[x][y])
5663 DrawLevelElement(x, y, Store[x][y]);
5664 else if (game.use_masked_elements)
5665 DrawLevelElement(x, y, EL_EMPTY);
5667 frame = getGraphicAnimationFrameXY(graphic, x, y);
5669 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5670 DrawGraphicThruMask(sx, sy, graphic, frame);
5672 DrawGraphic(sx, sy, graphic, frame);
5675 static void CheckDynamite(int x, int y)
5677 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5681 if (MovDelay[x][y] != 0)
5684 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5690 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5695 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5697 boolean num_checked_players = 0;
5700 for (i = 0; i < MAX_PLAYERS; i++)
5702 if (stored_player[i].active)
5704 int sx = stored_player[i].jx;
5705 int sy = stored_player[i].jy;
5707 if (num_checked_players == 0)
5714 *sx1 = MIN(*sx1, sx);
5715 *sy1 = MIN(*sy1, sy);
5716 *sx2 = MAX(*sx2, sx);
5717 *sy2 = MAX(*sy2, sy);
5720 num_checked_players++;
5725 static boolean checkIfAllPlayersFitToScreen_RND(void)
5727 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5729 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5731 return (sx2 - sx1 < SCR_FIELDX &&
5732 sy2 - sy1 < SCR_FIELDY);
5735 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5737 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5739 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5741 *sx = (sx1 + sx2) / 2;
5742 *sy = (sy1 + sy2) / 2;
5745 static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
5746 boolean center_screen, boolean quick_relocation)
5748 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5749 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5750 boolean no_delay = (tape.warp_forward);
5751 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5752 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5753 int new_scroll_x, new_scroll_y;
5755 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5757 // case 1: quick relocation inside visible screen (without scrolling)
5764 if (!level.shifted_relocation || center_screen)
5766 // relocation _with_ centering of screen
5768 new_scroll_x = SCROLL_POSITION_X(x);
5769 new_scroll_y = SCROLL_POSITION_Y(y);
5773 // relocation _without_ centering of screen
5775 // apply distance between old and new player position to scroll position
5776 int shifted_scroll_x = scroll_x + (x - old_x);
5777 int shifted_scroll_y = scroll_y + (y - old_y);
5779 // make sure that shifted scroll position does not scroll beyond screen
5780 new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
5781 new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
5783 // special case for teleporting from one end of the playfield to the other
5784 // (this kludge prevents the destination area to be shifted by half a tile
5785 // against the source destination for even screen width or screen height;
5786 // probably most useful when used with high "game.forced_scroll_delay_value"
5787 // in combination with "game.forced_scroll_x" and "game.forced_scroll_y")
5788 if (quick_relocation)
5790 if (EVEN(SCR_FIELDX))
5792 // relocate (teleport) between left and right border (half or full)
5793 if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1)
5794 new_scroll_x = SBX_Right;
5795 else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right)
5796 new_scroll_x = SBX_Right - 1;
5797 else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1)
5798 new_scroll_x = SBX_Left;
5799 else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left)
5800 new_scroll_x = SBX_Left + 1;
5803 if (EVEN(SCR_FIELDY))
5805 // relocate (teleport) between top and bottom border (half or full)
5806 if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1)
5807 new_scroll_y = SBY_Lower;
5808 else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower)
5809 new_scroll_y = SBY_Lower - 1;
5810 else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1)
5811 new_scroll_y = SBY_Upper;
5812 else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper)
5813 new_scroll_y = SBY_Upper + 1;
5818 if (quick_relocation)
5820 // case 2: quick relocation (redraw without visible scrolling)
5822 scroll_x = new_scroll_x;
5823 scroll_y = new_scroll_y;
5830 // case 3: visible relocation (with scrolling to new position)
5832 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5834 SetVideoFrameDelay(wait_delay_value);
5836 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5838 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5839 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5841 if (dx == 0 && dy == 0) // no scrolling needed at all
5847 // set values for horizontal/vertical screen scrolling (half tile size)
5848 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5849 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5850 int pos_x = dx * TILEX / 2;
5851 int pos_y = dy * TILEY / 2;
5852 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5853 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5855 ScrollLevel(dx, dy);
5858 // scroll in two steps of half tile size to make things smoother
5859 BlitScreenToBitmapExt_RND(window, fx, fy);
5861 // scroll second step to align at full tile size
5862 BlitScreenToBitmap(window);
5868 SetVideoFrameDelay(frame_delay_value_old);
5871 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5873 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5874 int player_nr = GET_PLAYER_NR(el_player);
5875 struct PlayerInfo *player = &stored_player[player_nr];
5876 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5877 boolean no_delay = (tape.warp_forward);
5878 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5879 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5880 int old_jx = player->jx;
5881 int old_jy = player->jy;
5882 int old_element = Tile[old_jx][old_jy];
5883 int element = Tile[jx][jy];
5884 boolean player_relocated = (old_jx != jx || old_jy != jy);
5886 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5887 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5888 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5889 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5890 int leave_side_horiz = move_dir_horiz;
5891 int leave_side_vert = move_dir_vert;
5892 int enter_side = enter_side_horiz | enter_side_vert;
5893 int leave_side = leave_side_horiz | leave_side_vert;
5895 if (player->buried) // do not reanimate dead player
5898 if (!player_relocated) // no need to relocate the player
5901 if (IS_PLAYER(jx, jy)) // player already placed at new position
5903 RemoveField(jx, jy); // temporarily remove newly placed player
5904 DrawLevelField(jx, jy);
5907 if (player->present)
5909 while (player->MovPos)
5911 ScrollPlayer(player, SCROLL_GO_ON);
5912 ScrollScreen(NULL, SCROLL_GO_ON);
5914 AdvanceFrameAndPlayerCounters(player->index_nr);
5918 BackToFront_WithFrameDelay(wait_delay_value);
5921 DrawPlayer(player); // needed here only to cleanup last field
5922 DrawLevelField(player->jx, player->jy); // remove player graphic
5924 player->is_moving = FALSE;
5927 if (IS_CUSTOM_ELEMENT(old_element))
5928 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5930 player->index_bit, leave_side);
5932 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5934 player->index_bit, leave_side);
5936 Tile[jx][jy] = el_player;
5937 InitPlayerField(jx, jy, el_player, TRUE);
5939 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5940 possible that the relocation target field did not contain a player element,
5941 but a walkable element, to which the new player was relocated -- in this
5942 case, restore that (already initialized!) element on the player field */
5943 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5945 Tile[jx][jy] = element; // restore previously existing element
5948 // only visually relocate centered player
5949 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy,
5950 FALSE, level.instant_relocation);
5952 TestIfPlayerTouchesBadThing(jx, jy);
5953 TestIfPlayerTouchesCustomElement(jx, jy);
5955 if (IS_CUSTOM_ELEMENT(element))
5956 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5957 player->index_bit, enter_side);
5959 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5960 player->index_bit, enter_side);
5962 if (player->is_switching)
5964 /* ensure that relocation while still switching an element does not cause
5965 a new element to be treated as also switched directly after relocation
5966 (this is important for teleporter switches that teleport the player to
5967 a place where another teleporter switch is in the same direction, which
5968 would then incorrectly be treated as immediately switched before the
5969 direction key that caused the switch was released) */
5971 player->switch_x += jx - old_jx;
5972 player->switch_y += jy - old_jy;
5976 static void Explode(int ex, int ey, int phase, int mode)
5982 if (game.explosions_delayed)
5984 ExplodeField[ex][ey] = mode;
5988 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5990 int center_element = Tile[ex][ey];
5991 int ce_value = CustomValue[ex][ey];
5992 int ce_score = element_info[center_element].collect_score;
5993 int artwork_element, explosion_element; // set these values later
5995 // remove things displayed in background while burning dynamite
5996 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5999 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
6001 // put moving element to center field (and let it explode there)
6002 center_element = MovingOrBlocked2Element(ex, ey);
6003 RemoveMovingField(ex, ey);
6004 Tile[ex][ey] = center_element;
6007 // now "center_element" is finally determined -- set related values now
6008 artwork_element = center_element; // for custom player artwork
6009 explosion_element = center_element; // for custom player artwork
6011 if (IS_PLAYER(ex, ey))
6013 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
6015 artwork_element = stored_player[player_nr].artwork_element;
6017 if (level.use_explosion_element[player_nr])
6019 explosion_element = level.explosion_element[player_nr];
6020 artwork_element = explosion_element;
6024 if (mode == EX_TYPE_NORMAL ||
6025 mode == EX_TYPE_CENTER ||
6026 mode == EX_TYPE_CROSS)
6027 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
6029 last_phase = element_info[explosion_element].explosion_delay + 1;
6031 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
6033 int xx = x - ex + 1;
6034 int yy = y - ey + 1;
6037 if (!IN_LEV_FIELD(x, y) ||
6038 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
6039 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
6042 element = Tile[x][y];
6044 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
6046 element = MovingOrBlocked2Element(x, y);
6048 if (!IS_EXPLOSION_PROOF(element))
6049 RemoveMovingField(x, y);
6052 // indestructible elements can only explode in center (but not flames)
6053 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
6054 mode == EX_TYPE_BORDER)) ||
6055 element == EL_FLAMES)
6058 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
6059 behaviour, for example when touching a yamyam that explodes to rocks
6060 with active deadly shield, a rock is created under the player !!! */
6061 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
6063 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
6064 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
6065 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
6067 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
6070 if (IS_ACTIVE_BOMB(element))
6072 // re-activate things under the bomb like gate or penguin
6073 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
6080 // save walkable background elements while explosion on same tile
6081 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
6082 (x != ex || y != ey || mode == EX_TYPE_BORDER))
6083 Back[x][y] = element;
6085 // ignite explodable elements reached by other explosion
6086 if (element == EL_EXPLOSION)
6087 element = Store2[x][y];
6089 if (AmoebaNr[x][y] &&
6090 (element == EL_AMOEBA_FULL ||
6091 element == EL_BD_AMOEBA ||
6092 element == EL_AMOEBA_GROWING))
6094 AmoebaCnt[AmoebaNr[x][y]]--;
6095 AmoebaCnt2[AmoebaNr[x][y]]--;
6100 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
6102 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
6104 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
6106 if (PLAYERINFO(ex, ey)->use_murphy)
6107 Store[x][y] = EL_EMPTY;
6110 // !!! check this case -- currently needed for rnd_rado_negundo_v,
6111 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
6112 else if (IS_PLAYER_ELEMENT(center_element))
6113 Store[x][y] = EL_EMPTY;
6114 else if (center_element == EL_YAMYAM)
6115 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
6116 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
6117 Store[x][y] = element_info[center_element].content.e[xx][yy];
6119 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
6120 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
6121 // otherwise) -- FIX THIS !!!
6122 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
6123 Store[x][y] = element_info[element].content.e[1][1];
6125 else if (!CAN_EXPLODE(element))
6126 Store[x][y] = element_info[element].content.e[1][1];
6129 Store[x][y] = EL_EMPTY;
6131 if (IS_CUSTOM_ELEMENT(center_element))
6132 Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value :
6133 Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score :
6134 Store[x][y] >= EL_PREV_CE_8 &&
6135 Store[x][y] <= EL_NEXT_CE_8 ?
6136 RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) :
6139 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
6140 center_element == EL_AMOEBA_TO_DIAMOND)
6141 Store2[x][y] = element;
6143 Tile[x][y] = EL_EXPLOSION;
6144 GfxElement[x][y] = artwork_element;
6146 ExplodePhase[x][y] = 1;
6147 ExplodeDelay[x][y] = last_phase;
6152 if (center_element == EL_YAMYAM)
6153 game.yamyam_content_nr =
6154 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
6166 GfxFrame[x][y] = 0; // restart explosion animation
6168 last_phase = ExplodeDelay[x][y];
6170 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
6172 // this can happen if the player leaves an explosion just in time
6173 if (GfxElement[x][y] == EL_UNDEFINED)
6174 GfxElement[x][y] = EL_EMPTY;
6176 border_element = Store2[x][y];
6177 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6178 border_element = StorePlayer[x][y];
6180 if (phase == element_info[border_element].ignition_delay ||
6181 phase == last_phase)
6183 boolean border_explosion = FALSE;
6185 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6186 !PLAYER_EXPLOSION_PROTECTED(x, y))
6188 KillPlayerUnlessExplosionProtected(x, y);
6189 border_explosion = TRUE;
6191 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6193 Tile[x][y] = Store2[x][y];
6196 border_explosion = TRUE;
6198 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6200 AmoebaToDiamond(x, y);
6202 border_explosion = TRUE;
6205 // if an element just explodes due to another explosion (chain-reaction),
6206 // do not immediately end the new explosion when it was the last frame of
6207 // the explosion (as it would be done in the following "if"-statement!)
6208 if (border_explosion && phase == last_phase)
6212 // this can happen if the player was just killed by an explosion
6213 if (GfxElement[x][y] == EL_UNDEFINED)
6214 GfxElement[x][y] = EL_EMPTY;
6216 if (phase == last_phase)
6220 element = Tile[x][y] = Store[x][y];
6221 Store[x][y] = Store2[x][y] = 0;
6222 GfxElement[x][y] = EL_UNDEFINED;
6224 // player can escape from explosions and might therefore be still alive
6225 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6226 element <= EL_PLAYER_IS_EXPLODING_4)
6228 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6229 int explosion_element = EL_PLAYER_1 + player_nr;
6230 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6231 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6233 if (level.use_explosion_element[player_nr])
6234 explosion_element = level.explosion_element[player_nr];
6236 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6237 element_info[explosion_element].content.e[xx][yy]);
6240 // restore probably existing indestructible background element
6241 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6242 element = Tile[x][y] = Back[x][y];
6245 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6246 GfxDir[x][y] = MV_NONE;
6247 ChangeDelay[x][y] = 0;
6248 ChangePage[x][y] = -1;
6250 CustomValue[x][y] = 0;
6252 InitField_WithBug2(x, y, FALSE);
6254 TEST_DrawLevelField(x, y);
6256 TestIfElementTouchesCustomElement(x, y);
6258 if (GFX_CRUMBLED(element))
6259 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6261 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6262 StorePlayer[x][y] = 0;
6264 if (IS_PLAYER_ELEMENT(element))
6265 RelocatePlayer(x, y, element);
6267 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6269 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6270 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6273 TEST_DrawLevelFieldCrumbled(x, y);
6275 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6277 DrawLevelElement(x, y, Back[x][y]);
6278 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6280 else if (IS_WALKABLE_UNDER(Back[x][y]))
6282 DrawLevelGraphic(x, y, graphic, frame);
6283 DrawLevelElementThruMask(x, y, Back[x][y]);
6285 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6286 DrawLevelGraphic(x, y, graphic, frame);
6290 static void DynaExplode(int ex, int ey)
6293 int dynabomb_element = Tile[ex][ey];
6294 int dynabomb_size = 1;
6295 boolean dynabomb_xl = FALSE;
6296 struct PlayerInfo *player;
6297 struct XY *xy = xy_topdown;
6299 if (IS_ACTIVE_BOMB(dynabomb_element))
6301 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6302 dynabomb_size = player->dynabomb_size;
6303 dynabomb_xl = player->dynabomb_xl;
6304 player->dynabombs_left++;
6307 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6309 for (i = 0; i < NUM_DIRECTIONS; i++)
6311 for (j = 1; j <= dynabomb_size; j++)
6313 int x = ex + j * xy[i].x;
6314 int y = ey + j * xy[i].y;
6317 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6320 element = Tile[x][y];
6322 // do not restart explosions of fields with active bombs
6323 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6326 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6328 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6329 !IS_DIGGABLE(element) && !dynabomb_xl)
6335 void Bang(int x, int y)
6337 int element = MovingOrBlocked2Element(x, y);
6338 int explosion_type = EX_TYPE_NORMAL;
6340 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6342 struct PlayerInfo *player = PLAYERINFO(x, y);
6344 element = Tile[x][y] = player->initial_element;
6346 if (level.use_explosion_element[player->index_nr])
6348 int explosion_element = level.explosion_element[player->index_nr];
6350 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6351 explosion_type = EX_TYPE_CROSS;
6352 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6353 explosion_type = EX_TYPE_CENTER;
6361 case EL_BD_BUTTERFLY:
6364 case EL_DARK_YAMYAM:
6368 RaiseScoreElement(element);
6371 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6372 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6373 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6374 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6375 case EL_DYNABOMB_INCREASE_NUMBER:
6376 case EL_DYNABOMB_INCREASE_SIZE:
6377 case EL_DYNABOMB_INCREASE_POWER:
6378 explosion_type = EX_TYPE_DYNA;
6381 case EL_DC_LANDMINE:
6382 explosion_type = EX_TYPE_CENTER;
6387 case EL_LAMP_ACTIVE:
6388 case EL_AMOEBA_TO_DIAMOND:
6389 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6390 explosion_type = EX_TYPE_CENTER;
6394 if (element_info[element].explosion_type == EXPLODES_CROSS)
6395 explosion_type = EX_TYPE_CROSS;
6396 else if (element_info[element].explosion_type == EXPLODES_1X1)
6397 explosion_type = EX_TYPE_CENTER;
6401 if (explosion_type == EX_TYPE_DYNA)
6404 Explode(x, y, EX_PHASE_START, explosion_type);
6406 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6409 static void SplashAcid(int x, int y)
6411 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6412 (!IN_LEV_FIELD(x - 1, y - 2) ||
6413 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6414 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6416 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6417 (!IN_LEV_FIELD(x + 1, y - 2) ||
6418 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6419 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6421 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6424 static void InitBeltMovement(void)
6426 static int belt_base_element[4] =
6428 EL_CONVEYOR_BELT_1_LEFT,
6429 EL_CONVEYOR_BELT_2_LEFT,
6430 EL_CONVEYOR_BELT_3_LEFT,
6431 EL_CONVEYOR_BELT_4_LEFT
6433 static int belt_base_active_element[4] =
6435 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6436 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6437 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6438 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6443 // set frame order for belt animation graphic according to belt direction
6444 for (i = 0; i < NUM_BELTS; i++)
6448 for (j = 0; j < NUM_BELT_PARTS; j++)
6450 int element = belt_base_active_element[belt_nr] + j;
6451 int graphic_1 = el2img(element);
6452 int graphic_2 = el2panelimg(element);
6454 if (game.belt_dir[i] == MV_LEFT)
6456 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6457 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6461 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6462 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6467 SCAN_PLAYFIELD(x, y)
6469 int element = Tile[x][y];
6471 for (i = 0; i < NUM_BELTS; i++)
6473 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6475 int e_belt_nr = getBeltNrFromBeltElement(element);
6478 if (e_belt_nr == belt_nr)
6480 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6482 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6489 static void ToggleBeltSwitch(int x, int y)
6491 static int belt_base_element[4] =
6493 EL_CONVEYOR_BELT_1_LEFT,
6494 EL_CONVEYOR_BELT_2_LEFT,
6495 EL_CONVEYOR_BELT_3_LEFT,
6496 EL_CONVEYOR_BELT_4_LEFT
6498 static int belt_base_active_element[4] =
6500 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6501 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6502 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6503 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6505 static int belt_base_switch_element[4] =
6507 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6508 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6509 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6510 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6512 static int belt_move_dir[4] =
6520 int element = Tile[x][y];
6521 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6522 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6523 int belt_dir = belt_move_dir[belt_dir_nr];
6526 if (!IS_BELT_SWITCH(element))
6529 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6530 game.belt_dir[belt_nr] = belt_dir;
6532 if (belt_dir_nr == 3)
6535 // set frame order for belt animation graphic according to belt direction
6536 for (i = 0; i < NUM_BELT_PARTS; i++)
6538 int element = belt_base_active_element[belt_nr] + i;
6539 int graphic_1 = el2img(element);
6540 int graphic_2 = el2panelimg(element);
6542 if (belt_dir == MV_LEFT)
6544 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6545 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6549 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6550 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6554 SCAN_PLAYFIELD(xx, yy)
6556 int element = Tile[xx][yy];
6558 if (IS_BELT_SWITCH(element))
6560 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6562 if (e_belt_nr == belt_nr)
6564 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6565 TEST_DrawLevelField(xx, yy);
6568 else if (IS_BELT(element) && belt_dir != MV_NONE)
6570 int e_belt_nr = getBeltNrFromBeltElement(element);
6572 if (e_belt_nr == belt_nr)
6574 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6576 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6577 TEST_DrawLevelField(xx, yy);
6580 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6582 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6584 if (e_belt_nr == belt_nr)
6586 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6588 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6589 TEST_DrawLevelField(xx, yy);
6595 static void ToggleSwitchgateSwitch(void)
6599 game.switchgate_pos = !game.switchgate_pos;
6601 SCAN_PLAYFIELD(xx, yy)
6603 int element = Tile[xx][yy];
6605 if (element == EL_SWITCHGATE_SWITCH_UP)
6607 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6608 TEST_DrawLevelField(xx, yy);
6610 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6612 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6613 TEST_DrawLevelField(xx, yy);
6615 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6617 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6618 TEST_DrawLevelField(xx, yy);
6620 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6622 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6623 TEST_DrawLevelField(xx, yy);
6625 else if (element == EL_SWITCHGATE_OPEN ||
6626 element == EL_SWITCHGATE_OPENING)
6628 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6630 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6632 else if (element == EL_SWITCHGATE_CLOSED ||
6633 element == EL_SWITCHGATE_CLOSING)
6635 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6637 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6642 static int getInvisibleActiveFromInvisibleElement(int element)
6644 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6645 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6646 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6650 static int getInvisibleFromInvisibleActiveElement(int element)
6652 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6653 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6654 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6658 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6662 SCAN_PLAYFIELD(x, y)
6664 int element = Tile[x][y];
6666 if (element == EL_LIGHT_SWITCH &&
6667 game.light_time_left > 0)
6669 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6670 TEST_DrawLevelField(x, y);
6672 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6673 game.light_time_left == 0)
6675 Tile[x][y] = EL_LIGHT_SWITCH;
6676 TEST_DrawLevelField(x, y);
6678 else if (element == EL_EMC_DRIPPER &&
6679 game.light_time_left > 0)
6681 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6682 TEST_DrawLevelField(x, y);
6684 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6685 game.light_time_left == 0)
6687 Tile[x][y] = EL_EMC_DRIPPER;
6688 TEST_DrawLevelField(x, y);
6690 else if (element == EL_INVISIBLE_STEELWALL ||
6691 element == EL_INVISIBLE_WALL ||
6692 element == EL_INVISIBLE_SAND)
6694 if (game.light_time_left > 0)
6695 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6697 TEST_DrawLevelField(x, y);
6699 // uncrumble neighbour fields, if needed
6700 if (element == EL_INVISIBLE_SAND)
6701 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6703 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6704 element == EL_INVISIBLE_WALL_ACTIVE ||
6705 element == EL_INVISIBLE_SAND_ACTIVE)
6707 if (game.light_time_left == 0)
6708 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6710 TEST_DrawLevelField(x, y);
6712 // re-crumble neighbour fields, if needed
6713 if (element == EL_INVISIBLE_SAND)
6714 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6719 static void RedrawAllInvisibleElementsForLenses(void)
6723 SCAN_PLAYFIELD(x, y)
6725 int element = Tile[x][y];
6727 if (element == EL_EMC_DRIPPER &&
6728 game.lenses_time_left > 0)
6730 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6731 TEST_DrawLevelField(x, y);
6733 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6734 game.lenses_time_left == 0)
6736 Tile[x][y] = EL_EMC_DRIPPER;
6737 TEST_DrawLevelField(x, y);
6739 else if (element == EL_INVISIBLE_STEELWALL ||
6740 element == EL_INVISIBLE_WALL ||
6741 element == EL_INVISIBLE_SAND)
6743 if (game.lenses_time_left > 0)
6744 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6746 TEST_DrawLevelField(x, y);
6748 // uncrumble neighbour fields, if needed
6749 if (element == EL_INVISIBLE_SAND)
6750 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6752 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6753 element == EL_INVISIBLE_WALL_ACTIVE ||
6754 element == EL_INVISIBLE_SAND_ACTIVE)
6756 if (game.lenses_time_left == 0)
6757 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6759 TEST_DrawLevelField(x, y);
6761 // re-crumble neighbour fields, if needed
6762 if (element == EL_INVISIBLE_SAND)
6763 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6768 static void RedrawAllInvisibleElementsForMagnifier(void)
6772 SCAN_PLAYFIELD(x, y)
6774 int element = Tile[x][y];
6776 if (element == EL_EMC_FAKE_GRASS &&
6777 game.magnify_time_left > 0)
6779 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6780 TEST_DrawLevelField(x, y);
6782 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6783 game.magnify_time_left == 0)
6785 Tile[x][y] = EL_EMC_FAKE_GRASS;
6786 TEST_DrawLevelField(x, y);
6788 else if (IS_GATE_GRAY(element) &&
6789 game.magnify_time_left > 0)
6791 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6792 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6793 IS_EM_GATE_GRAY(element) ?
6794 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6795 IS_EMC_GATE_GRAY(element) ?
6796 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6797 IS_DC_GATE_GRAY(element) ?
6798 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6800 TEST_DrawLevelField(x, y);
6802 else if (IS_GATE_GRAY_ACTIVE(element) &&
6803 game.magnify_time_left == 0)
6805 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6806 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6807 IS_EM_GATE_GRAY_ACTIVE(element) ?
6808 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6809 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6810 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6811 IS_DC_GATE_GRAY_ACTIVE(element) ?
6812 EL_DC_GATE_WHITE_GRAY :
6814 TEST_DrawLevelField(x, y);
6819 static void ToggleLightSwitch(int x, int y)
6821 int element = Tile[x][y];
6823 game.light_time_left =
6824 (element == EL_LIGHT_SWITCH ?
6825 level.time_light * FRAMES_PER_SECOND : 0);
6827 RedrawAllLightSwitchesAndInvisibleElements();
6830 static void ActivateTimegateSwitch(int x, int y)
6834 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6836 SCAN_PLAYFIELD(xx, yy)
6838 int element = Tile[xx][yy];
6840 if (element == EL_TIMEGATE_CLOSED ||
6841 element == EL_TIMEGATE_CLOSING)
6843 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6844 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6848 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6850 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6851 TEST_DrawLevelField(xx, yy);
6857 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6858 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6861 static void Impact(int x, int y)
6863 boolean last_line = (y == lev_fieldy - 1);
6864 boolean object_hit = FALSE;
6865 boolean impact = (last_line || object_hit);
6866 int element = Tile[x][y];
6867 int smashed = EL_STEELWALL;
6869 if (!last_line) // check if element below was hit
6871 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6874 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6875 MovDir[x][y + 1] != MV_DOWN ||
6876 MovPos[x][y + 1] <= TILEY / 2));
6878 // do not smash moving elements that left the smashed field in time
6879 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6880 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6883 #if USE_QUICKSAND_IMPACT_BUGFIX
6884 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6886 RemoveMovingField(x, y + 1);
6887 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6888 Tile[x][y + 2] = EL_ROCK;
6889 TEST_DrawLevelField(x, y + 2);
6894 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6896 RemoveMovingField(x, y + 1);
6897 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6898 Tile[x][y + 2] = EL_ROCK;
6899 TEST_DrawLevelField(x, y + 2);
6906 smashed = MovingOrBlocked2Element(x, y + 1);
6908 impact = (last_line || object_hit);
6911 if (!last_line && smashed == EL_ACID) // element falls into acid
6913 SplashAcid(x, y + 1);
6917 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6918 // only reset graphic animation if graphic really changes after impact
6920 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6922 ResetGfxAnimation(x, y);
6923 TEST_DrawLevelField(x, y);
6926 if (impact && CAN_EXPLODE_IMPACT(element))
6931 else if (impact && element == EL_PEARL &&
6932 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6934 ResetGfxAnimation(x, y);
6936 Tile[x][y] = EL_PEARL_BREAKING;
6937 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6940 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6942 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6947 if (impact && element == EL_AMOEBA_DROP)
6949 if (object_hit && IS_PLAYER(x, y + 1))
6950 KillPlayerUnlessEnemyProtected(x, y + 1);
6951 else if (object_hit && smashed == EL_PENGUIN)
6955 Tile[x][y] = EL_AMOEBA_GROWING;
6956 Store[x][y] = EL_AMOEBA_WET;
6958 ResetRandomAnimationValue(x, y);
6963 if (object_hit) // check which object was hit
6965 if ((CAN_PASS_MAGIC_WALL(element) &&
6966 (smashed == EL_MAGIC_WALL ||
6967 smashed == EL_BD_MAGIC_WALL)) ||
6968 (CAN_PASS_DC_MAGIC_WALL(element) &&
6969 smashed == EL_DC_MAGIC_WALL))
6972 int activated_magic_wall =
6973 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6974 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6975 EL_DC_MAGIC_WALL_ACTIVE);
6977 // activate magic wall / mill
6978 SCAN_PLAYFIELD(xx, yy)
6980 if (Tile[xx][yy] == smashed)
6981 Tile[xx][yy] = activated_magic_wall;
6984 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6985 game.magic_wall_active = TRUE;
6987 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6988 SND_MAGIC_WALL_ACTIVATING :
6989 smashed == EL_BD_MAGIC_WALL ?
6990 SND_BD_MAGIC_WALL_ACTIVATING :
6991 SND_DC_MAGIC_WALL_ACTIVATING));
6994 if (IS_PLAYER(x, y + 1))
6996 if (CAN_SMASH_PLAYER(element))
6998 KillPlayerUnlessEnemyProtected(x, y + 1);
7002 else if (smashed == EL_PENGUIN)
7004 if (CAN_SMASH_PLAYER(element))
7010 else if (element == EL_BD_DIAMOND)
7012 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
7018 else if (((element == EL_SP_INFOTRON ||
7019 element == EL_SP_ZONK) &&
7020 (smashed == EL_SP_SNIKSNAK ||
7021 smashed == EL_SP_ELECTRON ||
7022 smashed == EL_SP_DISK_ORANGE)) ||
7023 (element == EL_SP_INFOTRON &&
7024 smashed == EL_SP_DISK_YELLOW))
7029 else if (CAN_SMASH_EVERYTHING(element))
7031 if (IS_CLASSIC_ENEMY(smashed) ||
7032 CAN_EXPLODE_SMASHED(smashed))
7037 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
7039 if (smashed == EL_LAMP ||
7040 smashed == EL_LAMP_ACTIVE)
7045 else if (smashed == EL_NUT)
7047 Tile[x][y + 1] = EL_NUT_BREAKING;
7048 PlayLevelSound(x, y, SND_NUT_BREAKING);
7049 RaiseScoreElement(EL_NUT);
7052 else if (smashed == EL_PEARL)
7054 ResetGfxAnimation(x, y);
7056 Tile[x][y + 1] = EL_PEARL_BREAKING;
7057 PlayLevelSound(x, y, SND_PEARL_BREAKING);
7060 else if (smashed == EL_DIAMOND)
7062 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
7063 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
7066 else if (IS_BELT_SWITCH(smashed))
7068 ToggleBeltSwitch(x, y + 1);
7070 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
7071 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
7072 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
7073 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
7075 ToggleSwitchgateSwitch();
7077 else if (smashed == EL_LIGHT_SWITCH ||
7078 smashed == EL_LIGHT_SWITCH_ACTIVE)
7080 ToggleLightSwitch(x, y + 1);
7084 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7086 CheckElementChangeBySide(x, y + 1, smashed, element,
7087 CE_SWITCHED, CH_SIDE_TOP);
7088 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
7094 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7099 // play sound of magic wall / mill
7101 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7102 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
7103 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
7105 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7106 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
7107 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7108 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
7109 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7110 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
7115 // play sound of object that hits the ground
7116 if (last_line || object_hit)
7117 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
7120 static void TurnRoundExt(int x, int y)
7132 { 0, 0 }, { 0, 0 }, { 0, 0 },
7137 int left, right, back;
7141 { MV_DOWN, MV_UP, MV_RIGHT },
7142 { MV_UP, MV_DOWN, MV_LEFT },
7144 { MV_LEFT, MV_RIGHT, MV_DOWN },
7148 { MV_RIGHT, MV_LEFT, MV_UP }
7151 int element = Tile[x][y];
7152 int move_pattern = element_info[element].move_pattern;
7154 int old_move_dir = MovDir[x][y];
7155 int left_dir = turn[old_move_dir].left;
7156 int right_dir = turn[old_move_dir].right;
7157 int back_dir = turn[old_move_dir].back;
7159 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
7160 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
7161 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
7162 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
7164 int left_x = x + left_dx, left_y = y + left_dy;
7165 int right_x = x + right_dx, right_y = y + right_dy;
7166 int move_x = x + move_dx, move_y = y + move_dy;
7170 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
7172 TestIfBadThingTouchesOtherBadThing(x, y);
7174 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
7175 MovDir[x][y] = right_dir;
7176 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7177 MovDir[x][y] = left_dir;
7179 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7181 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7184 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7186 TestIfBadThingTouchesOtherBadThing(x, y);
7188 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7189 MovDir[x][y] = left_dir;
7190 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7191 MovDir[x][y] = right_dir;
7193 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7195 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7198 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7200 TestIfBadThingTouchesOtherBadThing(x, y);
7202 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7203 MovDir[x][y] = left_dir;
7204 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7205 MovDir[x][y] = right_dir;
7207 if (MovDir[x][y] != old_move_dir)
7210 else if (element == EL_YAMYAM)
7212 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7213 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7215 if (can_turn_left && can_turn_right)
7216 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7217 else if (can_turn_left)
7218 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7219 else if (can_turn_right)
7220 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7222 MovDir[x][y] = back_dir;
7224 MovDelay[x][y] = 16 + 16 * RND(3);
7226 else if (element == EL_DARK_YAMYAM)
7228 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7230 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7233 if (can_turn_left && can_turn_right)
7234 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7235 else if (can_turn_left)
7236 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7237 else if (can_turn_right)
7238 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7240 MovDir[x][y] = back_dir;
7242 MovDelay[x][y] = 16 + 16 * RND(3);
7244 else if (element == EL_PACMAN)
7246 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7247 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7249 if (can_turn_left && can_turn_right)
7250 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7251 else if (can_turn_left)
7252 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7253 else if (can_turn_right)
7254 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7256 MovDir[x][y] = back_dir;
7258 MovDelay[x][y] = 6 + RND(40);
7260 else if (element == EL_PIG)
7262 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7263 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7264 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7265 boolean should_turn_left, should_turn_right, should_move_on;
7267 int rnd = RND(rnd_value);
7269 should_turn_left = (can_turn_left &&
7271 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7272 y + back_dy + left_dy)));
7273 should_turn_right = (can_turn_right &&
7275 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7276 y + back_dy + right_dy)));
7277 should_move_on = (can_move_on &&
7280 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7281 y + move_dy + left_dy) ||
7282 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7283 y + move_dy + right_dy)));
7285 if (should_turn_left || should_turn_right || should_move_on)
7287 if (should_turn_left && should_turn_right && should_move_on)
7288 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7289 rnd < 2 * rnd_value / 3 ? right_dir :
7291 else if (should_turn_left && should_turn_right)
7292 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7293 else if (should_turn_left && should_move_on)
7294 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7295 else if (should_turn_right && should_move_on)
7296 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7297 else if (should_turn_left)
7298 MovDir[x][y] = left_dir;
7299 else if (should_turn_right)
7300 MovDir[x][y] = right_dir;
7301 else if (should_move_on)
7302 MovDir[x][y] = old_move_dir;
7304 else if (can_move_on && rnd > rnd_value / 8)
7305 MovDir[x][y] = old_move_dir;
7306 else if (can_turn_left && can_turn_right)
7307 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7308 else if (can_turn_left && rnd > rnd_value / 8)
7309 MovDir[x][y] = left_dir;
7310 else if (can_turn_right && rnd > rnd_value/8)
7311 MovDir[x][y] = right_dir;
7313 MovDir[x][y] = back_dir;
7315 xx = x + move_xy[MovDir[x][y]].dx;
7316 yy = y + move_xy[MovDir[x][y]].dy;
7318 if (!IN_LEV_FIELD(xx, yy) ||
7319 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7320 MovDir[x][y] = old_move_dir;
7324 else if (element == EL_DRAGON)
7326 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7327 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7328 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7330 int rnd = RND(rnd_value);
7332 if (can_move_on && rnd > rnd_value / 8)
7333 MovDir[x][y] = old_move_dir;
7334 else if (can_turn_left && can_turn_right)
7335 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7336 else if (can_turn_left && rnd > rnd_value / 8)
7337 MovDir[x][y] = left_dir;
7338 else if (can_turn_right && rnd > rnd_value / 8)
7339 MovDir[x][y] = right_dir;
7341 MovDir[x][y] = back_dir;
7343 xx = x + move_xy[MovDir[x][y]].dx;
7344 yy = y + move_xy[MovDir[x][y]].dy;
7346 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7347 MovDir[x][y] = old_move_dir;
7351 else if (element == EL_MOLE)
7353 boolean can_move_on =
7354 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7355 IS_AMOEBOID(Tile[move_x][move_y]) ||
7356 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7359 boolean can_turn_left =
7360 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7361 IS_AMOEBOID(Tile[left_x][left_y])));
7363 boolean can_turn_right =
7364 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7365 IS_AMOEBOID(Tile[right_x][right_y])));
7367 if (can_turn_left && can_turn_right)
7368 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7369 else if (can_turn_left)
7370 MovDir[x][y] = left_dir;
7372 MovDir[x][y] = right_dir;
7375 if (MovDir[x][y] != old_move_dir)
7378 else if (element == EL_BALLOON)
7380 MovDir[x][y] = game.wind_direction;
7383 else if (element == EL_SPRING)
7385 if (MovDir[x][y] & MV_HORIZONTAL)
7387 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7388 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7390 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7391 ResetGfxAnimation(move_x, move_y);
7392 TEST_DrawLevelField(move_x, move_y);
7394 MovDir[x][y] = back_dir;
7396 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7397 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7398 MovDir[x][y] = MV_NONE;
7403 else if (element == EL_ROBOT ||
7404 element == EL_SATELLITE ||
7405 element == EL_PENGUIN ||
7406 element == EL_EMC_ANDROID)
7408 int attr_x = -1, attr_y = -1;
7410 if (game.all_players_gone)
7412 attr_x = game.exit_x;
7413 attr_y = game.exit_y;
7419 for (i = 0; i < MAX_PLAYERS; i++)
7421 struct PlayerInfo *player = &stored_player[i];
7422 int jx = player->jx, jy = player->jy;
7424 if (!player->active)
7428 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7436 if (element == EL_ROBOT &&
7437 game.robot_wheel_x >= 0 &&
7438 game.robot_wheel_y >= 0 &&
7439 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7440 game.engine_version < VERSION_IDENT(3,1,0,0)))
7442 attr_x = game.robot_wheel_x;
7443 attr_y = game.robot_wheel_y;
7446 if (element == EL_PENGUIN)
7449 struct XY *xy = xy_topdown;
7451 for (i = 0; i < NUM_DIRECTIONS; i++)
7453 int ex = x + xy[i].x;
7454 int ey = y + xy[i].y;
7456 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7457 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7458 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7459 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7468 MovDir[x][y] = MV_NONE;
7470 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7471 else if (attr_x > x)
7472 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7474 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7475 else if (attr_y > y)
7476 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7478 if (element == EL_ROBOT)
7482 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7483 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7484 Moving2Blocked(x, y, &newx, &newy);
7486 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7487 MovDelay[x][y] = 8 + 8 * !RND(3);
7489 MovDelay[x][y] = 16;
7491 else if (element == EL_PENGUIN)
7497 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7499 boolean first_horiz = RND(2);
7500 int new_move_dir = MovDir[x][y];
7503 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7504 Moving2Blocked(x, y, &newx, &newy);
7506 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7510 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7511 Moving2Blocked(x, y, &newx, &newy);
7513 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7516 MovDir[x][y] = old_move_dir;
7520 else if (element == EL_SATELLITE)
7526 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7528 boolean first_horiz = RND(2);
7529 int new_move_dir = MovDir[x][y];
7532 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7533 Moving2Blocked(x, y, &newx, &newy);
7535 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7539 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7540 Moving2Blocked(x, y, &newx, &newy);
7542 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7545 MovDir[x][y] = old_move_dir;
7549 else if (element == EL_EMC_ANDROID)
7551 static int check_pos[16] =
7553 -1, // 0 => (invalid)
7556 -1, // 3 => (invalid)
7558 0, // 5 => MV_LEFT | MV_UP
7559 2, // 6 => MV_RIGHT | MV_UP
7560 -1, // 7 => (invalid)
7562 6, // 9 => MV_LEFT | MV_DOWN
7563 4, // 10 => MV_RIGHT | MV_DOWN
7564 -1, // 11 => (invalid)
7565 -1, // 12 => (invalid)
7566 -1, // 13 => (invalid)
7567 -1, // 14 => (invalid)
7568 -1, // 15 => (invalid)
7576 { -1, -1, MV_LEFT | MV_UP },
7578 { +1, -1, MV_RIGHT | MV_UP },
7579 { +1, 0, MV_RIGHT },
7580 { +1, +1, MV_RIGHT | MV_DOWN },
7582 { -1, +1, MV_LEFT | MV_DOWN },
7585 int start_pos, check_order;
7586 boolean can_clone = FALSE;
7589 // check if there is any free field around current position
7590 for (i = 0; i < 8; i++)
7592 int newx = x + check_xy[i].dx;
7593 int newy = y + check_xy[i].dy;
7595 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7603 if (can_clone) // randomly find an element to clone
7607 start_pos = check_pos[RND(8)];
7608 check_order = (RND(2) ? -1 : +1);
7610 for (i = 0; i < 8; i++)
7612 int pos_raw = start_pos + i * check_order;
7613 int pos = (pos_raw + 8) % 8;
7614 int newx = x + check_xy[pos].dx;
7615 int newy = y + check_xy[pos].dy;
7617 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7619 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7620 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7622 Store[x][y] = Tile[newx][newy];
7631 if (can_clone) // randomly find a direction to move
7635 start_pos = check_pos[RND(8)];
7636 check_order = (RND(2) ? -1 : +1);
7638 for (i = 0; i < 8; i++)
7640 int pos_raw = start_pos + i * check_order;
7641 int pos = (pos_raw + 8) % 8;
7642 int newx = x + check_xy[pos].dx;
7643 int newy = y + check_xy[pos].dy;
7644 int new_move_dir = check_xy[pos].dir;
7646 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7648 MovDir[x][y] = new_move_dir;
7649 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7658 if (can_clone) // cloning and moving successful
7661 // cannot clone -- try to move towards player
7663 start_pos = check_pos[MovDir[x][y] & 0x0f];
7664 check_order = (RND(2) ? -1 : +1);
7666 for (i = 0; i < 3; i++)
7668 // first check start_pos, then previous/next or (next/previous) pos
7669 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7670 int pos = (pos_raw + 8) % 8;
7671 int newx = x + check_xy[pos].dx;
7672 int newy = y + check_xy[pos].dy;
7673 int new_move_dir = check_xy[pos].dir;
7675 if (IS_PLAYER(newx, newy))
7678 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7680 MovDir[x][y] = new_move_dir;
7681 MovDelay[x][y] = level.android_move_time * 8 + 1;
7688 else if (move_pattern == MV_TURNING_LEFT ||
7689 move_pattern == MV_TURNING_RIGHT ||
7690 move_pattern == MV_TURNING_LEFT_RIGHT ||
7691 move_pattern == MV_TURNING_RIGHT_LEFT ||
7692 move_pattern == MV_TURNING_RANDOM ||
7693 move_pattern == MV_ALL_DIRECTIONS)
7695 boolean can_turn_left =
7696 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7697 boolean can_turn_right =
7698 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y);
7700 if (element_info[element].move_stepsize == 0) // "not moving"
7703 if (move_pattern == MV_TURNING_LEFT)
7704 MovDir[x][y] = left_dir;
7705 else if (move_pattern == MV_TURNING_RIGHT)
7706 MovDir[x][y] = right_dir;
7707 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7708 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7709 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7710 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7711 else if (move_pattern == MV_TURNING_RANDOM)
7712 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7713 can_turn_right && !can_turn_left ? right_dir :
7714 RND(2) ? left_dir : right_dir);
7715 else if (can_turn_left && can_turn_right)
7716 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7717 else if (can_turn_left)
7718 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7719 else if (can_turn_right)
7720 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7722 MovDir[x][y] = back_dir;
7724 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7726 else if (move_pattern == MV_HORIZONTAL ||
7727 move_pattern == MV_VERTICAL)
7729 if (move_pattern & old_move_dir)
7730 MovDir[x][y] = back_dir;
7731 else if (move_pattern == MV_HORIZONTAL)
7732 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7733 else if (move_pattern == MV_VERTICAL)
7734 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7736 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7738 else if (move_pattern & MV_ANY_DIRECTION)
7740 MovDir[x][y] = move_pattern;
7741 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7743 else if (move_pattern & MV_WIND_DIRECTION)
7745 MovDir[x][y] = game.wind_direction;
7746 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7748 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7750 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7751 MovDir[x][y] = left_dir;
7752 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7753 MovDir[x][y] = right_dir;
7755 if (MovDir[x][y] != old_move_dir)
7756 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7758 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7760 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7761 MovDir[x][y] = right_dir;
7762 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7763 MovDir[x][y] = left_dir;
7765 if (MovDir[x][y] != old_move_dir)
7766 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7768 else if (move_pattern == MV_TOWARDS_PLAYER ||
7769 move_pattern == MV_AWAY_FROM_PLAYER)
7771 int attr_x = -1, attr_y = -1;
7773 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7775 if (game.all_players_gone)
7777 attr_x = game.exit_x;
7778 attr_y = game.exit_y;
7784 for (i = 0; i < MAX_PLAYERS; i++)
7786 struct PlayerInfo *player = &stored_player[i];
7787 int jx = player->jx, jy = player->jy;
7789 if (!player->active)
7793 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7801 MovDir[x][y] = MV_NONE;
7803 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7804 else if (attr_x > x)
7805 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7807 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7808 else if (attr_y > y)
7809 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7811 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7813 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7815 boolean first_horiz = RND(2);
7816 int new_move_dir = MovDir[x][y];
7818 if (element_info[element].move_stepsize == 0) // "not moving"
7820 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7821 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7827 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7828 Moving2Blocked(x, y, &newx, &newy);
7830 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7834 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7835 Moving2Blocked(x, y, &newx, &newy);
7837 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7840 MovDir[x][y] = old_move_dir;
7843 else if (move_pattern == MV_WHEN_PUSHED ||
7844 move_pattern == MV_WHEN_DROPPED)
7846 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7847 MovDir[x][y] = MV_NONE;
7851 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7853 struct XY *test_xy = xy_topdown;
7854 static int test_dir[4] =
7861 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7862 int move_preference = -1000000; // start with very low preference
7863 int new_move_dir = MV_NONE;
7864 int start_test = RND(4);
7867 for (i = 0; i < NUM_DIRECTIONS; i++)
7869 int j = (start_test + i) % 4;
7870 int move_dir = test_dir[j];
7871 int move_dir_preference;
7873 xx = x + test_xy[j].x;
7874 yy = y + test_xy[j].y;
7876 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7877 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7879 new_move_dir = move_dir;
7884 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7887 move_dir_preference = -1 * RunnerVisit[xx][yy];
7888 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7889 move_dir_preference = PlayerVisit[xx][yy];
7891 if (move_dir_preference > move_preference)
7893 // prefer field that has not been visited for the longest time
7894 move_preference = move_dir_preference;
7895 new_move_dir = move_dir;
7897 else if (move_dir_preference == move_preference &&
7898 move_dir == old_move_dir)
7900 // prefer last direction when all directions are preferred equally
7901 move_preference = move_dir_preference;
7902 new_move_dir = move_dir;
7906 MovDir[x][y] = new_move_dir;
7907 if (old_move_dir != new_move_dir)
7908 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7912 static void TurnRound(int x, int y)
7914 int direction = MovDir[x][y];
7918 GfxDir[x][y] = MovDir[x][y];
7920 if (direction != MovDir[x][y])
7924 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7926 ResetGfxFrame(x, y);
7929 static boolean JustBeingPushed(int x, int y)
7933 for (i = 0; i < MAX_PLAYERS; i++)
7935 struct PlayerInfo *player = &stored_player[i];
7937 if (player->active && player->is_pushing && player->MovPos)
7939 int next_jx = player->jx + (player->jx - player->last_jx);
7940 int next_jy = player->jy + (player->jy - player->last_jy);
7942 if (x == next_jx && y == next_jy)
7950 static void StartMoving(int x, int y)
7952 boolean started_moving = FALSE; // some elements can fall _and_ move
7953 int element = Tile[x][y];
7958 if (MovDelay[x][y] == 0)
7959 GfxAction[x][y] = ACTION_DEFAULT;
7961 if (CAN_FALL(element) && y < lev_fieldy - 1)
7963 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7964 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7965 if (JustBeingPushed(x, y))
7968 if (element == EL_QUICKSAND_FULL)
7970 if (IS_FREE(x, y + 1))
7972 InitMovingField(x, y, MV_DOWN);
7973 started_moving = TRUE;
7975 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7976 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7977 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7978 Store[x][y] = EL_ROCK;
7980 Store[x][y] = EL_ROCK;
7983 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7985 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7987 if (!MovDelay[x][y])
7989 MovDelay[x][y] = TILEY + 1;
7991 ResetGfxAnimation(x, y);
7992 ResetGfxAnimation(x, y + 1);
7997 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7998 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8005 Tile[x][y] = EL_QUICKSAND_EMPTY;
8006 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8007 Store[x][y + 1] = Store[x][y];
8010 PlayLevelSoundAction(x, y, ACTION_FILLING);
8012 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8014 if (!MovDelay[x][y])
8016 MovDelay[x][y] = TILEY + 1;
8018 ResetGfxAnimation(x, y);
8019 ResetGfxAnimation(x, y + 1);
8024 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
8025 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8032 Tile[x][y] = EL_QUICKSAND_EMPTY;
8033 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8034 Store[x][y + 1] = Store[x][y];
8037 PlayLevelSoundAction(x, y, ACTION_FILLING);
8040 else if (element == EL_QUICKSAND_FAST_FULL)
8042 if (IS_FREE(x, y + 1))
8044 InitMovingField(x, y, MV_DOWN);
8045 started_moving = TRUE;
8047 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
8048 #if USE_QUICKSAND_BD_ROCK_BUGFIX
8049 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
8050 Store[x][y] = EL_ROCK;
8052 Store[x][y] = EL_ROCK;
8055 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
8057 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8059 if (!MovDelay[x][y])
8061 MovDelay[x][y] = TILEY + 1;
8063 ResetGfxAnimation(x, y);
8064 ResetGfxAnimation(x, y + 1);
8069 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8070 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
8077 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8078 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8079 Store[x][y + 1] = Store[x][y];
8082 PlayLevelSoundAction(x, y, ACTION_FILLING);
8084 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8086 if (!MovDelay[x][y])
8088 MovDelay[x][y] = TILEY + 1;
8090 ResetGfxAnimation(x, y);
8091 ResetGfxAnimation(x, y + 1);
8096 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8097 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8104 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8105 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8106 Store[x][y + 1] = Store[x][y];
8109 PlayLevelSoundAction(x, y, ACTION_FILLING);
8112 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8113 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8115 InitMovingField(x, y, MV_DOWN);
8116 started_moving = TRUE;
8118 Tile[x][y] = EL_QUICKSAND_FILLING;
8119 Store[x][y] = element;
8121 PlayLevelSoundAction(x, y, ACTION_FILLING);
8123 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8124 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8126 InitMovingField(x, y, MV_DOWN);
8127 started_moving = TRUE;
8129 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
8130 Store[x][y] = element;
8132 PlayLevelSoundAction(x, y, ACTION_FILLING);
8134 else if (element == EL_MAGIC_WALL_FULL)
8136 if (IS_FREE(x, y + 1))
8138 InitMovingField(x, y, MV_DOWN);
8139 started_moving = TRUE;
8141 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
8142 Store[x][y] = EL_CHANGED(Store[x][y]);
8144 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
8146 if (!MovDelay[x][y])
8147 MovDelay[x][y] = TILEY / 4 + 1;
8156 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
8157 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
8158 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8162 else if (element == EL_BD_MAGIC_WALL_FULL)
8164 if (IS_FREE(x, y + 1))
8166 InitMovingField(x, y, MV_DOWN);
8167 started_moving = TRUE;
8169 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8170 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8172 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8174 if (!MovDelay[x][y])
8175 MovDelay[x][y] = TILEY / 4 + 1;
8184 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8185 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8186 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8190 else if (element == EL_DC_MAGIC_WALL_FULL)
8192 if (IS_FREE(x, y + 1))
8194 InitMovingField(x, y, MV_DOWN);
8195 started_moving = TRUE;
8197 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8198 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8200 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8202 if (!MovDelay[x][y])
8203 MovDelay[x][y] = TILEY / 4 + 1;
8212 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8213 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8214 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8218 else if ((CAN_PASS_MAGIC_WALL(element) &&
8219 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8220 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8221 (CAN_PASS_DC_MAGIC_WALL(element) &&
8222 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8225 InitMovingField(x, y, MV_DOWN);
8226 started_moving = TRUE;
8229 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8230 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8231 EL_DC_MAGIC_WALL_FILLING);
8232 Store[x][y] = element;
8234 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8236 SplashAcid(x, y + 1);
8238 InitMovingField(x, y, MV_DOWN);
8239 started_moving = TRUE;
8241 Store[x][y] = EL_ACID;
8244 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8245 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8246 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8247 CAN_FALL(element) && WasJustFalling[x][y] &&
8248 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8250 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8251 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8252 (Tile[x][y + 1] == EL_BLOCKED)))
8254 /* this is needed for a special case not covered by calling "Impact()"
8255 from "ContinueMoving()": if an element moves to a tile directly below
8256 another element which was just falling on that tile (which was empty
8257 in the previous frame), the falling element above would just stop
8258 instead of smashing the element below (in previous version, the above
8259 element was just checked for "moving" instead of "falling", resulting
8260 in incorrect smashes caused by horizontal movement of the above
8261 element; also, the case of the player being the element to smash was
8262 simply not covered here... :-/ ) */
8264 CheckCollision[x][y] = 0;
8265 CheckImpact[x][y] = 0;
8269 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8271 if (MovDir[x][y] == MV_NONE)
8273 InitMovingField(x, y, MV_DOWN);
8274 started_moving = TRUE;
8277 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8279 if (WasJustFalling[x][y]) // prevent animation from being restarted
8280 MovDir[x][y] = MV_DOWN;
8282 InitMovingField(x, y, MV_DOWN);
8283 started_moving = TRUE;
8285 else if (element == EL_AMOEBA_DROP)
8287 Tile[x][y] = EL_AMOEBA_GROWING;
8288 Store[x][y] = EL_AMOEBA_WET;
8290 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8291 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8292 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8293 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8295 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8296 (IS_FREE(x - 1, y + 1) ||
8297 Tile[x - 1][y + 1] == EL_ACID));
8298 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8299 (IS_FREE(x + 1, y + 1) ||
8300 Tile[x + 1][y + 1] == EL_ACID));
8301 boolean can_fall_any = (can_fall_left || can_fall_right);
8302 boolean can_fall_both = (can_fall_left && can_fall_right);
8303 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8305 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8307 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8308 can_fall_right = FALSE;
8309 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8310 can_fall_left = FALSE;
8311 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8312 can_fall_right = FALSE;
8313 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8314 can_fall_left = FALSE;
8316 can_fall_any = (can_fall_left || can_fall_right);
8317 can_fall_both = FALSE;
8322 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8323 can_fall_right = FALSE; // slip down on left side
8325 can_fall_left = !(can_fall_right = RND(2));
8327 can_fall_both = FALSE;
8332 // if not determined otherwise, prefer left side for slipping down
8333 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8334 started_moving = TRUE;
8337 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8339 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8340 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8341 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8342 int belt_dir = game.belt_dir[belt_nr];
8344 if ((belt_dir == MV_LEFT && left_is_free) ||
8345 (belt_dir == MV_RIGHT && right_is_free))
8347 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8349 InitMovingField(x, y, belt_dir);
8350 started_moving = TRUE;
8352 Pushed[x][y] = TRUE;
8353 Pushed[nextx][y] = TRUE;
8355 GfxAction[x][y] = ACTION_DEFAULT;
8359 MovDir[x][y] = 0; // if element was moving, stop it
8364 // not "else if" because of elements that can fall and move (EL_SPRING)
8365 if (CAN_MOVE(element) && !started_moving)
8367 int move_pattern = element_info[element].move_pattern;
8370 Moving2Blocked(x, y, &newx, &newy);
8372 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8375 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8376 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8378 WasJustMoving[x][y] = 0;
8379 CheckCollision[x][y] = 0;
8381 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8383 if (Tile[x][y] != element) // element has changed
8387 if (!MovDelay[x][y]) // start new movement phase
8389 // all objects that can change their move direction after each step
8390 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8392 if (element != EL_YAMYAM &&
8393 element != EL_DARK_YAMYAM &&
8394 element != EL_PACMAN &&
8395 !(move_pattern & MV_ANY_DIRECTION) &&
8396 move_pattern != MV_TURNING_LEFT &&
8397 move_pattern != MV_TURNING_RIGHT &&
8398 move_pattern != MV_TURNING_LEFT_RIGHT &&
8399 move_pattern != MV_TURNING_RIGHT_LEFT &&
8400 move_pattern != MV_TURNING_RANDOM)
8404 if (MovDelay[x][y] && (element == EL_BUG ||
8405 element == EL_SPACESHIP ||
8406 element == EL_SP_SNIKSNAK ||
8407 element == EL_SP_ELECTRON ||
8408 element == EL_MOLE))
8409 TEST_DrawLevelField(x, y);
8413 if (MovDelay[x][y]) // wait some time before next movement
8417 if (element == EL_ROBOT ||
8418 element == EL_YAMYAM ||
8419 element == EL_DARK_YAMYAM)
8421 DrawLevelElementAnimationIfNeeded(x, y, element);
8422 PlayLevelSoundAction(x, y, ACTION_WAITING);
8424 else if (element == EL_SP_ELECTRON)
8425 DrawLevelElementAnimationIfNeeded(x, y, element);
8426 else if (element == EL_DRAGON)
8429 int dir = MovDir[x][y];
8430 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8431 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8432 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8433 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8434 dir == MV_UP ? IMG_FLAMES_1_UP :
8435 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8436 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8438 GfxAction[x][y] = ACTION_ATTACKING;
8440 if (IS_PLAYER(x, y))
8441 DrawPlayerField(x, y);
8443 TEST_DrawLevelField(x, y);
8445 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8447 for (i = 1; i <= 3; i++)
8449 int xx = x + i * dx;
8450 int yy = y + i * dy;
8451 int sx = SCREENX(xx);
8452 int sy = SCREENY(yy);
8453 int flame_graphic = graphic + (i - 1);
8455 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8460 int flamed = MovingOrBlocked2Element(xx, yy);
8462 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8465 RemoveMovingField(xx, yy);
8467 ChangeDelay[xx][yy] = 0;
8469 Tile[xx][yy] = EL_FLAMES;
8471 if (IN_SCR_FIELD(sx, sy))
8473 TEST_DrawLevelFieldCrumbled(xx, yy);
8474 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8479 if (Tile[xx][yy] == EL_FLAMES)
8480 Tile[xx][yy] = EL_EMPTY;
8481 TEST_DrawLevelField(xx, yy);
8486 if (MovDelay[x][y]) // element still has to wait some time
8488 PlayLevelSoundAction(x, y, ACTION_WAITING);
8494 // now make next step
8496 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8498 if (DONT_COLLIDE_WITH(element) &&
8499 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8500 !PLAYER_ENEMY_PROTECTED(newx, newy))
8502 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8507 else if (CAN_MOVE_INTO_ACID(element) &&
8508 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8509 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8510 (MovDir[x][y] == MV_DOWN ||
8511 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8513 SplashAcid(newx, newy);
8514 Store[x][y] = EL_ACID;
8516 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8518 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8519 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8520 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8521 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8524 TEST_DrawLevelField(x, y);
8526 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8527 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8528 DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0);
8530 game.friends_still_needed--;
8531 if (!game.friends_still_needed &&
8533 game.all_players_gone)
8538 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8540 if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING)
8541 TEST_DrawLevelField(newx, newy);
8543 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8545 else if (!IS_FREE(newx, newy))
8547 GfxAction[x][y] = ACTION_WAITING;
8549 if (IS_PLAYER(x, y))
8550 DrawPlayerField(x, y);
8552 TEST_DrawLevelField(x, y);
8557 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8559 if (IS_FOOD_PIG(Tile[newx][newy]))
8561 if (IS_MOVING(newx, newy))
8562 RemoveMovingField(newx, newy);
8565 Tile[newx][newy] = EL_EMPTY;
8566 TEST_DrawLevelField(newx, newy);
8569 PlayLevelSound(x, y, SND_PIG_DIGGING);
8571 else if (!IS_FREE(newx, newy))
8573 if (IS_PLAYER(x, y))
8574 DrawPlayerField(x, y);
8576 TEST_DrawLevelField(x, y);
8581 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8583 if (Store[x][y] != EL_EMPTY)
8585 boolean can_clone = FALSE;
8588 // check if element to clone is still there
8589 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8591 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8599 // cannot clone or target field not free anymore -- do not clone
8600 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8601 Store[x][y] = EL_EMPTY;
8604 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8606 if (IS_MV_DIAGONAL(MovDir[x][y]))
8608 int diagonal_move_dir = MovDir[x][y];
8609 int stored = Store[x][y];
8610 int change_delay = 8;
8613 // android is moving diagonally
8615 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8617 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8618 GfxElement[x][y] = EL_EMC_ANDROID;
8619 GfxAction[x][y] = ACTION_SHRINKING;
8620 GfxDir[x][y] = diagonal_move_dir;
8621 ChangeDelay[x][y] = change_delay;
8623 if (Store[x][y] == EL_EMPTY)
8624 Store[x][y] = GfxElementEmpty[x][y];
8626 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8629 DrawLevelGraphicAnimation(x, y, graphic);
8630 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8632 if (Tile[newx][newy] == EL_ACID)
8634 SplashAcid(newx, newy);
8639 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8641 Store[newx][newy] = EL_EMC_ANDROID;
8642 GfxElement[newx][newy] = EL_EMC_ANDROID;
8643 GfxAction[newx][newy] = ACTION_GROWING;
8644 GfxDir[newx][newy] = diagonal_move_dir;
8645 ChangeDelay[newx][newy] = change_delay;
8647 graphic = el_act_dir2img(GfxElement[newx][newy],
8648 GfxAction[newx][newy], GfxDir[newx][newy]);
8650 DrawLevelGraphicAnimation(newx, newy, graphic);
8651 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8657 Tile[newx][newy] = EL_EMPTY;
8658 TEST_DrawLevelField(newx, newy);
8660 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8663 else if (!IS_FREE(newx, newy))
8668 else if (IS_CUSTOM_ELEMENT(element) &&
8669 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8671 if (!DigFieldByCE(newx, newy, element))
8674 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8676 RunnerVisit[x][y] = FrameCounter;
8677 PlayerVisit[x][y] /= 8; // expire player visit path
8680 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8682 if (!IS_FREE(newx, newy))
8684 if (IS_PLAYER(x, y))
8685 DrawPlayerField(x, y);
8687 TEST_DrawLevelField(x, y);
8693 boolean wanna_flame = !RND(10);
8694 int dx = newx - x, dy = newy - y;
8695 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8696 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8697 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8698 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8699 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8700 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8703 IS_CLASSIC_ENEMY(element1) ||
8704 IS_CLASSIC_ENEMY(element2)) &&
8705 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8706 element1 != EL_FLAMES && element2 != EL_FLAMES)
8708 ResetGfxAnimation(x, y);
8709 GfxAction[x][y] = ACTION_ATTACKING;
8711 if (IS_PLAYER(x, y))
8712 DrawPlayerField(x, y);
8714 TEST_DrawLevelField(x, y);
8716 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8718 MovDelay[x][y] = 50;
8720 Tile[newx][newy] = EL_FLAMES;
8721 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8722 Tile[newx1][newy1] = EL_FLAMES;
8723 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8724 Tile[newx2][newy2] = EL_FLAMES;
8730 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8731 Tile[newx][newy] == EL_DIAMOND)
8733 if (IS_MOVING(newx, newy))
8734 RemoveMovingField(newx, newy);
8737 Tile[newx][newy] = EL_EMPTY;
8738 TEST_DrawLevelField(newx, newy);
8741 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8743 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8744 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8746 if (AmoebaNr[newx][newy])
8748 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8749 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8750 Tile[newx][newy] == EL_BD_AMOEBA)
8751 AmoebaCnt[AmoebaNr[newx][newy]]--;
8754 if (IS_MOVING(newx, newy))
8756 RemoveMovingField(newx, newy);
8760 Tile[newx][newy] = EL_EMPTY;
8761 TEST_DrawLevelField(newx, newy);
8764 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8766 else if ((element == EL_PACMAN || element == EL_MOLE)
8767 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8769 if (AmoebaNr[newx][newy])
8771 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8772 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8773 Tile[newx][newy] == EL_BD_AMOEBA)
8774 AmoebaCnt[AmoebaNr[newx][newy]]--;
8777 if (element == EL_MOLE)
8779 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8780 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8782 ResetGfxAnimation(x, y);
8783 GfxAction[x][y] = ACTION_DIGGING;
8784 TEST_DrawLevelField(x, y);
8786 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8788 return; // wait for shrinking amoeba
8790 else // element == EL_PACMAN
8792 Tile[newx][newy] = EL_EMPTY;
8793 TEST_DrawLevelField(newx, newy);
8794 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8797 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8798 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8799 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8801 // wait for shrinking amoeba to completely disappear
8804 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8806 // object was running against a wall
8810 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8811 DrawLevelElementAnimation(x, y, element);
8813 if (DONT_TOUCH(element))
8814 TestIfBadThingTouchesPlayer(x, y);
8819 InitMovingField(x, y, MovDir[x][y]);
8821 PlayLevelSoundAction(x, y, ACTION_MOVING);
8825 ContinueMoving(x, y);
8828 void ContinueMoving(int x, int y)
8830 int element = Tile[x][y];
8831 struct ElementInfo *ei = &element_info[element];
8832 int direction = MovDir[x][y];
8833 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8834 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8835 int newx = x + dx, newy = y + dy;
8836 int stored = Store[x][y];
8837 int stored_new = Store[newx][newy];
8838 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8839 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8840 boolean last_line = (newy == lev_fieldy - 1);
8841 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8843 if (pushed_by_player) // special case: moving object pushed by player
8845 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos));
8847 else if (use_step_delay) // special case: moving object has step delay
8849 if (!MovDelay[x][y])
8850 MovPos[x][y] += getElementMoveStepsize(x, y);
8855 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8859 TEST_DrawLevelField(x, y);
8861 return; // element is still waiting
8864 else // normal case: generically moving object
8866 MovPos[x][y] += getElementMoveStepsize(x, y);
8869 if (ABS(MovPos[x][y]) < TILEX)
8871 TEST_DrawLevelField(x, y);
8873 return; // element is still moving
8876 // element reached destination field
8878 Tile[x][y] = EL_EMPTY;
8879 Tile[newx][newy] = element;
8880 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8882 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8884 element = Tile[newx][newy] = EL_ACID;
8886 else if (element == EL_MOLE)
8888 Tile[x][y] = EL_SAND;
8890 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8892 else if (element == EL_QUICKSAND_FILLING)
8894 element = Tile[newx][newy] = get_next_element(element);
8895 Store[newx][newy] = Store[x][y];
8897 else if (element == EL_QUICKSAND_EMPTYING)
8899 Tile[x][y] = get_next_element(element);
8900 element = Tile[newx][newy] = Store[x][y];
8902 else if (element == EL_QUICKSAND_FAST_FILLING)
8904 element = Tile[newx][newy] = get_next_element(element);
8905 Store[newx][newy] = Store[x][y];
8907 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8909 Tile[x][y] = get_next_element(element);
8910 element = Tile[newx][newy] = Store[x][y];
8912 else if (element == EL_MAGIC_WALL_FILLING)
8914 element = Tile[newx][newy] = get_next_element(element);
8915 if (!game.magic_wall_active)
8916 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8917 Store[newx][newy] = Store[x][y];
8919 else if (element == EL_MAGIC_WALL_EMPTYING)
8921 Tile[x][y] = get_next_element(element);
8922 if (!game.magic_wall_active)
8923 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8924 element = Tile[newx][newy] = Store[x][y];
8926 InitField(newx, newy, FALSE);
8928 else if (element == EL_BD_MAGIC_WALL_FILLING)
8930 element = Tile[newx][newy] = get_next_element(element);
8931 if (!game.magic_wall_active)
8932 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8933 Store[newx][newy] = Store[x][y];
8935 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8937 Tile[x][y] = get_next_element(element);
8938 if (!game.magic_wall_active)
8939 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8940 element = Tile[newx][newy] = Store[x][y];
8942 InitField(newx, newy, FALSE);
8944 else if (element == EL_DC_MAGIC_WALL_FILLING)
8946 element = Tile[newx][newy] = get_next_element(element);
8947 if (!game.magic_wall_active)
8948 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8949 Store[newx][newy] = Store[x][y];
8951 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8953 Tile[x][y] = get_next_element(element);
8954 if (!game.magic_wall_active)
8955 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8956 element = Tile[newx][newy] = Store[x][y];
8958 InitField(newx, newy, FALSE);
8960 else if (element == EL_AMOEBA_DROPPING)
8962 Tile[x][y] = get_next_element(element);
8963 element = Tile[newx][newy] = Store[x][y];
8965 else if (element == EL_SOKOBAN_OBJECT)
8968 Tile[x][y] = Back[x][y];
8970 if (Back[newx][newy])
8971 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8973 Back[x][y] = Back[newx][newy] = 0;
8976 Store[x][y] = EL_EMPTY;
8981 MovDelay[newx][newy] = 0;
8983 if (CAN_CHANGE_OR_HAS_ACTION(element))
8985 // copy element change control values to new field
8986 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8987 ChangePage[newx][newy] = ChangePage[x][y];
8988 ChangeCount[newx][newy] = ChangeCount[x][y];
8989 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8992 CustomValue[newx][newy] = CustomValue[x][y];
8994 ChangeDelay[x][y] = 0;
8995 ChangePage[x][y] = -1;
8996 ChangeCount[x][y] = 0;
8997 ChangeEvent[x][y] = -1;
8999 CustomValue[x][y] = 0;
9001 // copy animation control values to new field
9002 GfxFrame[newx][newy] = GfxFrame[x][y];
9003 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
9004 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
9005 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
9007 Pushed[x][y] = Pushed[newx][newy] = FALSE;
9009 // some elements can leave other elements behind after moving
9010 if (ei->move_leave_element != EL_EMPTY &&
9011 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
9012 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
9014 int move_leave_element = ei->move_leave_element;
9016 // this makes it possible to leave the removed element again
9017 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
9018 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
9020 Tile[x][y] = move_leave_element;
9022 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
9023 MovDir[x][y] = direction;
9025 InitField(x, y, FALSE);
9027 if (GFX_CRUMBLED(Tile[x][y]))
9028 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
9030 if (IS_PLAYER_ELEMENT(move_leave_element))
9031 RelocatePlayer(x, y, move_leave_element);
9034 // do this after checking for left-behind element
9035 ResetGfxAnimation(x, y); // reset animation values for old field
9037 if (!CAN_MOVE(element) ||
9038 (CAN_FALL(element) && direction == MV_DOWN &&
9039 (element == EL_SPRING ||
9040 element_info[element].move_pattern == MV_WHEN_PUSHED ||
9041 element_info[element].move_pattern == MV_WHEN_DROPPED)))
9042 GfxDir[x][y] = MovDir[newx][newy] = 0;
9044 TEST_DrawLevelField(x, y);
9045 TEST_DrawLevelField(newx, newy);
9047 Stop[newx][newy] = TRUE; // ignore this element until the next frame
9049 // prevent pushed element from moving on in pushed direction
9050 if (pushed_by_player && CAN_MOVE(element) &&
9051 element_info[element].move_pattern & MV_ANY_DIRECTION &&
9052 !(element_info[element].move_pattern & direction))
9053 TurnRound(newx, newy);
9055 // prevent elements on conveyor belt from moving on in last direction
9056 if (pushed_by_conveyor && CAN_FALL(element) &&
9057 direction & MV_HORIZONTAL)
9058 MovDir[newx][newy] = 0;
9060 if (!pushed_by_player)
9062 int nextx = newx + dx, nexty = newy + dy;
9063 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
9065 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
9067 if (CAN_FALL(element) && direction == MV_DOWN)
9068 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
9070 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
9071 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
9073 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
9074 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
9077 if (DONT_TOUCH(element)) // object may be nasty to player or others
9079 TestIfBadThingTouchesPlayer(newx, newy);
9080 TestIfBadThingTouchesFriend(newx, newy);
9082 if (!IS_CUSTOM_ELEMENT(element))
9083 TestIfBadThingTouchesOtherBadThing(newx, newy);
9085 else if (element == EL_PENGUIN)
9086 TestIfFriendTouchesBadThing(newx, newy);
9088 if (DONT_GET_HIT_BY(element))
9090 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
9093 // give the player one last chance (one more frame) to move away
9094 if (CAN_FALL(element) && direction == MV_DOWN &&
9095 (last_line || (!IS_FREE(x, newy + 1) &&
9096 (!IS_PLAYER(x, newy + 1) ||
9097 game.engine_version < VERSION_IDENT(3,1,1,0)))))
9100 if (pushed_by_player && !game.use_change_when_pushing_bug)
9102 int push_side = MV_DIR_OPPOSITE(direction);
9103 struct PlayerInfo *player = PLAYERINFO(x, y);
9105 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
9106 player->index_bit, push_side);
9107 CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X,
9108 player->index_bit, push_side);
9111 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
9112 MovDelay[newx][newy] = 1;
9114 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
9116 TestIfElementTouchesCustomElement(x, y); // empty or new element
9117 TestIfElementHitsCustomElement(newx, newy, direction);
9118 TestIfPlayerTouchesCustomElement(newx, newy);
9119 TestIfElementTouchesCustomElement(newx, newy);
9121 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
9122 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
9123 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
9124 MV_DIR_OPPOSITE(direction));
9127 int AmoebaNeighbourNr(int ax, int ay)
9130 int element = Tile[ax][ay];
9132 struct XY *xy = xy_topdown;
9134 for (i = 0; i < NUM_DIRECTIONS; i++)
9136 int x = ax + xy[i].x;
9137 int y = ay + xy[i].y;
9139 if (!IN_LEV_FIELD(x, y))
9142 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
9143 group_nr = AmoebaNr[x][y];
9149 static void AmoebaMerge(int ax, int ay)
9151 int i, x, y, xx, yy;
9152 int new_group_nr = AmoebaNr[ax][ay];
9153 struct XY *xy = xy_topdown;
9155 if (new_group_nr == 0)
9158 for (i = 0; i < NUM_DIRECTIONS; i++)
9163 if (!IN_LEV_FIELD(x, y))
9166 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9167 Tile[x][y] == EL_BD_AMOEBA ||
9168 Tile[x][y] == EL_AMOEBA_DEAD) &&
9169 AmoebaNr[x][y] != new_group_nr)
9171 int old_group_nr = AmoebaNr[x][y];
9173 if (old_group_nr == 0)
9176 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9177 AmoebaCnt[old_group_nr] = 0;
9178 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9179 AmoebaCnt2[old_group_nr] = 0;
9181 SCAN_PLAYFIELD(xx, yy)
9183 if (AmoebaNr[xx][yy] == old_group_nr)
9184 AmoebaNr[xx][yy] = new_group_nr;
9190 void AmoebaToDiamond(int ax, int ay)
9194 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9196 int group_nr = AmoebaNr[ax][ay];
9201 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9202 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9208 SCAN_PLAYFIELD(x, y)
9210 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9213 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9217 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9218 SND_AMOEBA_TURNING_TO_GEM :
9219 SND_AMOEBA_TURNING_TO_ROCK));
9224 struct XY *xy = xy_topdown;
9226 for (i = 0; i < NUM_DIRECTIONS; i++)
9231 if (!IN_LEV_FIELD(x, y))
9234 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9236 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9237 SND_AMOEBA_TURNING_TO_GEM :
9238 SND_AMOEBA_TURNING_TO_ROCK));
9245 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9248 int group_nr = AmoebaNr[ax][ay];
9249 boolean done = FALSE;
9254 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9255 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9261 SCAN_PLAYFIELD(x, y)
9263 if (AmoebaNr[x][y] == group_nr &&
9264 (Tile[x][y] == EL_AMOEBA_DEAD ||
9265 Tile[x][y] == EL_BD_AMOEBA ||
9266 Tile[x][y] == EL_AMOEBA_GROWING))
9269 Tile[x][y] = new_element;
9270 InitField(x, y, FALSE);
9271 TEST_DrawLevelField(x, y);
9277 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9278 SND_BD_AMOEBA_TURNING_TO_ROCK :
9279 SND_BD_AMOEBA_TURNING_TO_GEM));
9282 static void AmoebaGrowing(int x, int y)
9284 static DelayCounter sound_delay = { 0 };
9286 if (!MovDelay[x][y]) // start new growing cycle
9290 if (DelayReached(&sound_delay))
9292 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9293 sound_delay.value = 30;
9297 if (MovDelay[x][y]) // wait some time before growing bigger
9300 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9302 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9303 6 - MovDelay[x][y]);
9305 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9308 if (!MovDelay[x][y])
9310 Tile[x][y] = Store[x][y];
9312 TEST_DrawLevelField(x, y);
9317 static void AmoebaShrinking(int x, int y)
9319 static DelayCounter sound_delay = { 0 };
9321 if (!MovDelay[x][y]) // start new shrinking cycle
9325 if (DelayReached(&sound_delay))
9326 sound_delay.value = 30;
9329 if (MovDelay[x][y]) // wait some time before shrinking
9332 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9334 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9335 6 - MovDelay[x][y]);
9337 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9340 if (!MovDelay[x][y])
9342 Tile[x][y] = EL_EMPTY;
9343 TEST_DrawLevelField(x, y);
9345 // don't let mole enter this field in this cycle;
9346 // (give priority to objects falling to this field from above)
9352 static void AmoebaReproduce(int ax, int ay)
9355 int element = Tile[ax][ay];
9356 int graphic = el2img(element);
9357 int newax = ax, neway = ay;
9358 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9359 struct XY *xy = xy_topdown;
9361 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9363 Tile[ax][ay] = EL_AMOEBA_DEAD;
9364 TEST_DrawLevelField(ax, ay);
9368 if (IS_ANIMATED(graphic))
9369 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9371 if (!MovDelay[ax][ay]) // start making new amoeba field
9372 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9374 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9377 if (MovDelay[ax][ay])
9381 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9384 int x = ax + xy[start].x;
9385 int y = ay + xy[start].y;
9387 if (!IN_LEV_FIELD(x, y))
9390 if (IS_FREE(x, y) ||
9391 CAN_GROW_INTO(Tile[x][y]) ||
9392 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9393 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9399 if (newax == ax && neway == ay)
9402 else // normal or "filled" (BD style) amoeba
9405 boolean waiting_for_player = FALSE;
9407 for (i = 0; i < NUM_DIRECTIONS; i++)
9409 int j = (start + i) % 4;
9410 int x = ax + xy[j].x;
9411 int y = ay + xy[j].y;
9413 if (!IN_LEV_FIELD(x, y))
9416 if (IS_FREE(x, y) ||
9417 CAN_GROW_INTO(Tile[x][y]) ||
9418 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9419 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9425 else if (IS_PLAYER(x, y))
9426 waiting_for_player = TRUE;
9429 if (newax == ax && neway == ay) // amoeba cannot grow
9431 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9433 Tile[ax][ay] = EL_AMOEBA_DEAD;
9434 TEST_DrawLevelField(ax, ay);
9435 AmoebaCnt[AmoebaNr[ax][ay]]--;
9437 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9439 if (element == EL_AMOEBA_FULL)
9440 AmoebaToDiamond(ax, ay);
9441 else if (element == EL_BD_AMOEBA)
9442 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9447 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9449 // amoeba gets larger by growing in some direction
9451 int new_group_nr = AmoebaNr[ax][ay];
9454 if (new_group_nr == 0)
9456 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9458 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9464 AmoebaNr[newax][neway] = new_group_nr;
9465 AmoebaCnt[new_group_nr]++;
9466 AmoebaCnt2[new_group_nr]++;
9468 // if amoeba touches other amoeba(s) after growing, unify them
9469 AmoebaMerge(newax, neway);
9471 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9473 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9479 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9480 (neway == lev_fieldy - 1 && newax != ax))
9482 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9483 Store[newax][neway] = element;
9485 else if (neway == ay || element == EL_EMC_DRIPPER)
9487 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9489 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9493 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9494 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9495 Store[ax][ay] = EL_AMOEBA_DROP;
9496 ContinueMoving(ax, ay);
9500 TEST_DrawLevelField(newax, neway);
9503 static void Life(int ax, int ay)
9507 int element = Tile[ax][ay];
9508 int graphic = el2img(element);
9509 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9511 boolean changed = FALSE;
9513 if (IS_ANIMATED(graphic))
9514 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9519 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9520 MovDelay[ax][ay] = life_time;
9522 if (MovDelay[ax][ay]) // wait some time before next cycle
9525 if (MovDelay[ax][ay])
9529 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9531 int xx = ax + x1, yy = ay + y1;
9532 int old_element = Tile[xx][yy];
9533 int num_neighbours = 0;
9535 if (!IN_LEV_FIELD(xx, yy))
9538 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9540 int x = xx + x2, y = yy + y2;
9542 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9545 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9546 boolean is_neighbour = FALSE;
9548 if (level.use_life_bugs)
9550 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9551 (IS_FREE(x, y) && Stop[x][y]));
9554 (Last[x][y] == element || is_player_cell);
9560 boolean is_free = FALSE;
9562 if (level.use_life_bugs)
9563 is_free = (IS_FREE(xx, yy));
9565 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9567 if (xx == ax && yy == ay) // field in the middle
9569 if (num_neighbours < life_parameter[0] ||
9570 num_neighbours > life_parameter[1])
9572 Tile[xx][yy] = EL_EMPTY;
9573 if (Tile[xx][yy] != old_element)
9574 TEST_DrawLevelField(xx, yy);
9575 Stop[xx][yy] = TRUE;
9579 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9580 { // free border field
9581 if (num_neighbours >= life_parameter[2] &&
9582 num_neighbours <= life_parameter[3])
9584 Tile[xx][yy] = element;
9585 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9586 if (Tile[xx][yy] != old_element)
9587 TEST_DrawLevelField(xx, yy);
9588 Stop[xx][yy] = TRUE;
9595 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9596 SND_GAME_OF_LIFE_GROWING);
9599 static void InitRobotWheel(int x, int y)
9601 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9604 static void RunRobotWheel(int x, int y)
9606 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9609 static void StopRobotWheel(int x, int y)
9611 if (game.robot_wheel_x == x &&
9612 game.robot_wheel_y == y)
9614 game.robot_wheel_x = -1;
9615 game.robot_wheel_y = -1;
9616 game.robot_wheel_active = FALSE;
9620 static void InitTimegateWheel(int x, int y)
9622 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9625 static void RunTimegateWheel(int x, int y)
9627 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9630 static void InitMagicBallDelay(int x, int y)
9632 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9635 static void ActivateMagicBall(int bx, int by)
9639 if (level.ball_random)
9641 int pos_border = RND(8); // select one of the eight border elements
9642 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9643 int xx = pos_content % 3;
9644 int yy = pos_content / 3;
9649 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9650 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9654 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9656 int xx = x - bx + 1;
9657 int yy = y - by + 1;
9659 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9660 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9664 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9667 static void CheckExit(int x, int y)
9669 if (game.gems_still_needed > 0 ||
9670 game.sokoban_fields_still_needed > 0 ||
9671 game.sokoban_objects_still_needed > 0 ||
9672 game.lights_still_needed > 0)
9674 int element = Tile[x][y];
9675 int graphic = el2img(element);
9677 if (IS_ANIMATED(graphic))
9678 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9683 // do not re-open exit door closed after last player
9684 if (game.all_players_gone)
9687 Tile[x][y] = EL_EXIT_OPENING;
9689 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9692 static void CheckExitEM(int x, int y)
9694 if (game.gems_still_needed > 0 ||
9695 game.sokoban_fields_still_needed > 0 ||
9696 game.sokoban_objects_still_needed > 0 ||
9697 game.lights_still_needed > 0)
9699 int element = Tile[x][y];
9700 int graphic = el2img(element);
9702 if (IS_ANIMATED(graphic))
9703 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9708 // do not re-open exit door closed after last player
9709 if (game.all_players_gone)
9712 Tile[x][y] = EL_EM_EXIT_OPENING;
9714 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9717 static void CheckExitSteel(int x, int y)
9719 if (game.gems_still_needed > 0 ||
9720 game.sokoban_fields_still_needed > 0 ||
9721 game.sokoban_objects_still_needed > 0 ||
9722 game.lights_still_needed > 0)
9724 int element = Tile[x][y];
9725 int graphic = el2img(element);
9727 if (IS_ANIMATED(graphic))
9728 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9733 // do not re-open exit door closed after last player
9734 if (game.all_players_gone)
9737 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9739 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9742 static void CheckExitSteelEM(int x, int y)
9744 if (game.gems_still_needed > 0 ||
9745 game.sokoban_fields_still_needed > 0 ||
9746 game.sokoban_objects_still_needed > 0 ||
9747 game.lights_still_needed > 0)
9749 int element = Tile[x][y];
9750 int graphic = el2img(element);
9752 if (IS_ANIMATED(graphic))
9753 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9758 // do not re-open exit door closed after last player
9759 if (game.all_players_gone)
9762 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9764 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9767 static void CheckExitSP(int x, int y)
9769 if (game.gems_still_needed > 0)
9771 int element = Tile[x][y];
9772 int graphic = el2img(element);
9774 if (IS_ANIMATED(graphic))
9775 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9780 // do not re-open exit door closed after last player
9781 if (game.all_players_gone)
9784 Tile[x][y] = EL_SP_EXIT_OPENING;
9786 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9789 static void CloseAllOpenTimegates(void)
9793 SCAN_PLAYFIELD(x, y)
9795 int element = Tile[x][y];
9797 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9799 Tile[x][y] = EL_TIMEGATE_CLOSING;
9801 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9806 static void DrawTwinkleOnField(int x, int y)
9808 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9811 if (Tile[x][y] == EL_BD_DIAMOND)
9814 if (MovDelay[x][y] == 0) // next animation frame
9815 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9817 if (MovDelay[x][y] != 0) // wait some time before next frame
9821 DrawLevelElementAnimation(x, y, Tile[x][y]);
9823 if (MovDelay[x][y] != 0)
9825 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9826 10 - MovDelay[x][y]);
9828 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9833 static void WallGrowing(int x, int y)
9837 if (!MovDelay[x][y]) // next animation frame
9838 MovDelay[x][y] = 3 * delay;
9840 if (MovDelay[x][y]) // wait some time before next frame
9844 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9846 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9847 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9849 DrawLevelGraphic(x, y, graphic, frame);
9852 if (!MovDelay[x][y])
9854 if (MovDir[x][y] == MV_LEFT)
9856 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9857 TEST_DrawLevelField(x - 1, y);
9859 else if (MovDir[x][y] == MV_RIGHT)
9861 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9862 TEST_DrawLevelField(x + 1, y);
9864 else if (MovDir[x][y] == MV_UP)
9866 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9867 TEST_DrawLevelField(x, y - 1);
9871 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9872 TEST_DrawLevelField(x, y + 1);
9875 Tile[x][y] = Store[x][y];
9877 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9878 TEST_DrawLevelField(x, y);
9883 static void CheckWallGrowing(int ax, int ay)
9885 int element = Tile[ax][ay];
9886 int graphic = el2img(element);
9887 boolean free_top = FALSE;
9888 boolean free_bottom = FALSE;
9889 boolean free_left = FALSE;
9890 boolean free_right = FALSE;
9891 boolean stop_top = FALSE;
9892 boolean stop_bottom = FALSE;
9893 boolean stop_left = FALSE;
9894 boolean stop_right = FALSE;
9895 boolean new_wall = FALSE;
9897 boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9898 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9899 element == EL_EXPANDABLE_STEELWALL_ANY);
9901 boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9902 element == EL_EXPANDABLE_WALL_ANY ||
9903 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9904 element == EL_EXPANDABLE_STEELWALL_ANY);
9906 boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9907 element == EL_EXPANDABLE_WALL_ANY ||
9908 element == EL_EXPANDABLE_WALL ||
9909 element == EL_BD_EXPANDABLE_WALL ||
9910 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9911 element == EL_EXPANDABLE_STEELWALL_ANY);
9913 boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9914 element == EL_EXPANDABLE_STEELWALL_VERTICAL);
9916 boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9917 element == EL_EXPANDABLE_WALL ||
9918 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL);
9920 int wall_growing = (is_steelwall ?
9921 EL_EXPANDABLE_STEELWALL_GROWING :
9922 EL_EXPANDABLE_WALL_GROWING);
9924 int gfx_wall_growing_up = (is_steelwall ?
9925 IMG_EXPANDABLE_STEELWALL_GROWING_UP :
9926 IMG_EXPANDABLE_WALL_GROWING_UP);
9927 int gfx_wall_growing_down = (is_steelwall ?
9928 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN :
9929 IMG_EXPANDABLE_WALL_GROWING_DOWN);
9930 int gfx_wall_growing_left = (is_steelwall ?
9931 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT :
9932 IMG_EXPANDABLE_WALL_GROWING_LEFT);
9933 int gfx_wall_growing_right = (is_steelwall ?
9934 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT :
9935 IMG_EXPANDABLE_WALL_GROWING_RIGHT);
9937 if (IS_ANIMATED(graphic))
9938 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9940 if (!MovDelay[ax][ay]) // start building new wall
9941 MovDelay[ax][ay] = 6;
9943 if (MovDelay[ax][ay]) // wait some time before building new wall
9946 if (MovDelay[ax][ay])
9950 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9952 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9954 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9956 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9963 Tile[ax][ay - 1] = wall_growing;
9964 Store[ax][ay - 1] = element;
9965 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9967 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9968 DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0);
9975 Tile[ax][ay + 1] = wall_growing;
9976 Store[ax][ay + 1] = element;
9977 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9979 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9980 DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0);
9986 if (grow_horizontal)
9990 Tile[ax - 1][ay] = wall_growing;
9991 Store[ax - 1][ay] = element;
9992 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9994 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9995 DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0);
10002 Tile[ax + 1][ay] = wall_growing;
10003 Store[ax + 1][ay] = element;
10004 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
10006 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
10007 DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0);
10013 if (element == EL_EXPANDABLE_WALL && (free_left || free_right))
10014 TEST_DrawLevelField(ax, ay);
10016 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
10018 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
10019 stop_bottom = TRUE;
10020 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
10022 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
10025 if (((stop_top && stop_bottom) || stop_horizontal) &&
10026 ((stop_left && stop_right) || stop_vertical))
10027 Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL);
10030 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
10033 static void CheckForDragon(int x, int y)
10036 boolean dragon_found = FALSE;
10037 struct XY *xy = xy_topdown;
10039 for (i = 0; i < NUM_DIRECTIONS; i++)
10041 for (j = 0; j < 4; j++)
10043 int xx = x + j * xy[i].x;
10044 int yy = y + j * xy[i].y;
10046 if (IN_LEV_FIELD(xx, yy) &&
10047 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
10049 if (Tile[xx][yy] == EL_DRAGON)
10050 dragon_found = TRUE;
10059 for (i = 0; i < NUM_DIRECTIONS; i++)
10061 for (j = 0; j < 3; j++)
10063 int xx = x + j * xy[i].x;
10064 int yy = y + j * xy[i].y;
10066 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
10068 Tile[xx][yy] = EL_EMPTY;
10069 TEST_DrawLevelField(xx, yy);
10078 static void InitBuggyBase(int x, int y)
10080 int element = Tile[x][y];
10081 int activating_delay = FRAMES_PER_SECOND / 4;
10083 ChangeDelay[x][y] =
10084 (element == EL_SP_BUGGY_BASE ?
10085 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10086 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10088 element == EL_SP_BUGGY_BASE_ACTIVE ?
10089 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10092 static void WarnBuggyBase(int x, int y)
10095 struct XY *xy = xy_topdown;
10097 for (i = 0; i < NUM_DIRECTIONS; i++)
10099 int xx = x + xy[i].x;
10100 int yy = y + xy[i].y;
10102 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10104 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10111 static void InitTrap(int x, int y)
10113 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10116 static void ActivateTrap(int x, int y)
10118 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10121 static void ChangeActiveTrap(int x, int y)
10123 int graphic = IMG_TRAP_ACTIVE;
10125 // if new animation frame was drawn, correct crumbled sand border
10126 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10127 TEST_DrawLevelFieldCrumbled(x, y);
10130 static int getSpecialActionElement(int element, int number, int base_element)
10132 return (element != EL_EMPTY ? element :
10133 number != -1 ? base_element + number - 1 :
10137 static int getModifiedActionNumber(int value_old, int operator, int operand,
10138 int value_min, int value_max)
10140 int value_new = (operator == CA_MODE_SET ? operand :
10141 operator == CA_MODE_ADD ? value_old + operand :
10142 operator == CA_MODE_SUBTRACT ? value_old - operand :
10143 operator == CA_MODE_MULTIPLY ? value_old * operand :
10144 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10145 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10148 return (value_new < value_min ? value_min :
10149 value_new > value_max ? value_max :
10153 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10155 struct ElementInfo *ei = &element_info[element];
10156 struct ElementChangeInfo *change = &ei->change_page[page];
10157 int target_element = change->target_element;
10158 int action_type = change->action_type;
10159 int action_mode = change->action_mode;
10160 int action_arg = change->action_arg;
10161 int action_element = change->action_element;
10164 if (!change->has_action)
10167 // ---------- determine action paramater values -----------------------------
10169 int level_time_value =
10170 (level.time > 0 ? TimeLeft :
10173 int action_arg_element_raw =
10174 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10175 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10176 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10177 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10178 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10179 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10180 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10182 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10184 int action_arg_direction =
10185 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10186 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10187 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10188 change->actual_trigger_side :
10189 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10190 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10193 int action_arg_number_min =
10194 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10197 int action_arg_number_max =
10198 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10199 action_type == CA_SET_LEVEL_GEMS ? 999 :
10200 action_type == CA_SET_LEVEL_TIME ? 9999 :
10201 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10202 action_type == CA_SET_CE_VALUE ? 9999 :
10203 action_type == CA_SET_CE_SCORE ? 9999 :
10206 int action_arg_number_reset =
10207 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10208 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10209 action_type == CA_SET_LEVEL_TIME ? level.time :
10210 action_type == CA_SET_LEVEL_SCORE ? 0 :
10211 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10212 action_type == CA_SET_CE_SCORE ? 0 :
10215 int action_arg_number =
10216 (action_arg <= CA_ARG_MAX ? action_arg :
10217 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10218 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10219 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10220 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10221 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10222 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10223 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10224 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10225 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10226 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10227 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10228 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10229 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10230 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10231 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10232 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10233 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10234 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10235 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10236 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10237 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10240 int action_arg_number_old =
10241 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10242 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10243 action_type == CA_SET_LEVEL_SCORE ? game.score :
10244 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10245 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10248 int action_arg_number_new =
10249 getModifiedActionNumber(action_arg_number_old,
10250 action_mode, action_arg_number,
10251 action_arg_number_min, action_arg_number_max);
10253 int trigger_player_bits =
10254 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10255 change->actual_trigger_player_bits : change->trigger_player);
10257 int action_arg_player_bits =
10258 (action_arg >= CA_ARG_PLAYER_1 &&
10259 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10260 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10261 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10264 // ---------- execute action -----------------------------------------------
10266 switch (action_type)
10273 // ---------- level actions ----------------------------------------------
10275 case CA_RESTART_LEVEL:
10277 game.restart_level = TRUE;
10282 case CA_SHOW_ENVELOPE:
10284 int element = getSpecialActionElement(action_arg_element,
10285 action_arg_number, EL_ENVELOPE_1);
10287 if (IS_ENVELOPE(element))
10288 local_player->show_envelope = element;
10293 case CA_SET_LEVEL_TIME:
10295 if (level.time > 0) // only modify limited time value
10297 TimeLeft = action_arg_number_new;
10299 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10301 DisplayGameControlValues();
10303 if (!TimeLeft && game.time_limit)
10304 for (i = 0; i < MAX_PLAYERS; i++)
10305 KillPlayer(&stored_player[i]);
10311 case CA_SET_LEVEL_SCORE:
10313 game.score = action_arg_number_new;
10315 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10317 DisplayGameControlValues();
10322 case CA_SET_LEVEL_GEMS:
10324 game.gems_still_needed = action_arg_number_new;
10326 game.snapshot.collected_item = TRUE;
10328 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10330 DisplayGameControlValues();
10335 case CA_SET_LEVEL_WIND:
10337 game.wind_direction = action_arg_direction;
10342 case CA_SET_LEVEL_RANDOM_SEED:
10344 // ensure that setting a new random seed while playing is predictable
10345 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10350 // ---------- player actions ---------------------------------------------
10352 case CA_MOVE_PLAYER:
10353 case CA_MOVE_PLAYER_NEW:
10355 // automatically move to the next field in specified direction
10356 for (i = 0; i < MAX_PLAYERS; i++)
10357 if (trigger_player_bits & (1 << i))
10358 if (action_type == CA_MOVE_PLAYER ||
10359 stored_player[i].MovPos == 0)
10360 stored_player[i].programmed_action = action_arg_direction;
10365 case CA_EXIT_PLAYER:
10367 for (i = 0; i < MAX_PLAYERS; i++)
10368 if (action_arg_player_bits & (1 << i))
10369 ExitPlayer(&stored_player[i]);
10371 if (game.players_still_needed == 0)
10377 case CA_KILL_PLAYER:
10379 for (i = 0; i < MAX_PLAYERS; i++)
10380 if (action_arg_player_bits & (1 << i))
10381 KillPlayer(&stored_player[i]);
10386 case CA_SET_PLAYER_KEYS:
10388 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10389 int element = getSpecialActionElement(action_arg_element,
10390 action_arg_number, EL_KEY_1);
10392 if (IS_KEY(element))
10394 for (i = 0; i < MAX_PLAYERS; i++)
10396 if (trigger_player_bits & (1 << i))
10398 stored_player[i].key[KEY_NR(element)] = key_state;
10400 DrawGameDoorValues();
10408 case CA_SET_PLAYER_SPEED:
10410 for (i = 0; i < MAX_PLAYERS; i++)
10412 if (trigger_player_bits & (1 << i))
10414 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10416 if (action_arg == CA_ARG_SPEED_FASTER &&
10417 stored_player[i].cannot_move)
10419 action_arg_number = STEPSIZE_VERY_SLOW;
10421 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10422 action_arg == CA_ARG_SPEED_FASTER)
10424 action_arg_number = 2;
10425 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10428 else if (action_arg == CA_ARG_NUMBER_RESET)
10430 action_arg_number = level.initial_player_stepsize[i];
10434 getModifiedActionNumber(move_stepsize,
10437 action_arg_number_min,
10438 action_arg_number_max);
10440 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10447 case CA_SET_PLAYER_SHIELD:
10449 for (i = 0; i < MAX_PLAYERS; i++)
10451 if (trigger_player_bits & (1 << i))
10453 if (action_arg == CA_ARG_SHIELD_OFF)
10455 stored_player[i].shield_normal_time_left = 0;
10456 stored_player[i].shield_deadly_time_left = 0;
10458 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10460 stored_player[i].shield_normal_time_left = 999999;
10462 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10464 stored_player[i].shield_normal_time_left = 999999;
10465 stored_player[i].shield_deadly_time_left = 999999;
10473 case CA_SET_PLAYER_GRAVITY:
10475 for (i = 0; i < MAX_PLAYERS; i++)
10477 if (trigger_player_bits & (1 << i))
10479 stored_player[i].gravity =
10480 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10481 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10482 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10483 stored_player[i].gravity);
10490 case CA_SET_PLAYER_ARTWORK:
10492 for (i = 0; i < MAX_PLAYERS; i++)
10494 if (trigger_player_bits & (1 << i))
10496 int artwork_element = action_arg_element;
10498 if (action_arg == CA_ARG_ELEMENT_RESET)
10500 (level.use_artwork_element[i] ? level.artwork_element[i] :
10501 stored_player[i].element_nr);
10503 if (stored_player[i].artwork_element != artwork_element)
10504 stored_player[i].Frame = 0;
10506 stored_player[i].artwork_element = artwork_element;
10508 SetPlayerWaiting(&stored_player[i], FALSE);
10510 // set number of special actions for bored and sleeping animation
10511 stored_player[i].num_special_action_bored =
10512 get_num_special_action(artwork_element,
10513 ACTION_BORING_1, ACTION_BORING_LAST);
10514 stored_player[i].num_special_action_sleeping =
10515 get_num_special_action(artwork_element,
10516 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10523 case CA_SET_PLAYER_INVENTORY:
10525 for (i = 0; i < MAX_PLAYERS; i++)
10527 struct PlayerInfo *player = &stored_player[i];
10530 if (trigger_player_bits & (1 << i))
10532 int inventory_element = action_arg_element;
10534 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10535 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10536 action_arg == CA_ARG_ELEMENT_ACTION)
10538 int element = inventory_element;
10539 int collect_count = element_info[element].collect_count_initial;
10541 if (!IS_CUSTOM_ELEMENT(element))
10544 if (collect_count == 0)
10545 player->inventory_infinite_element = element;
10547 for (k = 0; k < collect_count; k++)
10548 if (player->inventory_size < MAX_INVENTORY_SIZE)
10549 player->inventory_element[player->inventory_size++] =
10552 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10553 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10554 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10556 if (player->inventory_infinite_element != EL_UNDEFINED &&
10557 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10558 action_arg_element_raw))
10559 player->inventory_infinite_element = EL_UNDEFINED;
10561 for (k = 0, j = 0; j < player->inventory_size; j++)
10563 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10564 action_arg_element_raw))
10565 player->inventory_element[k++] = player->inventory_element[j];
10568 player->inventory_size = k;
10570 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10572 if (player->inventory_size > 0)
10574 for (j = 0; j < player->inventory_size - 1; j++)
10575 player->inventory_element[j] = player->inventory_element[j + 1];
10577 player->inventory_size--;
10580 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10582 if (player->inventory_size > 0)
10583 player->inventory_size--;
10585 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10587 player->inventory_infinite_element = EL_UNDEFINED;
10588 player->inventory_size = 0;
10590 else if (action_arg == CA_ARG_INVENTORY_RESET)
10592 player->inventory_infinite_element = EL_UNDEFINED;
10593 player->inventory_size = 0;
10595 if (level.use_initial_inventory[i])
10597 for (j = 0; j < level.initial_inventory_size[i]; j++)
10599 int element = level.initial_inventory_content[i][j];
10600 int collect_count = element_info[element].collect_count_initial;
10602 if (!IS_CUSTOM_ELEMENT(element))
10605 if (collect_count == 0)
10606 player->inventory_infinite_element = element;
10608 for (k = 0; k < collect_count; k++)
10609 if (player->inventory_size < MAX_INVENTORY_SIZE)
10610 player->inventory_element[player->inventory_size++] =
10621 // ---------- CE actions -------------------------------------------------
10623 case CA_SET_CE_VALUE:
10625 int last_ce_value = CustomValue[x][y];
10627 CustomValue[x][y] = action_arg_number_new;
10629 if (CustomValue[x][y] != last_ce_value)
10631 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10632 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10634 if (CustomValue[x][y] == 0)
10636 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10637 ChangeCount[x][y] = 0; // allow at least one more change
10639 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10640 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10647 case CA_SET_CE_SCORE:
10649 int last_ce_score = ei->collect_score;
10651 ei->collect_score = action_arg_number_new;
10653 if (ei->collect_score != last_ce_score)
10655 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10656 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10658 if (ei->collect_score == 0)
10662 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10663 ChangeCount[x][y] = 0; // allow at least one more change
10665 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10666 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10669 This is a very special case that seems to be a mixture between
10670 CheckElementChange() and CheckTriggeredElementChange(): while
10671 the first one only affects single elements that are triggered
10672 directly, the second one affects multiple elements in the playfield
10673 that are triggered indirectly by another element. This is a third
10674 case: Changing the CE score always affects multiple identical CEs,
10675 so every affected CE must be checked, not only the single CE for
10676 which the CE score was changed in the first place (as every instance
10677 of that CE shares the same CE score, and therefore also can change)!
10679 SCAN_PLAYFIELD(xx, yy)
10681 if (Tile[xx][yy] == element)
10682 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10683 CE_SCORE_GETS_ZERO);
10691 case CA_SET_CE_ARTWORK:
10693 int artwork_element = action_arg_element;
10694 boolean reset_frame = FALSE;
10697 if (action_arg == CA_ARG_ELEMENT_RESET)
10698 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10701 if (ei->gfx_element != artwork_element)
10702 reset_frame = TRUE;
10704 ei->gfx_element = artwork_element;
10706 SCAN_PLAYFIELD(xx, yy)
10708 if (Tile[xx][yy] == element)
10712 ResetGfxAnimation(xx, yy);
10713 ResetRandomAnimationValue(xx, yy);
10716 TEST_DrawLevelField(xx, yy);
10723 // ---------- engine actions ---------------------------------------------
10725 case CA_SET_ENGINE_SCAN_MODE:
10727 InitPlayfieldScanMode(action_arg);
10737 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10739 int old_element = Tile[x][y];
10740 int new_element = GetElementFromGroupElement(element);
10741 int previous_move_direction = MovDir[x][y];
10742 int last_ce_value = CustomValue[x][y];
10743 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10744 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10745 boolean add_player_onto_element = (new_element_is_player &&
10746 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10747 IS_WALKABLE(old_element));
10749 if (!add_player_onto_element)
10751 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10752 RemoveMovingField(x, y);
10756 Tile[x][y] = new_element;
10758 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10759 MovDir[x][y] = previous_move_direction;
10761 if (element_info[new_element].use_last_ce_value)
10762 CustomValue[x][y] = last_ce_value;
10764 InitField_WithBug1(x, y, FALSE);
10766 new_element = Tile[x][y]; // element may have changed
10768 ResetGfxAnimation(x, y);
10769 ResetRandomAnimationValue(x, y);
10771 TEST_DrawLevelField(x, y);
10773 if (GFX_CRUMBLED(new_element))
10774 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10776 if (old_element == EL_EXPLOSION)
10778 Store[x][y] = Store2[x][y] = 0;
10780 // check if new element replaces an exploding player, requiring cleanup
10781 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
10782 StorePlayer[x][y] = 0;
10785 // check if element under the player changes from accessible to unaccessible
10786 // (needed for special case of dropping element which then changes)
10787 // (must be checked after creating new element for walkable group elements)
10788 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10789 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10791 KillPlayer(PLAYERINFO(x, y));
10797 // "ChangeCount" not set yet to allow "entered by player" change one time
10798 if (new_element_is_player)
10799 RelocatePlayer(x, y, new_element);
10802 ChangeCount[x][y]++; // count number of changes in the same frame
10804 TestIfBadThingTouchesPlayer(x, y);
10805 TestIfPlayerTouchesCustomElement(x, y);
10806 TestIfElementTouchesCustomElement(x, y);
10809 static void CreateField(int x, int y, int element)
10811 CreateFieldExt(x, y, element, FALSE);
10814 static void CreateElementFromChange(int x, int y, int element)
10816 element = GET_VALID_RUNTIME_ELEMENT(element);
10818 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10820 int old_element = Tile[x][y];
10822 // prevent changed element from moving in same engine frame
10823 // unless both old and new element can either fall or move
10824 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10825 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10829 CreateFieldExt(x, y, element, TRUE);
10832 static boolean ChangeElement(int x, int y, int element, int page)
10834 struct ElementInfo *ei = &element_info[element];
10835 struct ElementChangeInfo *change = &ei->change_page[page];
10836 int ce_value = CustomValue[x][y];
10837 int ce_score = ei->collect_score;
10838 int target_element;
10839 int old_element = Tile[x][y];
10841 // always use default change event to prevent running into a loop
10842 if (ChangeEvent[x][y] == -1)
10843 ChangeEvent[x][y] = CE_DELAY;
10845 if (ChangeEvent[x][y] == CE_DELAY)
10847 // reset actual trigger element, trigger player and action element
10848 change->actual_trigger_element = EL_EMPTY;
10849 change->actual_trigger_player = EL_EMPTY;
10850 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10851 change->actual_trigger_side = CH_SIDE_NONE;
10852 change->actual_trigger_ce_value = 0;
10853 change->actual_trigger_ce_score = 0;
10854 change->actual_trigger_x = -1;
10855 change->actual_trigger_y = -1;
10858 // do not change elements more than a specified maximum number of changes
10859 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10862 ChangeCount[x][y]++; // count number of changes in the same frame
10864 if (ei->has_anim_event)
10865 HandleGlobalAnimEventByElementChange(element, page, x, y,
10866 change->actual_trigger_x,
10867 change->actual_trigger_y);
10869 if (change->explode)
10876 if (change->use_target_content)
10878 boolean complete_replace = TRUE;
10879 boolean can_replace[3][3];
10882 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10885 boolean is_walkable;
10886 boolean is_diggable;
10887 boolean is_collectible;
10888 boolean is_removable;
10889 boolean is_destructible;
10890 int ex = x + xx - 1;
10891 int ey = y + yy - 1;
10892 int content_element = change->target_content.e[xx][yy];
10895 can_replace[xx][yy] = TRUE;
10897 if (ex == x && ey == y) // do not check changing element itself
10900 if (content_element == EL_EMPTY_SPACE)
10902 can_replace[xx][yy] = FALSE; // do not replace border with space
10907 if (!IN_LEV_FIELD(ex, ey))
10909 can_replace[xx][yy] = FALSE;
10910 complete_replace = FALSE;
10917 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10918 e = MovingOrBlocked2Element(ex, ey);
10920 is_empty = (IS_FREE(ex, ey) ||
10921 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10923 is_walkable = (is_empty || IS_WALKABLE(e));
10924 is_diggable = (is_empty || IS_DIGGABLE(e));
10925 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10926 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10927 is_removable = (is_diggable || is_collectible);
10929 can_replace[xx][yy] =
10930 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10931 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10932 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10933 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10934 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10935 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10936 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10938 if (!can_replace[xx][yy])
10939 complete_replace = FALSE;
10942 if (!change->only_if_complete || complete_replace)
10944 boolean something_has_changed = FALSE;
10946 if (change->only_if_complete && change->use_random_replace &&
10947 RND(100) < change->random_percentage)
10950 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10952 int ex = x + xx - 1;
10953 int ey = y + yy - 1;
10954 int content_element;
10956 if (can_replace[xx][yy] && (!change->use_random_replace ||
10957 RND(100) < change->random_percentage))
10959 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10960 RemoveMovingField(ex, ey);
10962 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10964 content_element = change->target_content.e[xx][yy];
10965 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10966 ce_value, ce_score);
10968 CreateElementFromChange(ex, ey, target_element);
10970 something_has_changed = TRUE;
10972 // for symmetry reasons, freeze newly created border elements
10973 if (ex != x || ey != y)
10974 Stop[ex][ey] = TRUE; // no more moving in this frame
10978 if (something_has_changed)
10980 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10981 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10987 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10988 ce_value, ce_score);
10990 if (element == EL_DIAGONAL_GROWING ||
10991 element == EL_DIAGONAL_SHRINKING)
10993 target_element = Store[x][y];
10995 Store[x][y] = EL_EMPTY;
10998 // special case: element changes to player (and may be kept if walkable)
10999 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
11000 CreateElementFromChange(x, y, EL_EMPTY);
11002 CreateElementFromChange(x, y, target_element);
11004 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
11005 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
11008 // this uses direct change before indirect change
11009 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
11014 static void HandleElementChange(int x, int y, int page)
11016 int element = MovingOrBlocked2Element(x, y);
11017 struct ElementInfo *ei = &element_info[element];
11018 struct ElementChangeInfo *change = &ei->change_page[page];
11019 boolean handle_action_before_change = FALSE;
11022 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
11023 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
11025 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
11026 x, y, element, element_info[element].token_name);
11027 Debug("game:playing:HandleElementChange", "This should never happen!");
11031 // this can happen with classic bombs on walkable, changing elements
11032 if (!CAN_CHANGE_OR_HAS_ACTION(element))
11037 if (ChangeDelay[x][y] == 0) // initialize element change
11039 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
11041 if (change->can_change)
11043 // !!! not clear why graphic animation should be reset at all here !!!
11044 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
11045 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
11048 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
11050 When using an animation frame delay of 1 (this only happens with
11051 "sp_zonk.moving.left/right" in the classic graphics), the default
11052 (non-moving) animation shows wrong animation frames (while the
11053 moving animation, like "sp_zonk.moving.left/right", is correct,
11054 so this graphical bug never shows up with the classic graphics).
11055 For an animation with 4 frames, this causes wrong frames 0,0,1,2
11056 be drawn instead of the correct frames 0,1,2,3. This is caused by
11057 "GfxFrame[][]" being reset *twice* (in two successive frames) after
11058 an element change: First when the change delay ("ChangeDelay[][]")
11059 counter has reached zero after decrementing, then a second time in
11060 the next frame (after "GfxFrame[][]" was already incremented) when
11061 "ChangeDelay[][]" is reset to the initial delay value again.
11063 This causes frame 0 to be drawn twice, while the last frame won't
11064 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
11066 As some animations may already be cleverly designed around this bug
11067 (at least the "Snake Bite" snake tail animation does this), it cannot
11068 simply be fixed here without breaking such existing animations.
11069 Unfortunately, it cannot easily be detected if a graphics set was
11070 designed "before" or "after" the bug was fixed. As a workaround,
11071 a new graphics set option "game.graphics_engine_version" was added
11072 to be able to specify the game's major release version for which the
11073 graphics set was designed, which can then be used to decide if the
11074 bugfix should be used (version 4 and above) or not (version 3 or
11075 below, or if no version was specified at all, as with old sets).
11077 (The wrong/fixed animation frames can be tested with the test level set
11078 "test_gfxframe" and level "000", which contains a specially prepared
11079 custom element at level position (x/y) == (11/9) which uses the zonk
11080 animation mentioned above. Using "game.graphics_engine_version: 4"
11081 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
11082 This can also be seen from the debug output for this test element.)
11085 // when a custom element is about to change (for example by change delay),
11086 // do not reset graphic animation when the custom element is moving
11087 if (game.graphics_engine_version < 4 &&
11090 ResetGfxAnimation(x, y);
11091 ResetRandomAnimationValue(x, y);
11094 if (change->pre_change_function)
11095 change->pre_change_function(x, y);
11099 ChangeDelay[x][y]--;
11101 if (ChangeDelay[x][y] != 0) // continue element change
11103 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11105 // also needed if CE can not change, but has CE delay with CE action
11106 if (IS_ANIMATED(graphic))
11107 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11109 if (change->can_change)
11111 if (change->change_function)
11112 change->change_function(x, y);
11115 else // finish element change
11117 if (ChangePage[x][y] != -1) // remember page from delayed change
11119 page = ChangePage[x][y];
11120 ChangePage[x][y] = -1;
11122 change = &ei->change_page[page];
11125 if (IS_MOVING(x, y)) // never change a running system ;-)
11127 ChangeDelay[x][y] = 1; // try change after next move step
11128 ChangePage[x][y] = page; // remember page to use for change
11133 // special case: set new level random seed before changing element
11134 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11135 handle_action_before_change = TRUE;
11137 if (change->has_action && handle_action_before_change)
11138 ExecuteCustomElementAction(x, y, element, page);
11140 if (change->can_change)
11142 if (ChangeElement(x, y, element, page))
11144 if (change->post_change_function)
11145 change->post_change_function(x, y);
11149 if (change->has_action && !handle_action_before_change)
11150 ExecuteCustomElementAction(x, y, element, page);
11154 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11155 int trigger_element,
11157 int trigger_player,
11161 boolean change_done_any = FALSE;
11162 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11165 if (!(trigger_events[trigger_element][trigger_event]))
11168 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11170 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11172 int element = EL_CUSTOM_START + i;
11173 boolean change_done = FALSE;
11176 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11177 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11180 for (p = 0; p < element_info[element].num_change_pages; p++)
11182 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11184 if (change->can_change_or_has_action &&
11185 change->has_event[trigger_event] &&
11186 change->trigger_side & trigger_side &&
11187 change->trigger_player & trigger_player &&
11188 change->trigger_page & trigger_page_bits &&
11189 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11191 change->actual_trigger_element = trigger_element;
11192 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11193 change->actual_trigger_player_bits = trigger_player;
11194 change->actual_trigger_side = trigger_side;
11195 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11196 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11197 change->actual_trigger_x = trigger_x;
11198 change->actual_trigger_y = trigger_y;
11200 if ((change->can_change && !change_done) || change->has_action)
11204 SCAN_PLAYFIELD(x, y)
11206 if (Tile[x][y] == element)
11208 if (change->can_change && !change_done)
11210 // if element already changed in this frame, not only prevent
11211 // another element change (checked in ChangeElement()), but
11212 // also prevent additional element actions for this element
11214 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11215 !level.use_action_after_change_bug)
11218 ChangeDelay[x][y] = 1;
11219 ChangeEvent[x][y] = trigger_event;
11221 HandleElementChange(x, y, p);
11223 else if (change->has_action)
11225 // if element already changed in this frame, not only prevent
11226 // another element change (checked in ChangeElement()), but
11227 // also prevent additional element actions for this element
11229 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11230 !level.use_action_after_change_bug)
11233 ExecuteCustomElementAction(x, y, element, p);
11234 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11239 if (change->can_change)
11241 change_done = TRUE;
11242 change_done_any = TRUE;
11249 RECURSION_LOOP_DETECTION_END();
11251 return change_done_any;
11254 static boolean CheckElementChangeExt(int x, int y,
11256 int trigger_element,
11258 int trigger_player,
11261 boolean change_done = FALSE;
11264 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11265 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11268 if (Tile[x][y] == EL_BLOCKED)
11270 Blocked2Moving(x, y, &x, &y);
11271 element = Tile[x][y];
11274 // check if element has already changed or is about to change after moving
11275 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11276 Tile[x][y] != element) ||
11278 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11279 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11280 ChangePage[x][y] != -1)))
11283 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11285 for (p = 0; p < element_info[element].num_change_pages; p++)
11287 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11289 /* check trigger element for all events where the element that is checked
11290 for changing interacts with a directly adjacent element -- this is
11291 different to element changes that affect other elements to change on the
11292 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11293 boolean check_trigger_element =
11294 (trigger_event == CE_NEXT_TO_X ||
11295 trigger_event == CE_TOUCHING_X ||
11296 trigger_event == CE_HITTING_X ||
11297 trigger_event == CE_HIT_BY_X ||
11298 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11300 if (change->can_change_or_has_action &&
11301 change->has_event[trigger_event] &&
11302 change->trigger_side & trigger_side &&
11303 change->trigger_player & trigger_player &&
11304 (!check_trigger_element ||
11305 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11307 change->actual_trigger_element = trigger_element;
11308 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11309 change->actual_trigger_player_bits = trigger_player;
11310 change->actual_trigger_side = trigger_side;
11311 change->actual_trigger_ce_value = CustomValue[x][y];
11312 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11313 change->actual_trigger_x = x;
11314 change->actual_trigger_y = y;
11316 // special case: trigger element not at (x,y) position for some events
11317 if (check_trigger_element)
11329 { 0, 0 }, { 0, 0 }, { 0, 0 },
11333 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11334 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11336 change->actual_trigger_ce_value = CustomValue[xx][yy];
11337 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11338 change->actual_trigger_x = xx;
11339 change->actual_trigger_y = yy;
11342 if (change->can_change && !change_done)
11344 ChangeDelay[x][y] = 1;
11345 ChangeEvent[x][y] = trigger_event;
11347 HandleElementChange(x, y, p);
11349 change_done = TRUE;
11351 else if (change->has_action)
11353 ExecuteCustomElementAction(x, y, element, p);
11354 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11359 RECURSION_LOOP_DETECTION_END();
11361 return change_done;
11364 static void PlayPlayerSound(struct PlayerInfo *player)
11366 int jx = player->jx, jy = player->jy;
11367 int sound_element = player->artwork_element;
11368 int last_action = player->last_action_waiting;
11369 int action = player->action_waiting;
11371 if (player->is_waiting)
11373 if (action != last_action)
11374 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11376 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11380 if (action != last_action)
11381 StopSound(element_info[sound_element].sound[last_action]);
11383 if (last_action == ACTION_SLEEPING)
11384 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11388 static void PlayAllPlayersSound(void)
11392 for (i = 0; i < MAX_PLAYERS; i++)
11393 if (stored_player[i].active)
11394 PlayPlayerSound(&stored_player[i]);
11397 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11399 boolean last_waiting = player->is_waiting;
11400 int move_dir = player->MovDir;
11402 player->dir_waiting = move_dir;
11403 player->last_action_waiting = player->action_waiting;
11407 if (!last_waiting) // not waiting -> waiting
11409 player->is_waiting = TRUE;
11411 player->frame_counter_bored =
11413 game.player_boring_delay_fixed +
11414 GetSimpleRandom(game.player_boring_delay_random);
11415 player->frame_counter_sleeping =
11417 game.player_sleeping_delay_fixed +
11418 GetSimpleRandom(game.player_sleeping_delay_random);
11420 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11423 if (game.player_sleeping_delay_fixed +
11424 game.player_sleeping_delay_random > 0 &&
11425 player->anim_delay_counter == 0 &&
11426 player->post_delay_counter == 0 &&
11427 FrameCounter >= player->frame_counter_sleeping)
11428 player->is_sleeping = TRUE;
11429 else if (game.player_boring_delay_fixed +
11430 game.player_boring_delay_random > 0 &&
11431 FrameCounter >= player->frame_counter_bored)
11432 player->is_bored = TRUE;
11434 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11435 player->is_bored ? ACTION_BORING :
11438 if (player->is_sleeping && player->use_murphy)
11440 // special case for sleeping Murphy when leaning against non-free tile
11442 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11443 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11444 !IS_MOVING(player->jx - 1, player->jy)))
11445 move_dir = MV_LEFT;
11446 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11447 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11448 !IS_MOVING(player->jx + 1, player->jy)))
11449 move_dir = MV_RIGHT;
11451 player->is_sleeping = FALSE;
11453 player->dir_waiting = move_dir;
11456 if (player->is_sleeping)
11458 if (player->num_special_action_sleeping > 0)
11460 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11462 int last_special_action = player->special_action_sleeping;
11463 int num_special_action = player->num_special_action_sleeping;
11464 int special_action =
11465 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11466 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11467 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11468 last_special_action + 1 : ACTION_SLEEPING);
11469 int special_graphic =
11470 el_act_dir2img(player->artwork_element, special_action, move_dir);
11472 player->anim_delay_counter =
11473 graphic_info[special_graphic].anim_delay_fixed +
11474 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11475 player->post_delay_counter =
11476 graphic_info[special_graphic].post_delay_fixed +
11477 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11479 player->special_action_sleeping = special_action;
11482 if (player->anim_delay_counter > 0)
11484 player->action_waiting = player->special_action_sleeping;
11485 player->anim_delay_counter--;
11487 else if (player->post_delay_counter > 0)
11489 player->post_delay_counter--;
11493 else if (player->is_bored)
11495 if (player->num_special_action_bored > 0)
11497 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11499 int special_action =
11500 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11501 int special_graphic =
11502 el_act_dir2img(player->artwork_element, special_action, move_dir);
11504 player->anim_delay_counter =
11505 graphic_info[special_graphic].anim_delay_fixed +
11506 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11507 player->post_delay_counter =
11508 graphic_info[special_graphic].post_delay_fixed +
11509 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11511 player->special_action_bored = special_action;
11514 if (player->anim_delay_counter > 0)
11516 player->action_waiting = player->special_action_bored;
11517 player->anim_delay_counter--;
11519 else if (player->post_delay_counter > 0)
11521 player->post_delay_counter--;
11526 else if (last_waiting) // waiting -> not waiting
11528 player->is_waiting = FALSE;
11529 player->is_bored = FALSE;
11530 player->is_sleeping = FALSE;
11532 player->frame_counter_bored = -1;
11533 player->frame_counter_sleeping = -1;
11535 player->anim_delay_counter = 0;
11536 player->post_delay_counter = 0;
11538 player->dir_waiting = player->MovDir;
11539 player->action_waiting = ACTION_DEFAULT;
11541 player->special_action_bored = ACTION_DEFAULT;
11542 player->special_action_sleeping = ACTION_DEFAULT;
11546 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11548 if ((!player->is_moving && player->was_moving) ||
11549 (player->MovPos == 0 && player->was_moving) ||
11550 (player->is_snapping && !player->was_snapping) ||
11551 (player->is_dropping && !player->was_dropping))
11553 if (!CheckSaveEngineSnapshotToList())
11556 player->was_moving = FALSE;
11557 player->was_snapping = TRUE;
11558 player->was_dropping = TRUE;
11562 if (player->is_moving)
11563 player->was_moving = TRUE;
11565 if (!player->is_snapping)
11566 player->was_snapping = FALSE;
11568 if (!player->is_dropping)
11569 player->was_dropping = FALSE;
11572 static struct MouseActionInfo mouse_action_last = { 0 };
11573 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11574 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11577 CheckSaveEngineSnapshotToList();
11579 mouse_action_last = mouse_action;
11582 static void CheckSingleStepMode(struct PlayerInfo *player)
11584 if (tape.single_step && tape.recording && !tape.pausing)
11586 // as it is called "single step mode", just return to pause mode when the
11587 // player stopped moving after one tile (or never starts moving at all)
11588 // (reverse logic needed here in case single step mode used in team mode)
11589 if (player->is_moving ||
11590 player->is_pushing ||
11591 player->is_dropping_pressed ||
11592 player->effective_mouse_action.button)
11593 game.enter_single_step_mode = FALSE;
11596 CheckSaveEngineSnapshot(player);
11599 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11601 int left = player_action & JOY_LEFT;
11602 int right = player_action & JOY_RIGHT;
11603 int up = player_action & JOY_UP;
11604 int down = player_action & JOY_DOWN;
11605 int button1 = player_action & JOY_BUTTON_1;
11606 int button2 = player_action & JOY_BUTTON_2;
11607 int dx = (left ? -1 : right ? 1 : 0);
11608 int dy = (up ? -1 : down ? 1 : 0);
11610 if (!player->active || tape.pausing)
11616 SnapField(player, dx, dy);
11620 DropElement(player);
11622 MovePlayer(player, dx, dy);
11625 CheckSingleStepMode(player);
11627 SetPlayerWaiting(player, FALSE);
11629 return player_action;
11633 // no actions for this player (no input at player's configured device)
11635 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11636 SnapField(player, 0, 0);
11637 CheckGravityMovementWhenNotMoving(player);
11639 if (player->MovPos == 0)
11640 SetPlayerWaiting(player, TRUE);
11642 if (player->MovPos == 0) // needed for tape.playing
11643 player->is_moving = FALSE;
11645 player->is_dropping = FALSE;
11646 player->is_dropping_pressed = FALSE;
11647 player->drop_pressed_delay = 0;
11649 CheckSingleStepMode(player);
11655 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11658 if (!tape.use_mouse_actions)
11661 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11662 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11663 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11666 static void SetTapeActionFromMouseAction(byte *tape_action,
11667 struct MouseActionInfo *mouse_action)
11669 if (!tape.use_mouse_actions)
11672 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11673 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11674 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11677 static void CheckLevelSolved(void)
11679 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11681 if (game_bd.level_solved &&
11682 !game_bd.game_over) // game won
11686 game_bd.game_over = TRUE;
11688 game.all_players_gone = TRUE;
11691 if (game_bd.game_over) // game lost
11692 game.all_players_gone = TRUE;
11694 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11696 if (game_em.level_solved &&
11697 !game_em.game_over) // game won
11701 game_em.game_over = TRUE;
11703 game.all_players_gone = TRUE;
11706 if (game_em.game_over) // game lost
11707 game.all_players_gone = TRUE;
11709 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11711 if (game_sp.level_solved &&
11712 !game_sp.game_over) // game won
11716 game_sp.game_over = TRUE;
11718 game.all_players_gone = TRUE;
11721 if (game_sp.game_over) // game lost
11722 game.all_players_gone = TRUE;
11724 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11726 if (game_mm.level_solved &&
11727 !game_mm.game_over) // game won
11731 game_mm.game_over = TRUE;
11733 game.all_players_gone = TRUE;
11736 if (game_mm.game_over) // game lost
11737 game.all_players_gone = TRUE;
11741 static void PlayTimeoutSound(int seconds_left)
11743 // will be played directly by BD engine (for classic bonus time sounds)
11744 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && checkBonusTime_BD())
11747 // try to use individual "running out of time" sound for each second left
11748 int sound = SND_GAME_RUNNING_OUT_OF_TIME_0 - seconds_left;
11750 // if special sound per second not defined, use default sound
11751 if (getSoundInfoEntryFilename(sound) == NULL)
11752 sound = SND_GAME_RUNNING_OUT_OF_TIME;
11754 // if out of time, but player still alive, play special "timeout" sound, if defined
11755 if (seconds_left == 0 && !checkGameFailed())
11756 if (getSoundInfoEntryFilename(SND_GAME_TIMEOUT) != NULL)
11757 sound = SND_GAME_TIMEOUT;
11762 static void CheckLevelTime_StepCounter(void)
11772 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11773 PlayTimeoutSound(TimeLeft);
11775 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11777 DisplayGameControlValues();
11779 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11780 for (i = 0; i < MAX_PLAYERS; i++)
11781 KillPlayer(&stored_player[i]);
11783 else if (game.no_level_time_limit && !game.all_players_gone)
11785 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11787 DisplayGameControlValues();
11791 static void CheckLevelTime(void)
11793 int frames_per_second = FRAMES_PER_SECOND;
11796 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11798 // level time may be running slower in native BD engine
11799 frames_per_second = getFramesPerSecond_BD();
11801 // if native engine time changed, force main engine time change
11802 if (getTimeLeft_BD() < TimeLeft)
11803 TimeFrames = frames_per_second;
11805 // if last second running, wait for native engine time to exactly reach zero
11806 if (getTimeLeft_BD() == 1 && TimeLeft == 1)
11807 TimeFrames = frames_per_second - 1;
11810 if (TimeFrames >= frames_per_second)
11814 for (i = 0; i < MAX_PLAYERS; i++)
11816 struct PlayerInfo *player = &stored_player[i];
11818 if (SHIELD_ON(player))
11820 player->shield_normal_time_left--;
11822 if (player->shield_deadly_time_left > 0)
11823 player->shield_deadly_time_left--;
11827 if (!game.LevelSolved && !level.use_step_counter)
11835 if (TimeLeft <= 10 && game.time_limit)
11836 PlayTimeoutSound(TimeLeft);
11838 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11839 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11841 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11843 if (!TimeLeft && game.time_limit)
11845 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
11847 if (game_bd.game->cave->player_state == GD_PL_LIVING)
11848 game_bd.game->cave->player_state = GD_PL_TIMEOUT;
11850 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11852 game_em.lev->killed_out_of_time = TRUE;
11856 for (i = 0; i < MAX_PLAYERS; i++)
11857 KillPlayer(&stored_player[i]);
11861 else if (game.no_level_time_limit && !game.all_players_gone)
11863 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11866 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11870 if (TapeTimeFrames >= FRAMES_PER_SECOND)
11872 TapeTimeFrames = 0;
11875 if (tape.recording || tape.playing)
11876 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11879 if (tape.recording || tape.playing)
11880 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11882 UpdateAndDisplayGameControlValues();
11885 void AdvanceFrameAndPlayerCounters(int player_nr)
11889 // handle game and tape time differently for native BD game engine
11891 // tape time is running in native BD engine even if player is not hatched yet
11892 if (!checkGameRunning())
11895 // advance frame counters (global frame counter and tape time frame counter)
11899 // level time is running in native BD engine after player is being hatched
11900 if (!checkGamePlaying())
11903 // advance time frame counter (used to control available time to solve level)
11906 // advance player counters (counters for move delay, move animation etc.)
11907 for (i = 0; i < MAX_PLAYERS; i++)
11909 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11910 int move_delay_value = stored_player[i].move_delay_value;
11911 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11913 if (!advance_player_counters) // not all players may be affected
11916 if (move_frames == 0) // less than one move per game frame
11918 int stepsize = TILEX / move_delay_value;
11919 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11920 int count = (stored_player[i].is_moving ?
11921 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11923 if (count % delay == 0)
11927 stored_player[i].Frame += move_frames;
11929 if (stored_player[i].MovPos != 0)
11930 stored_player[i].StepFrame += move_frames;
11932 if (stored_player[i].move_delay > 0)
11933 stored_player[i].move_delay--;
11935 // due to bugs in previous versions, counter must count up, not down
11936 if (stored_player[i].push_delay != -1)
11937 stored_player[i].push_delay++;
11939 if (stored_player[i].drop_delay > 0)
11940 stored_player[i].drop_delay--;
11942 if (stored_player[i].is_dropping_pressed)
11943 stored_player[i].drop_pressed_delay++;
11947 void AdvanceFrameCounter(void)
11952 void AdvanceGfxFrame(void)
11956 SCAN_PLAYFIELD(x, y)
11962 static void HandleMouseAction(struct MouseActionInfo *mouse_action,
11963 struct MouseActionInfo *mouse_action_last)
11965 if (mouse_action->button)
11967 int new_button = (mouse_action->button && mouse_action_last->button == 0);
11968 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button);
11969 int x = mouse_action->lx;
11970 int y = mouse_action->ly;
11971 int element = Tile[x][y];
11975 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
11976 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
11980 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
11981 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
11984 if (level.use_step_counter)
11986 boolean counted_click = FALSE;
11988 // element clicked that can change when clicked/pressed
11989 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
11990 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
11991 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
11992 counted_click = TRUE;
11994 // element clicked that can trigger change when clicked/pressed
11995 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
11996 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
11997 counted_click = TRUE;
11999 if (new_button && counted_click)
12000 CheckLevelTime_StepCounter();
12005 void StartGameActions(boolean init_network_game, boolean record_tape,
12008 unsigned int new_random_seed = InitRND(random_seed);
12011 TapeStartRecording(new_random_seed);
12013 if (setup.auto_pause_on_start && !tape.pausing)
12014 TapeTogglePause(TAPE_TOGGLE_MANUAL);
12016 if (init_network_game)
12018 SendToServer_LevelFile();
12019 SendToServer_StartPlaying();
12027 static void GameActionsExt(void)
12030 static unsigned int game_frame_delay = 0;
12032 unsigned int game_frame_delay_value;
12033 byte *recorded_player_action;
12034 byte summarized_player_action = 0;
12035 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
12038 // detect endless loops, caused by custom element programming
12039 if (recursion_loop_detected && recursion_loop_depth == 0)
12041 char *message = getStringCat3("Internal Error! Element ",
12042 EL_NAME(recursion_loop_element),
12043 " caused endless loop! Quit the game?");
12045 Warn("element '%s' caused endless loop in game engine",
12046 EL_NAME(recursion_loop_element));
12048 RequestQuitGameExt(program.headless, level_editor_test_game, message);
12050 recursion_loop_detected = FALSE; // if game should be continued
12057 if (game.restart_level)
12058 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
12060 CheckLevelSolved();
12062 if (game.LevelSolved && !game.LevelSolved_GameEnd)
12065 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
12068 if (game_status != GAME_MODE_PLAYING) // status might have changed
12071 game_frame_delay_value =
12072 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
12074 if (tape.playing && tape.warp_forward && !tape.pausing)
12075 game_frame_delay_value = 0;
12077 SetVideoFrameDelay(game_frame_delay_value);
12079 // (de)activate virtual buttons depending on current game status
12080 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
12082 if (game.all_players_gone) // if no players there to be controlled anymore
12083 SetOverlayActive(FALSE);
12084 else if (!tape.playing) // if game continues after tape stopped playing
12085 SetOverlayActive(TRUE);
12090 // ---------- main game synchronization point ----------
12092 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12094 Debug("game:playing:skip", "skip == %d", skip);
12097 // ---------- main game synchronization point ----------
12099 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
12103 if (network_playing && !network_player_action_received)
12105 // try to get network player actions in time
12107 // last chance to get network player actions without main loop delay
12108 HandleNetworking();
12110 // game was quit by network peer
12111 if (game_status != GAME_MODE_PLAYING)
12114 // check if network player actions still missing and game still running
12115 if (!network_player_action_received && !checkGameEnded())
12116 return; // failed to get network player actions in time
12118 // do not yet reset "network_player_action_received" (for tape.pausing)
12124 // at this point we know that we really continue executing the game
12126 network_player_action_received = FALSE;
12128 // when playing tape, read previously recorded player input from tape data
12129 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
12131 local_player->effective_mouse_action = local_player->mouse_action;
12133 if (recorded_player_action != NULL)
12134 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
12135 recorded_player_action);
12137 // TapePlayAction() may return NULL when toggling to "pause before death"
12141 if (tape.set_centered_player)
12143 game.centered_player_nr_next = tape.centered_player_nr_next;
12144 game.set_centered_player = TRUE;
12147 for (i = 0; i < MAX_PLAYERS; i++)
12149 summarized_player_action |= stored_player[i].action;
12151 if (!network_playing && (game.team_mode || tape.playing))
12152 stored_player[i].effective_action = stored_player[i].action;
12155 if (network_playing && !checkGameEnded())
12156 SendToServer_MovePlayer(summarized_player_action);
12158 // summarize all actions at local players mapped input device position
12159 // (this allows using different input devices in single player mode)
12160 if (!network.enabled && !game.team_mode)
12161 stored_player[map_player_action[local_player->index_nr]].effective_action =
12162 summarized_player_action;
12164 // summarize all actions at centered player in local team mode
12165 if (tape.recording &&
12166 setup.team_mode && !network.enabled &&
12167 setup.input_on_focus &&
12168 game.centered_player_nr != -1)
12170 for (i = 0; i < MAX_PLAYERS; i++)
12171 stored_player[map_player_action[i]].effective_action =
12172 (i == game.centered_player_nr ? summarized_player_action : 0);
12175 if (recorded_player_action != NULL)
12176 for (i = 0; i < MAX_PLAYERS; i++)
12177 stored_player[i].effective_action = recorded_player_action[i];
12179 for (i = 0; i < MAX_PLAYERS; i++)
12181 tape_action[i] = stored_player[i].effective_action;
12183 /* (this may happen in the RND game engine if a player was not present on
12184 the playfield on level start, but appeared later from a custom element */
12185 if (setup.team_mode &&
12188 !tape.player_participates[i])
12189 tape.player_participates[i] = TRUE;
12192 SetTapeActionFromMouseAction(tape_action,
12193 &local_player->effective_mouse_action);
12195 // only record actions from input devices, but not programmed actions
12196 if (tape.recording)
12197 TapeRecordAction(tape_action);
12199 // remember if game was played (especially after tape stopped playing)
12200 if (!tape.playing && summarized_player_action && !checkGameFailed())
12201 game.GamePlayed = TRUE;
12203 #if USE_NEW_PLAYER_ASSIGNMENTS
12204 // !!! also map player actions in single player mode !!!
12205 // if (game.team_mode)
12208 byte mapped_action[MAX_PLAYERS];
12210 #if DEBUG_PLAYER_ACTIONS
12211 for (i = 0; i < MAX_PLAYERS; i++)
12212 DebugContinued("", "%d, ", stored_player[i].effective_action);
12215 for (i = 0; i < MAX_PLAYERS; i++)
12216 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
12218 for (i = 0; i < MAX_PLAYERS; i++)
12219 stored_player[i].effective_action = mapped_action[i];
12221 #if DEBUG_PLAYER_ACTIONS
12222 DebugContinued("", "=> ");
12223 for (i = 0; i < MAX_PLAYERS; i++)
12224 DebugContinued("", "%d, ", stored_player[i].effective_action);
12225 DebugContinued("game:playing:player", "\n");
12228 #if DEBUG_PLAYER_ACTIONS
12231 for (i = 0; i < MAX_PLAYERS; i++)
12232 DebugContinued("", "%d, ", stored_player[i].effective_action);
12233 DebugContinued("game:playing:player", "\n");
12238 for (i = 0; i < MAX_PLAYERS; i++)
12240 // allow engine snapshot in case of changed movement attempt
12241 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12242 (stored_player[i].effective_action & KEY_MOTION))
12243 game.snapshot.changed_action = TRUE;
12245 // allow engine snapshot in case of snapping/dropping attempt
12246 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12247 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12248 game.snapshot.changed_action = TRUE;
12250 game.snapshot.last_action[i] = stored_player[i].effective_action;
12253 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
12255 GameActions_BD_Main();
12257 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12259 GameActions_EM_Main();
12261 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12263 GameActions_SP_Main();
12265 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12267 GameActions_MM_Main();
12271 GameActions_RND_Main();
12274 BlitScreenToBitmap(backbuffer);
12276 CheckLevelSolved();
12279 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12281 if (global.show_frames_per_second)
12283 static unsigned int fps_counter = 0;
12284 static int fps_frames = 0;
12285 unsigned int fps_delay_ms = Counter() - fps_counter;
12289 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12291 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12294 fps_counter = Counter();
12296 // always draw FPS to screen after FPS value was updated
12297 redraw_mask |= REDRAW_FPS;
12300 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12301 if (GetDrawDeactivationMask() == REDRAW_NONE)
12302 redraw_mask |= REDRAW_FPS;
12306 static void GameActions_CheckSaveEngineSnapshot(void)
12308 if (!game.snapshot.save_snapshot)
12311 // clear flag for saving snapshot _before_ saving snapshot
12312 game.snapshot.save_snapshot = FALSE;
12314 SaveEngineSnapshotToList();
12317 void GameActions(void)
12321 GameActions_CheckSaveEngineSnapshot();
12324 void GameActions_BD_Main(void)
12326 byte effective_action[MAX_PLAYERS];
12329 for (i = 0; i < MAX_PLAYERS; i++)
12330 effective_action[i] = stored_player[i].effective_action;
12332 GameActions_BD(effective_action);
12335 void GameActions_EM_Main(void)
12337 byte effective_action[MAX_PLAYERS];
12340 for (i = 0; i < MAX_PLAYERS; i++)
12341 effective_action[i] = stored_player[i].effective_action;
12343 GameActions_EM(effective_action);
12346 void GameActions_SP_Main(void)
12348 byte effective_action[MAX_PLAYERS];
12351 for (i = 0; i < MAX_PLAYERS; i++)
12352 effective_action[i] = stored_player[i].effective_action;
12354 GameActions_SP(effective_action);
12356 for (i = 0; i < MAX_PLAYERS; i++)
12358 if (stored_player[i].force_dropping)
12359 stored_player[i].action |= KEY_BUTTON_DROP;
12361 stored_player[i].force_dropping = FALSE;
12365 void GameActions_MM_Main(void)
12369 GameActions_MM(local_player->effective_mouse_action);
12372 void GameActions_RND_Main(void)
12377 void GameActions_RND(void)
12379 static struct MouseActionInfo mouse_action_last = { 0 };
12380 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12381 int magic_wall_x = 0, magic_wall_y = 0;
12382 int i, x, y, element, graphic, last_gfx_frame;
12384 InitPlayfieldScanModeVars();
12386 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12388 SCAN_PLAYFIELD(x, y)
12390 ChangeCount[x][y] = 0;
12391 ChangeEvent[x][y] = -1;
12395 if (game.set_centered_player)
12397 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12399 // switching to "all players" only possible if all players fit to screen
12400 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12402 game.centered_player_nr_next = game.centered_player_nr;
12403 game.set_centered_player = FALSE;
12406 // do not switch focus to non-existing (or non-active) player
12407 if (game.centered_player_nr_next >= 0 &&
12408 !stored_player[game.centered_player_nr_next].active)
12410 game.centered_player_nr_next = game.centered_player_nr;
12411 game.set_centered_player = FALSE;
12415 if (game.set_centered_player &&
12416 ScreenMovPos == 0) // screen currently aligned at tile position
12420 if (game.centered_player_nr_next == -1)
12422 setScreenCenteredToAllPlayers(&sx, &sy);
12426 sx = stored_player[game.centered_player_nr_next].jx;
12427 sy = stored_player[game.centered_player_nr_next].jy;
12430 game.centered_player_nr = game.centered_player_nr_next;
12431 game.set_centered_player = FALSE;
12433 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12434 DrawGameDoorValues();
12437 // check single step mode (set flag and clear again if any player is active)
12438 game.enter_single_step_mode =
12439 (tape.single_step && tape.recording && !tape.pausing);
12441 for (i = 0; i < MAX_PLAYERS; i++)
12443 int actual_player_action = stored_player[i].effective_action;
12446 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12447 - rnd_equinox_tetrachloride 048
12448 - rnd_equinox_tetrachloride_ii 096
12449 - rnd_emanuel_schmieg 002
12450 - doctor_sloan_ww 001, 020
12452 if (stored_player[i].MovPos == 0)
12453 CheckGravityMovement(&stored_player[i]);
12456 // overwrite programmed action with tape action
12457 if (stored_player[i].programmed_action)
12458 actual_player_action = stored_player[i].programmed_action;
12460 PlayerActions(&stored_player[i], actual_player_action);
12462 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12465 // single step pause mode may already have been toggled by "ScrollPlayer()"
12466 if (game.enter_single_step_mode && !tape.pausing)
12467 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12469 ScrollScreen(NULL, SCROLL_GO_ON);
12471 /* for backwards compatibility, the following code emulates a fixed bug that
12472 occured when pushing elements (causing elements that just made their last
12473 pushing step to already (if possible) make their first falling step in the
12474 same game frame, which is bad); this code is also needed to use the famous
12475 "spring push bug" which is used in older levels and might be wanted to be
12476 used also in newer levels, but in this case the buggy pushing code is only
12477 affecting the "spring" element and no other elements */
12479 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12481 for (i = 0; i < MAX_PLAYERS; i++)
12483 struct PlayerInfo *player = &stored_player[i];
12484 int x = player->jx;
12485 int y = player->jy;
12487 if (player->active && player->is_pushing && player->is_moving &&
12489 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12490 Tile[x][y] == EL_SPRING))
12492 ContinueMoving(x, y);
12494 // continue moving after pushing (this is actually a bug)
12495 if (!IS_MOVING(x, y))
12496 Stop[x][y] = FALSE;
12501 SCAN_PLAYFIELD(x, y)
12503 Last[x][y] = Tile[x][y];
12505 ChangeCount[x][y] = 0;
12506 ChangeEvent[x][y] = -1;
12508 // this must be handled before main playfield loop
12509 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12512 if (MovDelay[x][y] <= 0)
12516 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12519 if (MovDelay[x][y] <= 0)
12521 int element = Store[x][y];
12522 int move_direction = MovDir[x][y];
12523 int player_index_bit = Store2[x][y];
12529 TEST_DrawLevelField(x, y);
12531 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12533 if (IS_ENVELOPE(element))
12534 local_player->show_envelope = element;
12539 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12541 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12543 Debug("game:playing:GameActions_RND", "This should never happen!");
12545 ChangePage[x][y] = -1;
12549 Stop[x][y] = FALSE;
12550 if (WasJustMoving[x][y] > 0)
12551 WasJustMoving[x][y]--;
12552 if (WasJustFalling[x][y] > 0)
12553 WasJustFalling[x][y]--;
12554 if (CheckCollision[x][y] > 0)
12555 CheckCollision[x][y]--;
12556 if (CheckImpact[x][y] > 0)
12557 CheckImpact[x][y]--;
12561 /* reset finished pushing action (not done in ContinueMoving() to allow
12562 continuous pushing animation for elements with zero push delay) */
12563 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12565 ResetGfxAnimation(x, y);
12566 TEST_DrawLevelField(x, y);
12570 if (IS_BLOCKED(x, y))
12574 Blocked2Moving(x, y, &oldx, &oldy);
12575 if (!IS_MOVING(oldx, oldy))
12577 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12578 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12579 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12580 Debug("game:playing:GameActions_RND", "This should never happen!");
12586 HandleMouseAction(&mouse_action, &mouse_action_last);
12588 SCAN_PLAYFIELD(x, y)
12590 element = Tile[x][y];
12591 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12592 last_gfx_frame = GfxFrame[x][y];
12594 if (element == EL_EMPTY)
12595 graphic = el2img(GfxElementEmpty[x][y]);
12597 ResetGfxFrame(x, y);
12599 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12600 DrawLevelGraphicAnimation(x, y, graphic);
12602 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12603 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12604 ResetRandomAnimationValue(x, y);
12606 SetRandomAnimationValue(x, y);
12608 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12610 if (IS_INACTIVE(element))
12612 if (IS_ANIMATED(graphic))
12613 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12618 // this may take place after moving, so 'element' may have changed
12619 if (IS_CHANGING(x, y) &&
12620 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12622 int page = element_info[element].event_page_nr[CE_DELAY];
12624 HandleElementChange(x, y, page);
12626 element = Tile[x][y];
12627 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12630 CheckNextToConditions(x, y);
12632 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12636 element = Tile[x][y];
12637 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12639 if (IS_ANIMATED(graphic) &&
12640 !IS_MOVING(x, y) &&
12642 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12644 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12645 TEST_DrawTwinkleOnField(x, y);
12647 else if (element == EL_ACID)
12650 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12652 else if ((element == EL_EXIT_OPEN ||
12653 element == EL_EM_EXIT_OPEN ||
12654 element == EL_SP_EXIT_OPEN ||
12655 element == EL_STEEL_EXIT_OPEN ||
12656 element == EL_EM_STEEL_EXIT_OPEN ||
12657 element == EL_SP_TERMINAL ||
12658 element == EL_SP_TERMINAL_ACTIVE ||
12659 element == EL_EXTRA_TIME ||
12660 element == EL_SHIELD_NORMAL ||
12661 element == EL_SHIELD_DEADLY) &&
12662 IS_ANIMATED(graphic))
12663 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12664 else if (IS_MOVING(x, y))
12665 ContinueMoving(x, y);
12666 else if (IS_ACTIVE_BOMB(element))
12667 CheckDynamite(x, y);
12668 else if (element == EL_AMOEBA_GROWING)
12669 AmoebaGrowing(x, y);
12670 else if (element == EL_AMOEBA_SHRINKING)
12671 AmoebaShrinking(x, y);
12673 #if !USE_NEW_AMOEBA_CODE
12674 else if (IS_AMOEBALIVE(element))
12675 AmoebaReproduce(x, y);
12678 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12680 else if (element == EL_EXIT_CLOSED)
12682 else if (element == EL_EM_EXIT_CLOSED)
12684 else if (element == EL_STEEL_EXIT_CLOSED)
12685 CheckExitSteel(x, y);
12686 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12687 CheckExitSteelEM(x, y);
12688 else if (element == EL_SP_EXIT_CLOSED)
12690 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12691 element == EL_EXPANDABLE_STEELWALL_GROWING)
12693 else if (element == EL_EXPANDABLE_WALL ||
12694 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12695 element == EL_EXPANDABLE_WALL_VERTICAL ||
12696 element == EL_EXPANDABLE_WALL_ANY ||
12697 element == EL_BD_EXPANDABLE_WALL ||
12698 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12699 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12700 element == EL_EXPANDABLE_STEELWALL_ANY)
12701 CheckWallGrowing(x, y);
12702 else if (element == EL_FLAMES)
12703 CheckForDragon(x, y);
12704 else if (element == EL_EXPLOSION)
12705 ; // drawing of correct explosion animation is handled separately
12706 else if (element == EL_ELEMENT_SNAPPING ||
12707 element == EL_DIAGONAL_SHRINKING ||
12708 element == EL_DIAGONAL_GROWING)
12710 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]);
12712 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12714 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12715 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12717 if (IS_BELT_ACTIVE(element))
12718 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12720 if (game.magic_wall_active)
12722 int jx = local_player->jx, jy = local_player->jy;
12724 // play the element sound at the position nearest to the player
12725 if ((element == EL_MAGIC_WALL_FULL ||
12726 element == EL_MAGIC_WALL_ACTIVE ||
12727 element == EL_MAGIC_WALL_EMPTYING ||
12728 element == EL_BD_MAGIC_WALL_FULL ||
12729 element == EL_BD_MAGIC_WALL_ACTIVE ||
12730 element == EL_BD_MAGIC_WALL_EMPTYING ||
12731 element == EL_DC_MAGIC_WALL_FULL ||
12732 element == EL_DC_MAGIC_WALL_ACTIVE ||
12733 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12734 ABS(x - jx) + ABS(y - jy) <
12735 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12743 #if USE_NEW_AMOEBA_CODE
12744 // new experimental amoeba growth stuff
12745 if (!(FrameCounter % 8))
12747 static unsigned int random = 1684108901;
12749 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12751 x = RND(lev_fieldx);
12752 y = RND(lev_fieldy);
12753 element = Tile[x][y];
12755 if (!IS_PLAYER(x, y) &&
12756 (element == EL_EMPTY ||
12757 CAN_GROW_INTO(element) ||
12758 element == EL_QUICKSAND_EMPTY ||
12759 element == EL_QUICKSAND_FAST_EMPTY ||
12760 element == EL_ACID_SPLASH_LEFT ||
12761 element == EL_ACID_SPLASH_RIGHT))
12763 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12764 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12765 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12766 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12767 Tile[x][y] = EL_AMOEBA_DROP;
12770 random = random * 129 + 1;
12775 game.explosions_delayed = FALSE;
12777 SCAN_PLAYFIELD(x, y)
12779 element = Tile[x][y];
12781 if (ExplodeField[x][y])
12782 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12783 else if (element == EL_EXPLOSION)
12784 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12786 ExplodeField[x][y] = EX_TYPE_NONE;
12789 game.explosions_delayed = TRUE;
12791 if (game.magic_wall_active)
12793 if (!(game.magic_wall_time_left % 4))
12795 int element = Tile[magic_wall_x][magic_wall_y];
12797 if (element == EL_BD_MAGIC_WALL_FULL ||
12798 element == EL_BD_MAGIC_WALL_ACTIVE ||
12799 element == EL_BD_MAGIC_WALL_EMPTYING)
12800 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12801 else if (element == EL_DC_MAGIC_WALL_FULL ||
12802 element == EL_DC_MAGIC_WALL_ACTIVE ||
12803 element == EL_DC_MAGIC_WALL_EMPTYING)
12804 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12806 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12809 if (game.magic_wall_time_left > 0)
12811 game.magic_wall_time_left--;
12813 if (!game.magic_wall_time_left)
12815 SCAN_PLAYFIELD(x, y)
12817 element = Tile[x][y];
12819 if (element == EL_MAGIC_WALL_ACTIVE ||
12820 element == EL_MAGIC_WALL_FULL)
12822 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12823 TEST_DrawLevelField(x, y);
12825 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12826 element == EL_BD_MAGIC_WALL_FULL)
12828 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12829 TEST_DrawLevelField(x, y);
12831 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12832 element == EL_DC_MAGIC_WALL_FULL)
12834 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12835 TEST_DrawLevelField(x, y);
12839 game.magic_wall_active = FALSE;
12844 if (game.light_time_left > 0)
12846 game.light_time_left--;
12848 if (game.light_time_left == 0)
12849 RedrawAllLightSwitchesAndInvisibleElements();
12852 if (game.timegate_time_left > 0)
12854 game.timegate_time_left--;
12856 if (game.timegate_time_left == 0)
12857 CloseAllOpenTimegates();
12860 if (game.lenses_time_left > 0)
12862 game.lenses_time_left--;
12864 if (game.lenses_time_left == 0)
12865 RedrawAllInvisibleElementsForLenses();
12868 if (game.magnify_time_left > 0)
12870 game.magnify_time_left--;
12872 if (game.magnify_time_left == 0)
12873 RedrawAllInvisibleElementsForMagnifier();
12876 for (i = 0; i < MAX_PLAYERS; i++)
12878 struct PlayerInfo *player = &stored_player[i];
12880 if (SHIELD_ON(player))
12882 if (player->shield_deadly_time_left)
12883 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12884 else if (player->shield_normal_time_left)
12885 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12889 #if USE_DELAYED_GFX_REDRAW
12890 SCAN_PLAYFIELD(x, y)
12892 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12894 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12895 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12897 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12898 DrawLevelField(x, y);
12900 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12901 DrawLevelFieldCrumbled(x, y);
12903 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12904 DrawLevelFieldCrumbledNeighbours(x, y);
12906 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12907 DrawTwinkleOnField(x, y);
12910 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12915 PlayAllPlayersSound();
12917 for (i = 0; i < MAX_PLAYERS; i++)
12919 struct PlayerInfo *player = &stored_player[i];
12921 if (player->show_envelope != 0 && (!player->active ||
12922 player->MovPos == 0))
12924 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12926 player->show_envelope = 0;
12930 // use random number generator in every frame to make it less predictable
12931 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12934 mouse_action_last = mouse_action;
12937 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12939 int min_x = x, min_y = y, max_x = x, max_y = y;
12940 int scr_fieldx = getScreenFieldSizeX();
12941 int scr_fieldy = getScreenFieldSizeY();
12944 for (i = 0; i < MAX_PLAYERS; i++)
12946 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12948 if (!stored_player[i].active || &stored_player[i] == player)
12951 min_x = MIN(min_x, jx);
12952 min_y = MIN(min_y, jy);
12953 max_x = MAX(max_x, jx);
12954 max_y = MAX(max_y, jy);
12957 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12960 static boolean AllPlayersInVisibleScreen(void)
12964 for (i = 0; i < MAX_PLAYERS; i++)
12966 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12968 if (!stored_player[i].active)
12971 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12978 void ScrollLevel(int dx, int dy)
12980 int scroll_offset = 2 * TILEX_VAR;
12983 BlitBitmap(drawto_field, drawto_field,
12984 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12985 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12986 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12987 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12988 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12989 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12993 x = (dx == 1 ? BX1 : BX2);
12994 for (y = BY1; y <= BY2; y++)
12995 DrawScreenField(x, y);
13000 y = (dy == 1 ? BY1 : BY2);
13001 for (x = BX1; x <= BX2; x++)
13002 DrawScreenField(x, y);
13005 redraw_mask |= REDRAW_FIELD;
13008 static boolean canFallDown(struct PlayerInfo *player)
13010 int jx = player->jx, jy = player->jy;
13012 return (IN_LEV_FIELD(jx, jy + 1) &&
13013 (IS_FREE(jx, jy + 1) ||
13014 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
13015 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
13016 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
13019 static boolean canPassField(int x, int y, int move_dir)
13021 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13022 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13023 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13024 int nextx = x + dx;
13025 int nexty = y + dy;
13026 int element = Tile[x][y];
13028 return (IS_PASSABLE_FROM(element, opposite_dir) &&
13029 !CAN_MOVE(element) &&
13030 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
13031 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
13032 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
13035 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
13037 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
13038 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
13039 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
13043 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
13044 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
13045 (IS_DIGGABLE(Tile[newx][newy]) ||
13046 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
13047 canPassField(newx, newy, move_dir)));
13050 static void CheckGravityMovement(struct PlayerInfo *player)
13052 if (player->gravity && !player->programmed_action)
13054 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
13055 int move_dir_vertical = player->effective_action & MV_VERTICAL;
13056 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
13057 int jx = player->jx, jy = player->jy;
13058 boolean player_is_moving_to_valid_field =
13059 (!player_is_snapping &&
13060 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
13061 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
13062 boolean player_can_fall_down = canFallDown(player);
13064 if (player_can_fall_down &&
13065 !player_is_moving_to_valid_field)
13066 player->programmed_action = MV_DOWN;
13070 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
13072 return CheckGravityMovement(player);
13074 if (player->gravity && !player->programmed_action)
13076 int jx = player->jx, jy = player->jy;
13077 boolean field_under_player_is_free =
13078 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
13079 boolean player_is_standing_on_valid_field =
13080 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
13081 (IS_WALKABLE(Tile[jx][jy]) &&
13082 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
13084 if (field_under_player_is_free && !player_is_standing_on_valid_field)
13085 player->programmed_action = MV_DOWN;
13090 MovePlayerOneStep()
13091 -----------------------------------------------------------------------------
13092 dx, dy: direction (non-diagonal) to try to move the player to
13093 real_dx, real_dy: direction as read from input device (can be diagonal)
13096 boolean MovePlayerOneStep(struct PlayerInfo *player,
13097 int dx, int dy, int real_dx, int real_dy)
13099 int jx = player->jx, jy = player->jy;
13100 int new_jx = jx + dx, new_jy = jy + dy;
13102 boolean player_can_move = !player->cannot_move;
13104 if (!player->active || (!dx && !dy))
13105 return MP_NO_ACTION;
13107 player->MovDir = (dx < 0 ? MV_LEFT :
13108 dx > 0 ? MV_RIGHT :
13110 dy > 0 ? MV_DOWN : MV_NONE);
13112 if (!IN_LEV_FIELD(new_jx, new_jy))
13113 return MP_NO_ACTION;
13115 if (!player_can_move)
13117 if (player->MovPos == 0)
13119 player->is_moving = FALSE;
13120 player->is_digging = FALSE;
13121 player->is_collecting = FALSE;
13122 player->is_snapping = FALSE;
13123 player->is_pushing = FALSE;
13127 if (!network.enabled && game.centered_player_nr == -1 &&
13128 !AllPlayersInSight(player, new_jx, new_jy))
13129 return MP_NO_ACTION;
13131 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG);
13132 if (can_move != MP_MOVING)
13135 // check if DigField() has caused relocation of the player
13136 if (player->jx != jx || player->jy != jy)
13137 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
13139 StorePlayer[jx][jy] = 0;
13140 player->last_jx = jx;
13141 player->last_jy = jy;
13142 player->jx = new_jx;
13143 player->jy = new_jy;
13144 StorePlayer[new_jx][new_jy] = player->element_nr;
13146 if (player->move_delay_value_next != -1)
13148 player->move_delay_value = player->move_delay_value_next;
13149 player->move_delay_value_next = -1;
13153 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
13155 player->step_counter++;
13157 PlayerVisit[jx][jy] = FrameCounter;
13159 player->is_moving = TRUE;
13162 // should better be called in MovePlayer(), but this breaks some tapes
13163 ScrollPlayer(player, SCROLL_INIT);
13169 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
13171 int jx = player->jx, jy = player->jy;
13172 int old_jx = jx, old_jy = jy;
13173 int moved = MP_NO_ACTION;
13175 if (!player->active)
13180 if (player->MovPos == 0)
13182 player->is_moving = FALSE;
13183 player->is_digging = FALSE;
13184 player->is_collecting = FALSE;
13185 player->is_snapping = FALSE;
13186 player->is_pushing = FALSE;
13192 if (player->move_delay > 0)
13195 player->move_delay = -1; // set to "uninitialized" value
13197 // store if player is automatically moved to next field
13198 player->is_auto_moving = (player->programmed_action != MV_NONE);
13200 // remove the last programmed player action
13201 player->programmed_action = 0;
13203 if (player->MovPos)
13205 // should only happen if pre-1.2 tape recordings are played
13206 // this is only for backward compatibility
13208 int original_move_delay_value = player->move_delay_value;
13211 Debug("game:playing:MovePlayer",
13212 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13216 // scroll remaining steps with finest movement resolution
13217 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13219 while (player->MovPos)
13221 ScrollPlayer(player, SCROLL_GO_ON);
13222 ScrollScreen(NULL, SCROLL_GO_ON);
13224 AdvanceFrameAndPlayerCounters(player->index_nr);
13227 BackToFront_WithFrameDelay(0);
13230 player->move_delay_value = original_move_delay_value;
13233 player->is_active = FALSE;
13235 if (player->last_move_dir & MV_HORIZONTAL)
13237 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13238 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13242 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13243 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13246 if (!moved && !player->is_active)
13248 player->is_moving = FALSE;
13249 player->is_digging = FALSE;
13250 player->is_collecting = FALSE;
13251 player->is_snapping = FALSE;
13252 player->is_pushing = FALSE;
13258 if (moved & MP_MOVING && !ScreenMovPos &&
13259 (player->index_nr == game.centered_player_nr ||
13260 game.centered_player_nr == -1))
13262 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13264 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13266 // actual player has left the screen -- scroll in that direction
13267 if (jx != old_jx) // player has moved horizontally
13268 scroll_x += (jx - old_jx);
13269 else // player has moved vertically
13270 scroll_y += (jy - old_jy);
13274 int offset_raw = game.scroll_delay_value;
13276 if (jx != old_jx) // player has moved horizontally
13278 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13279 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13280 int new_scroll_x = jx - MIDPOSX + offset_x;
13282 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13283 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13284 scroll_x = new_scroll_x;
13286 // don't scroll over playfield boundaries
13287 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13289 // don't scroll more than one field at a time
13290 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13292 // don't scroll against the player's moving direction
13293 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13294 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13295 scroll_x = old_scroll_x;
13297 else // player has moved vertically
13299 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13300 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13301 int new_scroll_y = jy - MIDPOSY + offset_y;
13303 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13304 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13305 scroll_y = new_scroll_y;
13307 // don't scroll over playfield boundaries
13308 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13310 // don't scroll more than one field at a time
13311 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13313 // don't scroll against the player's moving direction
13314 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13315 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13316 scroll_y = old_scroll_y;
13320 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13322 if (!network.enabled && game.centered_player_nr == -1 &&
13323 !AllPlayersInVisibleScreen())
13325 scroll_x = old_scroll_x;
13326 scroll_y = old_scroll_y;
13330 ScrollScreen(player, SCROLL_INIT);
13331 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13336 player->StepFrame = 0;
13338 if (moved & MP_MOVING)
13340 if (old_jx != jx && old_jy == jy)
13341 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13342 else if (old_jx == jx && old_jy != jy)
13343 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13345 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13347 player->last_move_dir = player->MovDir;
13348 player->is_moving = TRUE;
13349 player->is_snapping = FALSE;
13350 player->is_switching = FALSE;
13351 player->is_dropping = FALSE;
13352 player->is_dropping_pressed = FALSE;
13353 player->drop_pressed_delay = 0;
13356 // should better be called here than above, but this breaks some tapes
13357 ScrollPlayer(player, SCROLL_INIT);
13362 CheckGravityMovementWhenNotMoving(player);
13364 player->is_moving = FALSE;
13366 /* at this point, the player is allowed to move, but cannot move right now
13367 (e.g. because of something blocking the way) -- ensure that the player
13368 is also allowed to move in the next frame (in old versions before 3.1.1,
13369 the player was forced to wait again for eight frames before next try) */
13371 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13372 player->move_delay = 0; // allow direct movement in the next frame
13375 if (player->move_delay == -1) // not yet initialized by DigField()
13376 player->move_delay = player->move_delay_value;
13378 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13380 TestIfPlayerTouchesBadThing(jx, jy);
13381 TestIfPlayerTouchesCustomElement(jx, jy);
13384 if (!player->active)
13385 RemovePlayer(player);
13390 void ScrollPlayer(struct PlayerInfo *player, int mode)
13392 int jx = player->jx, jy = player->jy;
13393 int last_jx = player->last_jx, last_jy = player->last_jy;
13394 int move_stepsize = TILEX / player->move_delay_value;
13396 if (!player->active)
13399 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13402 if (mode == SCROLL_INIT)
13404 player->actual_frame_counter.count = FrameCounter;
13405 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13407 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13408 Tile[last_jx][last_jy] == EL_EMPTY)
13410 int last_field_block_delay = 0; // start with no blocking at all
13411 int block_delay_adjustment = player->block_delay_adjustment;
13413 // if player blocks last field, add delay for exactly one move
13414 if (player->block_last_field)
13416 last_field_block_delay += player->move_delay_value;
13418 // when blocking enabled, prevent moving up despite gravity
13419 if (player->gravity && player->MovDir == MV_UP)
13420 block_delay_adjustment = -1;
13423 // add block delay adjustment (also possible when not blocking)
13424 last_field_block_delay += block_delay_adjustment;
13426 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13427 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13430 if (player->MovPos != 0) // player has not yet reached destination
13433 else if (!FrameReached(&player->actual_frame_counter))
13436 if (player->MovPos != 0)
13438 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13439 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13441 // before DrawPlayer() to draw correct player graphic for this case
13442 if (player->MovPos == 0)
13443 CheckGravityMovement(player);
13446 if (player->MovPos == 0) // player reached destination field
13448 if (player->move_delay_reset_counter > 0)
13450 player->move_delay_reset_counter--;
13452 if (player->move_delay_reset_counter == 0)
13454 // continue with normal speed after quickly moving through gate
13455 HALVE_PLAYER_SPEED(player);
13457 // be able to make the next move without delay
13458 player->move_delay = 0;
13462 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13463 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13464 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13465 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13466 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13467 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13468 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13469 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13471 ExitPlayer(player);
13473 if (game.players_still_needed == 0 &&
13474 (game.friends_still_needed == 0 ||
13475 IS_SP_ELEMENT(Tile[jx][jy])))
13479 player->last_jx = jx;
13480 player->last_jy = jy;
13482 // this breaks one level: "machine", level 000
13484 int move_direction = player->MovDir;
13485 int enter_side = MV_DIR_OPPOSITE(move_direction);
13486 int leave_side = move_direction;
13487 int old_jx = last_jx;
13488 int old_jy = last_jy;
13489 int old_element = Tile[old_jx][old_jy];
13490 int new_element = Tile[jx][jy];
13492 if (IS_CUSTOM_ELEMENT(old_element))
13493 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13495 player->index_bit, leave_side);
13497 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13498 CE_PLAYER_LEAVES_X,
13499 player->index_bit, leave_side);
13501 // needed because pushed element has not yet reached its destination,
13502 // so it would trigger a change event at its previous field location
13503 if (!player->is_pushing)
13505 if (IS_CUSTOM_ELEMENT(new_element))
13506 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13507 player->index_bit, enter_side);
13509 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13510 CE_PLAYER_ENTERS_X,
13511 player->index_bit, enter_side);
13514 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13515 CE_MOVE_OF_X, move_direction);
13518 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13520 TestIfPlayerTouchesBadThing(jx, jy);
13521 TestIfPlayerTouchesCustomElement(jx, jy);
13523 // needed because pushed element has not yet reached its destination,
13524 // so it would trigger a change event at its previous field location
13525 if (!player->is_pushing)
13526 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13528 if (level.finish_dig_collect &&
13529 (player->is_digging || player->is_collecting))
13531 int last_element = player->last_removed_element;
13532 int move_direction = player->MovDir;
13533 int enter_side = MV_DIR_OPPOSITE(move_direction);
13534 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13535 CE_PLAYER_COLLECTS_X);
13537 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13538 player->index_bit, enter_side);
13540 player->last_removed_element = EL_UNDEFINED;
13543 if (!player->active)
13544 RemovePlayer(player);
13547 if (level.use_step_counter)
13548 CheckLevelTime_StepCounter();
13550 if (tape.single_step && tape.recording && !tape.pausing &&
13551 !player->programmed_action)
13552 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13554 if (!player->programmed_action)
13555 CheckSaveEngineSnapshot(player);
13559 void ScrollScreen(struct PlayerInfo *player, int mode)
13561 static DelayCounter screen_frame_counter = { 0 };
13563 if (mode == SCROLL_INIT)
13565 // set scrolling step size according to actual player's moving speed
13566 ScrollStepSize = TILEX / player->move_delay_value;
13568 screen_frame_counter.count = FrameCounter;
13569 screen_frame_counter.value = 1;
13571 ScreenMovDir = player->MovDir;
13572 ScreenMovPos = player->MovPos;
13573 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13576 else if (!FrameReached(&screen_frame_counter))
13581 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13582 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13583 redraw_mask |= REDRAW_FIELD;
13586 ScreenMovDir = MV_NONE;
13589 void CheckNextToConditions(int x, int y)
13591 int element = Tile[x][y];
13593 if (IS_PLAYER(x, y))
13594 TestIfPlayerNextToCustomElement(x, y);
13596 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13597 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13598 TestIfElementNextToCustomElement(x, y);
13601 void TestIfPlayerNextToCustomElement(int x, int y)
13603 struct XY *xy = xy_topdown;
13604 static int trigger_sides[4][2] =
13606 // center side border side
13607 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13608 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13609 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13610 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13614 if (!IS_PLAYER(x, y))
13617 struct PlayerInfo *player = PLAYERINFO(x, y);
13619 if (player->is_moving)
13622 for (i = 0; i < NUM_DIRECTIONS; i++)
13624 int xx = x + xy[i].x;
13625 int yy = y + xy[i].y;
13626 int border_side = trigger_sides[i][1];
13627 int border_element;
13629 if (!IN_LEV_FIELD(xx, yy))
13632 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13633 continue; // center and border element not connected
13635 border_element = Tile[xx][yy];
13637 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13638 player->index_bit, border_side);
13639 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13640 CE_PLAYER_NEXT_TO_X,
13641 player->index_bit, border_side);
13643 /* use player element that is initially defined in the level playfield,
13644 not the player element that corresponds to the runtime player number
13645 (example: a level that contains EL_PLAYER_3 as the only player would
13646 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13648 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13649 CE_NEXT_TO_X, border_side);
13653 void TestIfPlayerTouchesCustomElement(int x, int y)
13655 struct XY *xy = xy_topdown;
13656 static int trigger_sides[4][2] =
13658 // center side border side
13659 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13660 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13661 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13662 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13664 static int touch_dir[4] =
13666 MV_LEFT | MV_RIGHT,
13671 int center_element = Tile[x][y]; // should always be non-moving!
13674 for (i = 0; i < NUM_DIRECTIONS; i++)
13676 int xx = x + xy[i].x;
13677 int yy = y + xy[i].y;
13678 int center_side = trigger_sides[i][0];
13679 int border_side = trigger_sides[i][1];
13680 int border_element;
13682 if (!IN_LEV_FIELD(xx, yy))
13685 if (IS_PLAYER(x, y)) // player found at center element
13687 struct PlayerInfo *player = PLAYERINFO(x, y);
13689 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13690 border_element = Tile[xx][yy]; // may be moving!
13691 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13692 border_element = Tile[xx][yy];
13693 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13694 border_element = MovingOrBlocked2Element(xx, yy);
13696 continue; // center and border element do not touch
13698 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13699 player->index_bit, border_side);
13700 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13701 CE_PLAYER_TOUCHES_X,
13702 player->index_bit, border_side);
13705 /* use player element that is initially defined in the level playfield,
13706 not the player element that corresponds to the runtime player number
13707 (example: a level that contains EL_PLAYER_3 as the only player would
13708 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13709 int player_element = PLAYERINFO(x, y)->initial_element;
13711 // as element "X" is the player here, check opposite (center) side
13712 CheckElementChangeBySide(xx, yy, border_element, player_element,
13713 CE_TOUCHING_X, center_side);
13716 else if (IS_PLAYER(xx, yy)) // player found at border element
13718 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13720 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13722 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13723 continue; // center and border element do not touch
13726 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13727 player->index_bit, center_side);
13728 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13729 CE_PLAYER_TOUCHES_X,
13730 player->index_bit, center_side);
13733 /* use player element that is initially defined in the level playfield,
13734 not the player element that corresponds to the runtime player number
13735 (example: a level that contains EL_PLAYER_3 as the only player would
13736 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13737 int player_element = PLAYERINFO(xx, yy)->initial_element;
13739 // as element "X" is the player here, check opposite (border) side
13740 CheckElementChangeBySide(x, y, center_element, player_element,
13741 CE_TOUCHING_X, border_side);
13749 void TestIfElementNextToCustomElement(int x, int y)
13751 struct XY *xy = xy_topdown;
13752 static int trigger_sides[4][2] =
13754 // center side border side
13755 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13756 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13757 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13758 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13760 int center_element = Tile[x][y]; // should always be non-moving!
13763 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13766 for (i = 0; i < NUM_DIRECTIONS; i++)
13768 int xx = x + xy[i].x;
13769 int yy = y + xy[i].y;
13770 int border_side = trigger_sides[i][1];
13771 int border_element;
13773 if (!IN_LEV_FIELD(xx, yy))
13776 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13777 continue; // center and border element not connected
13779 border_element = Tile[xx][yy];
13781 // check for change of center element (but change it only once)
13782 if (CheckElementChangeBySide(x, y, center_element, border_element,
13783 CE_NEXT_TO_X, border_side))
13788 void TestIfElementTouchesCustomElement(int x, int y)
13790 struct XY *xy = xy_topdown;
13791 static int trigger_sides[4][2] =
13793 // center side border side
13794 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13795 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13796 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13797 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13799 static int touch_dir[4] =
13801 MV_LEFT | MV_RIGHT,
13806 boolean change_center_element = FALSE;
13807 int center_element = Tile[x][y]; // should always be non-moving!
13808 int border_element_old[NUM_DIRECTIONS];
13811 for (i = 0; i < NUM_DIRECTIONS; i++)
13813 int xx = x + xy[i].x;
13814 int yy = y + xy[i].y;
13815 int border_element;
13817 border_element_old[i] = -1;
13819 if (!IN_LEV_FIELD(xx, yy))
13822 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13823 border_element = Tile[xx][yy]; // may be moving!
13824 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13825 border_element = Tile[xx][yy];
13826 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13827 border_element = MovingOrBlocked2Element(xx, yy);
13829 continue; // center and border element do not touch
13831 border_element_old[i] = border_element;
13834 for (i = 0; i < NUM_DIRECTIONS; i++)
13836 int xx = x + xy[i].x;
13837 int yy = y + xy[i].y;
13838 int center_side = trigger_sides[i][0];
13839 int border_element = border_element_old[i];
13841 if (border_element == -1)
13844 // check for change of border element
13845 CheckElementChangeBySide(xx, yy, border_element, center_element,
13846 CE_TOUCHING_X, center_side);
13848 // (center element cannot be player, so we don't have to check this here)
13851 for (i = 0; i < NUM_DIRECTIONS; i++)
13853 int xx = x + xy[i].x;
13854 int yy = y + xy[i].y;
13855 int border_side = trigger_sides[i][1];
13856 int border_element = border_element_old[i];
13858 if (border_element == -1)
13861 // check for change of center element (but change it only once)
13862 if (!change_center_element)
13863 change_center_element =
13864 CheckElementChangeBySide(x, y, center_element, border_element,
13865 CE_TOUCHING_X, border_side);
13867 if (IS_PLAYER(xx, yy))
13869 /* use player element that is initially defined in the level playfield,
13870 not the player element that corresponds to the runtime player number
13871 (example: a level that contains EL_PLAYER_3 as the only player would
13872 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13873 int player_element = PLAYERINFO(xx, yy)->initial_element;
13875 // as element "X" is the player here, check opposite (border) side
13876 CheckElementChangeBySide(x, y, center_element, player_element,
13877 CE_TOUCHING_X, border_side);
13882 void TestIfElementHitsCustomElement(int x, int y, int direction)
13884 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13885 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13886 int hitx = x + dx, hity = y + dy;
13887 int hitting_element = Tile[x][y];
13888 int touched_element;
13890 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13893 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13894 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13896 if (IN_LEV_FIELD(hitx, hity))
13898 int opposite_direction = MV_DIR_OPPOSITE(direction);
13899 int hitting_side = direction;
13900 int touched_side = opposite_direction;
13901 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13902 MovDir[hitx][hity] != direction ||
13903 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13909 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13910 CE_HITTING_X, touched_side);
13912 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13913 CE_HIT_BY_X, hitting_side);
13915 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13916 CE_HIT_BY_SOMETHING, opposite_direction);
13918 if (IS_PLAYER(hitx, hity))
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(hitx, hity)->initial_element;
13926 CheckElementChangeBySide(x, y, hitting_element, player_element,
13927 CE_HITTING_X, touched_side);
13932 // "hitting something" is also true when hitting the playfield border
13933 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13934 CE_HITTING_SOMETHING, direction);
13937 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13939 int i, kill_x = -1, kill_y = -1;
13941 int bad_element = -1;
13942 struct XY *test_xy = xy_topdown;
13943 static int test_dir[4] =
13951 for (i = 0; i < NUM_DIRECTIONS; i++)
13953 int test_x, test_y, test_move_dir, test_element;
13955 test_x = good_x + test_xy[i].x;
13956 test_y = good_y + test_xy[i].y;
13958 if (!IN_LEV_FIELD(test_x, test_y))
13962 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13964 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13966 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13967 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13969 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13970 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13974 bad_element = test_element;
13980 if (kill_x != -1 || kill_y != -1)
13982 if (IS_PLAYER(good_x, good_y))
13984 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13986 if (player->shield_deadly_time_left > 0 &&
13987 !IS_INDESTRUCTIBLE(bad_element))
13988 Bang(kill_x, kill_y);
13989 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13990 KillPlayer(player);
13993 Bang(good_x, good_y);
13997 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13999 int i, kill_x = -1, kill_y = -1;
14000 int bad_element = Tile[bad_x][bad_y];
14001 struct XY *test_xy = xy_topdown;
14002 static int touch_dir[4] =
14004 MV_LEFT | MV_RIGHT,
14009 static int test_dir[4] =
14017 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
14020 for (i = 0; i < NUM_DIRECTIONS; i++)
14022 int test_x, test_y, test_move_dir, test_element;
14024 test_x = bad_x + test_xy[i].x;
14025 test_y = bad_y + test_xy[i].y;
14027 if (!IN_LEV_FIELD(test_x, test_y))
14031 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14033 test_element = Tile[test_x][test_y];
14035 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
14036 2nd case: DONT_TOUCH style bad thing does not move away from good thing
14038 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
14039 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
14041 // good thing is player or penguin that does not move away
14042 if (IS_PLAYER(test_x, test_y))
14044 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14046 if (bad_element == EL_ROBOT && player->is_moving)
14047 continue; // robot does not kill player if he is moving
14049 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
14051 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
14052 continue; // center and border element do not touch
14060 else if (test_element == EL_PENGUIN)
14070 if (kill_x != -1 || kill_y != -1)
14072 if (IS_PLAYER(kill_x, kill_y))
14074 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14076 if (player->shield_deadly_time_left > 0 &&
14077 !IS_INDESTRUCTIBLE(bad_element))
14078 Bang(bad_x, bad_y);
14079 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14080 KillPlayer(player);
14083 Bang(kill_x, kill_y);
14087 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
14089 int bad_element = Tile[bad_x][bad_y];
14090 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
14091 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
14092 int test_x = bad_x + dx, test_y = bad_y + dy;
14093 int test_move_dir, test_element;
14094 int kill_x = -1, kill_y = -1;
14096 if (!IN_LEV_FIELD(test_x, test_y))
14100 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
14102 test_element = Tile[test_x][test_y];
14104 if (test_move_dir != bad_move_dir)
14106 // good thing can be player or penguin that does not move away
14107 if (IS_PLAYER(test_x, test_y))
14109 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
14111 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
14112 player as being hit when he is moving towards the bad thing, because
14113 the "get hit by" condition would be lost after the player stops) */
14114 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
14115 return; // player moves away from bad thing
14120 else if (test_element == EL_PENGUIN)
14127 if (kill_x != -1 || kill_y != -1)
14129 if (IS_PLAYER(kill_x, kill_y))
14131 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
14133 if (player->shield_deadly_time_left > 0 &&
14134 !IS_INDESTRUCTIBLE(bad_element))
14135 Bang(bad_x, bad_y);
14136 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
14137 KillPlayer(player);
14140 Bang(kill_x, kill_y);
14144 void TestIfPlayerTouchesBadThing(int x, int y)
14146 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14149 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
14151 TestIfGoodThingHitsBadThing(x, y, move_dir);
14154 void TestIfBadThingTouchesPlayer(int x, int y)
14156 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14159 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
14161 TestIfBadThingHitsGoodThing(x, y, move_dir);
14164 void TestIfFriendTouchesBadThing(int x, int y)
14166 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14169 void TestIfBadThingTouchesFriend(int x, int y)
14171 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14174 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
14176 int i, kill_x = bad_x, kill_y = bad_y;
14177 struct XY *xy = xy_topdown;
14179 for (i = 0; i < NUM_DIRECTIONS; i++)
14183 x = bad_x + xy[i].x;
14184 y = bad_y + xy[i].y;
14185 if (!IN_LEV_FIELD(x, y))
14188 element = Tile[x][y];
14189 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14190 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14198 if (kill_x != bad_x || kill_y != bad_y)
14199 Bang(bad_x, bad_y);
14202 void KillPlayer(struct PlayerInfo *player)
14204 int jx = player->jx, jy = player->jy;
14206 if (!player->active)
14210 Debug("game:playing:KillPlayer",
14211 "0: killed == %d, active == %d, reanimated == %d",
14212 player->killed, player->active, player->reanimated);
14215 /* the following code was introduced to prevent an infinite loop when calling
14217 -> CheckTriggeredElementChangeExt()
14218 -> ExecuteCustomElementAction()
14220 -> (infinitely repeating the above sequence of function calls)
14221 which occurs when killing the player while having a CE with the setting
14222 "kill player X when explosion of <player X>"; the solution using a new
14223 field "player->killed" was chosen for backwards compatibility, although
14224 clever use of the fields "player->active" etc. would probably also work */
14226 if (player->killed)
14230 player->killed = TRUE;
14232 // remove accessible field at the player's position
14233 RemoveField(jx, jy);
14235 // deactivate shield (else Bang()/Explode() would not work right)
14236 player->shield_normal_time_left = 0;
14237 player->shield_deadly_time_left = 0;
14240 Debug("game:playing:KillPlayer",
14241 "1: killed == %d, active == %d, reanimated == %d",
14242 player->killed, player->active, player->reanimated);
14248 Debug("game:playing:KillPlayer",
14249 "2: killed == %d, active == %d, reanimated == %d",
14250 player->killed, player->active, player->reanimated);
14253 if (player->reanimated) // killed player may have been reanimated
14254 player->killed = player->reanimated = FALSE;
14256 BuryPlayer(player);
14259 static void KillPlayerUnlessEnemyProtected(int x, int y)
14261 if (!PLAYER_ENEMY_PROTECTED(x, y))
14262 KillPlayer(PLAYERINFO(x, y));
14265 static void KillPlayerUnlessExplosionProtected(int x, int y)
14267 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14268 KillPlayer(PLAYERINFO(x, y));
14271 void BuryPlayer(struct PlayerInfo *player)
14273 int jx = player->jx, jy = player->jy;
14275 if (!player->active)
14278 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14280 RemovePlayer(player);
14282 player->buried = TRUE;
14284 if (game.all_players_gone)
14285 game.GameOver = TRUE;
14288 void RemovePlayer(struct PlayerInfo *player)
14290 int jx = player->jx, jy = player->jy;
14291 int i, found = FALSE;
14293 player->present = FALSE;
14294 player->active = FALSE;
14296 // required for some CE actions (even if the player is not active anymore)
14297 player->MovPos = 0;
14299 if (!ExplodeField[jx][jy])
14300 StorePlayer[jx][jy] = 0;
14302 if (player->is_moving)
14303 TEST_DrawLevelField(player->last_jx, player->last_jy);
14305 for (i = 0; i < MAX_PLAYERS; i++)
14306 if (stored_player[i].active)
14311 game.all_players_gone = TRUE;
14312 game.GameOver = TRUE;
14315 game.exit_x = game.robot_wheel_x = jx;
14316 game.exit_y = game.robot_wheel_y = jy;
14319 void ExitPlayer(struct PlayerInfo *player)
14321 DrawPlayer(player); // needed here only to cleanup last field
14322 RemovePlayer(player);
14324 if (game.players_still_needed > 0)
14325 game.players_still_needed--;
14328 static void SetFieldForSnapping(int x, int y, int element, int direction,
14329 int player_index_bit)
14331 struct ElementInfo *ei = &element_info[element];
14332 int direction_bit = MV_DIR_TO_BIT(direction);
14333 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14334 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14335 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14337 Tile[x][y] = EL_ELEMENT_SNAPPING;
14338 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14339 MovDir[x][y] = direction;
14340 Store[x][y] = element;
14341 Store2[x][y] = player_index_bit;
14343 ResetGfxAnimation(x, y);
14345 GfxElement[x][y] = element;
14346 GfxAction[x][y] = action;
14347 GfxDir[x][y] = direction;
14348 GfxFrame[x][y] = -1;
14351 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14352 int player_index_bit)
14354 TestIfElementTouchesCustomElement(x, y); // for empty space
14356 if (level.finish_dig_collect)
14358 int dig_side = MV_DIR_OPPOSITE(direction);
14359 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14360 CE_PLAYER_COLLECTS_X);
14362 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14363 player_index_bit, dig_side);
14364 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14365 player_index_bit, dig_side);
14370 =============================================================================
14371 checkDiagonalPushing()
14372 -----------------------------------------------------------------------------
14373 check if diagonal input device direction results in pushing of object
14374 (by checking if the alternative direction is walkable, diggable, ...)
14375 =============================================================================
14378 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14379 int x, int y, int real_dx, int real_dy)
14381 int jx, jy, dx, dy, xx, yy;
14383 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14386 // diagonal direction: check alternative direction
14391 xx = jx + (dx == 0 ? real_dx : 0);
14392 yy = jy + (dy == 0 ? real_dy : 0);
14394 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14398 =============================================================================
14400 -----------------------------------------------------------------------------
14401 x, y: field next to player (non-diagonal) to try to dig to
14402 real_dx, real_dy: direction as read from input device (can be diagonal)
14403 =============================================================================
14406 static int DigField(struct PlayerInfo *player,
14407 int oldx, int oldy, int x, int y,
14408 int real_dx, int real_dy, int mode)
14410 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14411 boolean player_was_pushing = player->is_pushing;
14412 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14413 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14414 int jx = oldx, jy = oldy;
14415 int dx = x - jx, dy = y - jy;
14416 int nextx = x + dx, nexty = y + dy;
14417 int move_direction = (dx == -1 ? MV_LEFT :
14418 dx == +1 ? MV_RIGHT :
14420 dy == +1 ? MV_DOWN : MV_NONE);
14421 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14422 int dig_side = MV_DIR_OPPOSITE(move_direction);
14423 int old_element = Tile[jx][jy];
14424 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14427 if (is_player) // function can also be called by EL_PENGUIN
14429 if (player->MovPos == 0)
14431 player->is_digging = FALSE;
14432 player->is_collecting = FALSE;
14435 if (player->MovPos == 0) // last pushing move finished
14436 player->is_pushing = FALSE;
14438 if (mode == DF_NO_PUSH) // player just stopped pushing
14440 player->is_switching = FALSE;
14441 player->push_delay = -1;
14443 return MP_NO_ACTION;
14446 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14447 old_element = Back[jx][jy];
14449 // in case of element dropped at player position, check background
14450 else if (Back[jx][jy] != EL_EMPTY &&
14451 game.engine_version >= VERSION_IDENT(2,2,0,0))
14452 old_element = Back[jx][jy];
14454 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14455 return MP_NO_ACTION; // field has no opening in this direction
14457 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction))
14458 return MP_NO_ACTION; // field has no opening in this direction
14460 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14464 Tile[jx][jy] = player->artwork_element;
14465 InitMovingField(jx, jy, MV_DOWN);
14466 Store[jx][jy] = EL_ACID;
14467 ContinueMoving(jx, jy);
14468 BuryPlayer(player);
14470 return MP_DONT_RUN_INTO;
14473 if (player_can_move && DONT_RUN_INTO(element))
14475 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14477 return MP_DONT_RUN_INTO;
14480 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14481 return MP_NO_ACTION;
14483 collect_count = element_info[element].collect_count_initial;
14485 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14486 return MP_NO_ACTION;
14488 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14489 player_can_move = player_can_move_or_snap;
14491 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14492 game.engine_version >= VERSION_IDENT(2,2,0,0))
14494 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14495 player->index_bit, dig_side);
14496 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14497 player->index_bit, dig_side);
14499 if (element == EL_DC_LANDMINE)
14502 if (Tile[x][y] != element) // field changed by snapping
14505 return MP_NO_ACTION;
14508 if (player->gravity && is_player && !player->is_auto_moving &&
14509 canFallDown(player) && move_direction != MV_DOWN &&
14510 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14511 return MP_NO_ACTION; // player cannot walk here due to gravity
14513 if (player_can_move &&
14514 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14516 int sound_element = SND_ELEMENT(element);
14517 int sound_action = ACTION_WALKING;
14519 if (IS_RND_GATE(element))
14521 if (!player->key[RND_GATE_NR(element)])
14522 return MP_NO_ACTION;
14524 else if (IS_RND_GATE_GRAY(element))
14526 if (!player->key[RND_GATE_GRAY_NR(element)])
14527 return MP_NO_ACTION;
14529 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14531 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14532 return MP_NO_ACTION;
14534 else if (element == EL_EXIT_OPEN ||
14535 element == EL_EM_EXIT_OPEN ||
14536 element == EL_EM_EXIT_OPENING ||
14537 element == EL_STEEL_EXIT_OPEN ||
14538 element == EL_EM_STEEL_EXIT_OPEN ||
14539 element == EL_EM_STEEL_EXIT_OPENING ||
14540 element == EL_SP_EXIT_OPEN ||
14541 element == EL_SP_EXIT_OPENING)
14543 sound_action = ACTION_PASSING; // player is passing exit
14545 else if (element == EL_EMPTY)
14547 sound_action = ACTION_MOVING; // nothing to walk on
14550 // play sound from background or player, whatever is available
14551 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14552 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14554 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14556 else if (player_can_move &&
14557 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14559 if (!ACCESS_FROM(element, opposite_direction))
14560 return MP_NO_ACTION; // field not accessible from this direction
14562 if (CAN_MOVE(element)) // only fixed elements can be passed!
14563 return MP_NO_ACTION;
14565 if (IS_EM_GATE(element))
14567 if (!player->key[EM_GATE_NR(element)])
14568 return MP_NO_ACTION;
14570 else if (IS_EM_GATE_GRAY(element))
14572 if (!player->key[EM_GATE_GRAY_NR(element)])
14573 return MP_NO_ACTION;
14575 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14577 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14578 return MP_NO_ACTION;
14580 else if (IS_EMC_GATE(element))
14582 if (!player->key[EMC_GATE_NR(element)])
14583 return MP_NO_ACTION;
14585 else if (IS_EMC_GATE_GRAY(element))
14587 if (!player->key[EMC_GATE_GRAY_NR(element)])
14588 return MP_NO_ACTION;
14590 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14592 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14593 return MP_NO_ACTION;
14595 else if (element == EL_DC_GATE_WHITE ||
14596 element == EL_DC_GATE_WHITE_GRAY ||
14597 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14599 if (player->num_white_keys == 0)
14600 return MP_NO_ACTION;
14602 player->num_white_keys--;
14604 else if (IS_SP_PORT(element))
14606 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14607 element == EL_SP_GRAVITY_PORT_RIGHT ||
14608 element == EL_SP_GRAVITY_PORT_UP ||
14609 element == EL_SP_GRAVITY_PORT_DOWN)
14610 player->gravity = !player->gravity;
14611 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14612 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14613 element == EL_SP_GRAVITY_ON_PORT_UP ||
14614 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14615 player->gravity = TRUE;
14616 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14617 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14618 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14619 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14620 player->gravity = FALSE;
14623 // automatically move to the next field with double speed
14624 player->programmed_action = move_direction;
14626 if (player->move_delay_reset_counter == 0)
14628 player->move_delay_reset_counter = 2; // two double speed steps
14630 DOUBLE_PLAYER_SPEED(player);
14633 PlayLevelSoundAction(x, y, ACTION_PASSING);
14635 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14639 if (mode != DF_SNAP)
14641 GfxElement[x][y] = GFX_ELEMENT(element);
14642 player->is_digging = TRUE;
14645 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14647 // use old behaviour for old levels (digging)
14648 if (!level.finish_dig_collect)
14650 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14651 player->index_bit, dig_side);
14653 // if digging triggered player relocation, finish digging tile
14654 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14655 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14658 if (mode == DF_SNAP)
14660 if (level.block_snap_field)
14661 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14663 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14665 // use old behaviour for old levels (snapping)
14666 if (!level.finish_dig_collect)
14667 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14668 player->index_bit, dig_side);
14671 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14675 if (is_player && mode != DF_SNAP)
14677 GfxElement[x][y] = element;
14678 player->is_collecting = TRUE;
14681 if (element == EL_SPEED_PILL)
14683 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14685 else if (element == EL_EXTRA_TIME && level.time > 0)
14687 TimeLeft += level.extra_time;
14689 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14691 DisplayGameControlValues();
14693 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14695 int shield_time = (element == EL_SHIELD_DEADLY ?
14696 level.shield_deadly_time :
14697 level.shield_normal_time);
14699 player->shield_normal_time_left += shield_time;
14700 if (element == EL_SHIELD_DEADLY)
14701 player->shield_deadly_time_left += shield_time;
14703 else if (element == EL_DYNAMITE ||
14704 element == EL_EM_DYNAMITE ||
14705 element == EL_SP_DISK_RED)
14707 if (player->inventory_size < MAX_INVENTORY_SIZE)
14708 player->inventory_element[player->inventory_size++] = element;
14710 DrawGameDoorValues();
14712 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14714 player->dynabomb_count++;
14715 player->dynabombs_left++;
14717 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14719 player->dynabomb_size++;
14721 else if (element == EL_DYNABOMB_INCREASE_POWER)
14723 player->dynabomb_xl = TRUE;
14725 else if (IS_KEY(element))
14727 player->key[KEY_NR(element)] = TRUE;
14729 DrawGameDoorValues();
14731 else if (element == EL_DC_KEY_WHITE)
14733 player->num_white_keys++;
14735 // display white keys?
14736 // DrawGameDoorValues();
14738 else if (IS_ENVELOPE(element))
14740 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14742 if (!wait_for_snapping)
14743 player->show_envelope = element;
14745 else if (element == EL_EMC_LENSES)
14747 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14749 RedrawAllInvisibleElementsForLenses();
14751 else if (element == EL_EMC_MAGNIFIER)
14753 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14755 RedrawAllInvisibleElementsForMagnifier();
14757 else if (IS_DROPPABLE(element) ||
14758 IS_THROWABLE(element)) // can be collected and dropped
14762 if (collect_count == 0)
14763 player->inventory_infinite_element = element;
14765 for (i = 0; i < collect_count; i++)
14766 if (player->inventory_size < MAX_INVENTORY_SIZE)
14767 player->inventory_element[player->inventory_size++] = element;
14769 DrawGameDoorValues();
14771 else if (collect_count > 0)
14773 game.gems_still_needed -= collect_count;
14774 if (game.gems_still_needed < 0)
14775 game.gems_still_needed = 0;
14777 game.snapshot.collected_item = TRUE;
14779 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14781 DisplayGameControlValues();
14784 RaiseScoreElement(element);
14785 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14787 // use old behaviour for old levels (collecting)
14788 if (!level.finish_dig_collect && is_player)
14790 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14791 player->index_bit, dig_side);
14793 // if collecting triggered player relocation, finish collecting tile
14794 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14795 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14798 if (mode == DF_SNAP)
14800 if (level.block_snap_field)
14801 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14803 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14805 // use old behaviour for old levels (snapping)
14806 if (!level.finish_dig_collect)
14807 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14808 player->index_bit, dig_side);
14811 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14813 if (mode == DF_SNAP && element != EL_BD_ROCK)
14814 return MP_NO_ACTION;
14816 if (CAN_FALL(element) && dy)
14817 return MP_NO_ACTION;
14819 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14820 !(element == EL_SPRING && level.use_spring_bug))
14821 return MP_NO_ACTION;
14823 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14824 ((move_direction & MV_VERTICAL &&
14825 ((element_info[element].move_pattern & MV_LEFT &&
14826 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14827 (element_info[element].move_pattern & MV_RIGHT &&
14828 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14829 (move_direction & MV_HORIZONTAL &&
14830 ((element_info[element].move_pattern & MV_UP &&
14831 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14832 (element_info[element].move_pattern & MV_DOWN &&
14833 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14834 return MP_NO_ACTION;
14836 // do not push elements already moving away faster than player
14837 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14838 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14839 return MP_NO_ACTION;
14841 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14843 if (player->push_delay_value == -1 || !player_was_pushing)
14844 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14846 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14848 if (player->push_delay_value == -1)
14849 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14851 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14853 if (!player->is_pushing)
14854 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14857 player->is_pushing = TRUE;
14858 player->is_active = TRUE;
14860 if (!(IN_LEV_FIELD(nextx, nexty) &&
14861 (IS_FREE(nextx, nexty) ||
14862 (IS_SB_ELEMENT(element) &&
14863 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14864 (IS_CUSTOM_ELEMENT(element) &&
14865 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14866 return MP_NO_ACTION;
14868 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14869 return MP_NO_ACTION;
14871 if (player->push_delay == -1) // new pushing; restart delay
14872 player->push_delay = 0;
14874 if (player->push_delay < player->push_delay_value &&
14875 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14876 element != EL_SPRING && element != EL_BALLOON)
14878 // make sure that there is no move delay before next try to push
14879 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14880 player->move_delay = 0;
14882 return MP_NO_ACTION;
14885 if (IS_CUSTOM_ELEMENT(element) &&
14886 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14888 if (!DigFieldByCE(nextx, nexty, element))
14889 return MP_NO_ACTION;
14892 if (IS_SB_ELEMENT(element))
14894 boolean sokoban_task_solved = FALSE;
14896 if (element == EL_SOKOBAN_FIELD_FULL)
14898 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14900 IncrementSokobanFieldsNeeded();
14901 IncrementSokobanObjectsNeeded();
14904 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14906 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14908 DecrementSokobanFieldsNeeded();
14909 DecrementSokobanObjectsNeeded();
14911 // sokoban object was pushed from empty field to sokoban field
14912 if (Back[x][y] == EL_EMPTY)
14913 sokoban_task_solved = TRUE;
14916 Tile[x][y] = EL_SOKOBAN_OBJECT;
14918 if (Back[x][y] == Back[nextx][nexty])
14919 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14920 else if (Back[x][y] != 0)
14921 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14924 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14927 if (sokoban_task_solved &&
14928 game.sokoban_fields_still_needed == 0 &&
14929 game.sokoban_objects_still_needed == 0 &&
14930 level.auto_exit_sokoban)
14932 game.players_still_needed = 0;
14936 PlaySound(SND_GAME_SOKOBAN_SOLVING);
14940 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14942 InitMovingField(x, y, move_direction);
14943 GfxAction[x][y] = ACTION_PUSHING;
14945 if (mode == DF_SNAP)
14946 ContinueMoving(x, y);
14948 MovPos[x][y] = (dx != 0 ? dx : dy);
14950 Pushed[x][y] = TRUE;
14951 Pushed[nextx][nexty] = TRUE;
14953 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14954 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14956 player->push_delay_value = -1; // get new value later
14958 // check for element change _after_ element has been pushed
14959 if (game.use_change_when_pushing_bug)
14961 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14962 player->index_bit, dig_side);
14963 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14964 player->index_bit, dig_side);
14967 else if (IS_SWITCHABLE(element))
14969 if (PLAYER_SWITCHING(player, x, y))
14971 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14972 player->index_bit, dig_side);
14977 player->is_switching = TRUE;
14978 player->switch_x = x;
14979 player->switch_y = y;
14981 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14983 if (element == EL_ROBOT_WHEEL)
14985 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14987 game.robot_wheel_x = x;
14988 game.robot_wheel_y = y;
14989 game.robot_wheel_active = TRUE;
14991 TEST_DrawLevelField(x, y);
14993 else if (element == EL_SP_TERMINAL)
14997 SCAN_PLAYFIELD(xx, yy)
14999 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
15003 else if (Tile[xx][yy] == EL_SP_TERMINAL)
15005 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
15007 ResetGfxAnimation(xx, yy);
15008 TEST_DrawLevelField(xx, yy);
15012 else if (IS_BELT_SWITCH(element))
15014 ToggleBeltSwitch(x, y);
15016 else if (element == EL_SWITCHGATE_SWITCH_UP ||
15017 element == EL_SWITCHGATE_SWITCH_DOWN ||
15018 element == EL_DC_SWITCHGATE_SWITCH_UP ||
15019 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
15021 ToggleSwitchgateSwitch();
15023 else if (element == EL_LIGHT_SWITCH ||
15024 element == EL_LIGHT_SWITCH_ACTIVE)
15026 ToggleLightSwitch(x, y);
15028 else if (element == EL_TIMEGATE_SWITCH ||
15029 element == EL_DC_TIMEGATE_SWITCH)
15031 ActivateTimegateSwitch(x, y);
15033 else if (element == EL_BALLOON_SWITCH_LEFT ||
15034 element == EL_BALLOON_SWITCH_RIGHT ||
15035 element == EL_BALLOON_SWITCH_UP ||
15036 element == EL_BALLOON_SWITCH_DOWN ||
15037 element == EL_BALLOON_SWITCH_NONE ||
15038 element == EL_BALLOON_SWITCH_ANY)
15040 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
15041 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
15042 element == EL_BALLOON_SWITCH_UP ? MV_UP :
15043 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
15044 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
15047 else if (element == EL_LAMP)
15049 Tile[x][y] = EL_LAMP_ACTIVE;
15050 game.lights_still_needed--;
15052 ResetGfxAnimation(x, y);
15053 TEST_DrawLevelField(x, y);
15055 else if (element == EL_TIME_ORB_FULL)
15057 Tile[x][y] = EL_TIME_ORB_EMPTY;
15059 if (level.time > 0 || level.use_time_orb_bug)
15061 TimeLeft += level.time_orb_time;
15062 game.no_level_time_limit = FALSE;
15064 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
15066 DisplayGameControlValues();
15069 ResetGfxAnimation(x, y);
15070 TEST_DrawLevelField(x, y);
15072 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
15073 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15077 game.ball_active = !game.ball_active;
15079 SCAN_PLAYFIELD(xx, yy)
15081 int e = Tile[xx][yy];
15083 if (game.ball_active)
15085 if (e == EL_EMC_MAGIC_BALL)
15086 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
15087 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
15088 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
15092 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
15093 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
15094 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
15095 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
15100 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15101 player->index_bit, dig_side);
15103 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15104 player->index_bit, dig_side);
15106 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15107 player->index_bit, dig_side);
15113 if (!PLAYER_SWITCHING(player, x, y))
15115 player->is_switching = TRUE;
15116 player->switch_x = x;
15117 player->switch_y = y;
15119 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
15120 player->index_bit, dig_side);
15121 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
15122 player->index_bit, dig_side);
15124 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
15125 player->index_bit, dig_side);
15126 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
15127 player->index_bit, dig_side);
15130 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
15131 player->index_bit, dig_side);
15132 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
15133 player->index_bit, dig_side);
15135 return MP_NO_ACTION;
15138 player->push_delay = -1;
15140 if (is_player) // function can also be called by EL_PENGUIN
15142 if (Tile[x][y] != element) // really digged/collected something
15144 player->is_collecting = !player->is_digging;
15145 player->is_active = TRUE;
15147 player->last_removed_element = element;
15154 static boolean DigFieldByCE(int x, int y, int digging_element)
15156 int element = Tile[x][y];
15158 if (!IS_FREE(x, y))
15160 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
15161 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
15164 // no element can dig solid indestructible elements
15165 if (IS_INDESTRUCTIBLE(element) &&
15166 !IS_DIGGABLE(element) &&
15167 !IS_COLLECTIBLE(element))
15170 if (AmoebaNr[x][y] &&
15171 (element == EL_AMOEBA_FULL ||
15172 element == EL_BD_AMOEBA ||
15173 element == EL_AMOEBA_GROWING))
15175 AmoebaCnt[AmoebaNr[x][y]]--;
15176 AmoebaCnt2[AmoebaNr[x][y]]--;
15179 if (IS_MOVING(x, y))
15180 RemoveMovingField(x, y);
15184 TEST_DrawLevelField(x, y);
15187 // if digged element was about to explode, prevent the explosion
15188 ExplodeField[x][y] = EX_TYPE_NONE;
15190 PlayLevelSoundAction(x, y, action);
15193 Store[x][y] = EL_EMPTY;
15195 // this makes it possible to leave the removed element again
15196 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15197 Store[x][y] = element;
15202 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15204 int jx = player->jx, jy = player->jy;
15205 int x = jx + dx, y = jy + dy;
15206 int snap_direction = (dx == -1 ? MV_LEFT :
15207 dx == +1 ? MV_RIGHT :
15209 dy == +1 ? MV_DOWN : MV_NONE);
15210 boolean can_continue_snapping = (level.continuous_snapping &&
15211 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15213 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15216 if (!player->active || !IN_LEV_FIELD(x, y))
15224 if (player->MovPos == 0)
15225 player->is_pushing = FALSE;
15227 player->is_snapping = FALSE;
15229 if (player->MovPos == 0)
15231 player->is_moving = FALSE;
15232 player->is_digging = FALSE;
15233 player->is_collecting = FALSE;
15239 // prevent snapping with already pressed snap key when not allowed
15240 if (player->is_snapping && !can_continue_snapping)
15243 player->MovDir = snap_direction;
15245 if (player->MovPos == 0)
15247 player->is_moving = FALSE;
15248 player->is_digging = FALSE;
15249 player->is_collecting = FALSE;
15252 player->is_dropping = FALSE;
15253 player->is_dropping_pressed = FALSE;
15254 player->drop_pressed_delay = 0;
15256 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15259 player->is_snapping = TRUE;
15260 player->is_active = TRUE;
15262 if (player->MovPos == 0)
15264 player->is_moving = FALSE;
15265 player->is_digging = FALSE;
15266 player->is_collecting = FALSE;
15269 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15270 TEST_DrawLevelField(player->last_jx, player->last_jy);
15272 TEST_DrawLevelField(x, y);
15277 static boolean DropElement(struct PlayerInfo *player)
15279 int old_element, new_element;
15280 int dropx = player->jx, dropy = player->jy;
15281 int drop_direction = player->MovDir;
15282 int drop_side = drop_direction;
15283 int drop_element = get_next_dropped_element(player);
15285 /* do not drop an element on top of another element; when holding drop key
15286 pressed without moving, dropped element must move away before the next
15287 element can be dropped (this is especially important if the next element
15288 is dynamite, which can be placed on background for historical reasons) */
15289 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15292 if (IS_THROWABLE(drop_element))
15294 dropx += GET_DX_FROM_DIR(drop_direction);
15295 dropy += GET_DY_FROM_DIR(drop_direction);
15297 if (!IN_LEV_FIELD(dropx, dropy))
15301 old_element = Tile[dropx][dropy]; // old element at dropping position
15302 new_element = drop_element; // default: no change when dropping
15304 // check if player is active, not moving and ready to drop
15305 if (!player->active || player->MovPos || player->drop_delay > 0)
15308 // check if player has anything that can be dropped
15309 if (new_element == EL_UNDEFINED)
15312 // only set if player has anything that can be dropped
15313 player->is_dropping_pressed = TRUE;
15315 // check if drop key was pressed long enough for EM style dynamite
15316 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15319 // check if anything can be dropped at the current position
15320 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15323 // collected custom elements can only be dropped on empty fields
15324 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15327 if (old_element != EL_EMPTY)
15328 Back[dropx][dropy] = old_element; // store old element on this field
15330 ResetGfxAnimation(dropx, dropy);
15331 ResetRandomAnimationValue(dropx, dropy);
15333 if (player->inventory_size > 0 ||
15334 player->inventory_infinite_element != EL_UNDEFINED)
15336 if (player->inventory_size > 0)
15338 player->inventory_size--;
15340 DrawGameDoorValues();
15342 if (new_element == EL_DYNAMITE)
15343 new_element = EL_DYNAMITE_ACTIVE;
15344 else if (new_element == EL_EM_DYNAMITE)
15345 new_element = EL_EM_DYNAMITE_ACTIVE;
15346 else if (new_element == EL_SP_DISK_RED)
15347 new_element = EL_SP_DISK_RED_ACTIVE;
15350 Tile[dropx][dropy] = new_element;
15352 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15353 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15354 el2img(Tile[dropx][dropy]), 0);
15356 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15358 // needed if previous element just changed to "empty" in the last frame
15359 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15361 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15362 player->index_bit, drop_side);
15363 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15365 player->index_bit, drop_side);
15367 TestIfElementTouchesCustomElement(dropx, dropy);
15369 else // player is dropping a dyna bomb
15371 player->dynabombs_left--;
15373 Tile[dropx][dropy] = new_element;
15375 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15376 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15377 el2img(Tile[dropx][dropy]), 0);
15379 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15382 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15383 InitField_WithBug1(dropx, dropy, FALSE);
15385 new_element = Tile[dropx][dropy]; // element might have changed
15387 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15388 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15390 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15391 MovDir[dropx][dropy] = drop_direction;
15393 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15395 // do not cause impact style collision by dropping elements that can fall
15396 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15399 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15400 player->is_dropping = TRUE;
15402 player->drop_pressed_delay = 0;
15403 player->is_dropping_pressed = FALSE;
15405 player->drop_x = dropx;
15406 player->drop_y = dropy;
15411 // ----------------------------------------------------------------------------
15412 // game sound playing functions
15413 // ----------------------------------------------------------------------------
15415 static int *loop_sound_frame = NULL;
15416 static int *loop_sound_volume = NULL;
15418 void InitPlayLevelSound(void)
15420 int num_sounds = getSoundListSize();
15422 checked_free(loop_sound_frame);
15423 checked_free(loop_sound_volume);
15425 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15426 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15429 static void PlayLevelSoundExt(int x, int y, int nr, boolean is_loop_sound)
15431 int sx = SCREENX(x), sy = SCREENY(y);
15432 int volume, stereo_position;
15433 int max_distance = 8;
15434 int type = (is_loop_sound ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15436 if ((!setup.sound_simple && !is_loop_sound) ||
15437 (!setup.sound_loops && is_loop_sound))
15440 if (!IN_LEV_FIELD(x, y) ||
15441 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15442 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15445 volume = SOUND_MAX_VOLUME;
15447 if (!IN_SCR_FIELD(sx, sy))
15449 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15450 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15452 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15455 stereo_position = (SOUND_MAX_LEFT +
15456 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15457 (SCR_FIELDX + 2 * max_distance));
15461 /* This assures that quieter loop sounds do not overwrite louder ones,
15462 while restarting sound volume comparison with each new game frame. */
15464 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15467 loop_sound_volume[nr] = volume;
15468 loop_sound_frame[nr] = FrameCounter;
15471 PlaySoundExt(nr, volume, stereo_position, type);
15474 static void PlayLevelSound(int x, int y, int nr)
15476 PlayLevelSoundExt(x, y, nr, IS_LOOP_SOUND(nr));
15479 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15481 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15482 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15483 y < LEVELY(BY1) ? LEVELY(BY1) :
15484 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15488 static void PlayLevelSoundAction(int x, int y, int action)
15490 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15493 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15495 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15497 if (sound_effect != SND_UNDEFINED)
15498 PlayLevelSound(x, y, sound_effect);
15501 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15504 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15506 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15507 PlayLevelSound(x, y, sound_effect);
15510 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15512 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15514 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15515 PlayLevelSound(x, y, sound_effect);
15518 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15520 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15522 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15523 StopSound(sound_effect);
15526 static int getLevelMusicNr(void)
15528 int level_pos = level_nr - leveldir_current->first_level;
15530 if (levelset.music[level_nr] != MUS_UNDEFINED)
15531 return levelset.music[level_nr]; // from config file
15533 return MAP_NOCONF_MUSIC(level_pos); // from music dir
15536 static void FadeLevelSounds(void)
15541 static void FadeLevelMusic(void)
15543 int music_nr = getLevelMusicNr();
15544 char *curr_music = getCurrentlyPlayingMusicFilename();
15545 char *next_music = getMusicInfoEntryFilename(music_nr);
15547 if (!strEqual(curr_music, next_music))
15551 void FadeLevelSoundsAndMusic(void)
15557 static void PlayLevelMusic(void)
15559 int music_nr = getLevelMusicNr();
15560 char *curr_music = getCurrentlyPlayingMusicFilename();
15561 char *next_music = getMusicInfoEntryFilename(music_nr);
15563 if (!strEqual(curr_music, next_music))
15564 PlayMusicLoop(music_nr);
15567 static int getSoundAction_BD(int sample)
15571 case GD_S_STONE_PUSHING:
15572 case GD_S_MEGA_STONE_PUSHING:
15573 case GD_S_FLYING_STONE_PUSHING:
15574 case GD_S_WAITING_STONE_PUSHING:
15575 case GD_S_CHASING_STONE_PUSHING:
15576 case GD_S_NUT_PUSHING:
15577 case GD_S_NITRO_PACK_PUSHING:
15578 case GD_S_BLADDER_PUSHING:
15579 case GD_S_BOX_PUSHING:
15580 return ACTION_PUSHING;
15582 case GD_S_STONE_FALLING:
15583 case GD_S_MEGA_STONE_FALLING:
15584 case GD_S_FLYING_STONE_FALLING:
15585 case GD_S_NUT_FALLING:
15586 case GD_S_DIRT_BALL_FALLING:
15587 case GD_S_DIRT_LOOSE_FALLING:
15588 case GD_S_NITRO_PACK_FALLING:
15589 case GD_S_FALLING_WALL_FALLING:
15590 return ACTION_FALLING;
15592 case GD_S_STONE_IMPACT:
15593 case GD_S_MEGA_STONE_IMPACT:
15594 case GD_S_FLYING_STONE_IMPACT:
15595 case GD_S_NUT_IMPACT:
15596 case GD_S_DIRT_BALL_IMPACT:
15597 case GD_S_DIRT_LOOSE_IMPACT:
15598 case GD_S_NITRO_PACK_IMPACT:
15599 case GD_S_FALLING_WALL_IMPACT:
15600 return ACTION_IMPACT;
15602 case GD_S_NUT_CRACKING:
15603 return ACTION_BREAKING;
15605 case GD_S_EXPANDING_WALL:
15606 case GD_S_WALL_REAPPEARING:
15609 case GD_S_ACID_SPREADING:
15610 return ACTION_GROWING;
15612 case GD_S_DIAMOND_COLLECTING:
15613 case GD_S_FLYING_DIAMOND_COLLECTING:
15614 case GD_S_SKELETON_COLLECTING:
15615 case GD_S_PNEUMATIC_COLLECTING:
15616 case GD_S_BOMB_COLLECTING:
15617 case GD_S_CLOCK_COLLECTING:
15618 case GD_S_SWEET_COLLECTING:
15619 case GD_S_KEY_COLLECTING:
15620 case GD_S_DIAMOND_KEY_COLLECTING:
15621 return ACTION_COLLECTING;
15623 case GD_S_BOMB_PLACING:
15624 case GD_S_REPLICATOR:
15625 return ACTION_DROPPING;
15627 case GD_S_BLADDER_MOVING:
15628 return ACTION_MOVING;
15630 case GD_S_BLADDER_SPENDER:
15631 case GD_S_BLADDER_CONVERTING:
15632 case GD_S_GRAVITY_CHANGING:
15633 return ACTION_CHANGING;
15635 case GD_S_BITER_EATING:
15636 return ACTION_EATING;
15638 case GD_S_DOOR_OPENING:
15639 case GD_S_CRACKING:
15640 return ACTION_OPENING;
15642 case GD_S_DIRT_WALKING:
15643 return ACTION_DIGGING;
15645 case GD_S_EMPTY_WALKING:
15646 return ACTION_WALKING;
15648 case GD_S_SWITCH_BITER:
15649 case GD_S_SWITCH_CREATURES:
15650 case GD_S_SWITCH_GRAVITY:
15651 case GD_S_SWITCH_EXPANDING:
15652 case GD_S_SWITCH_CONVEYOR:
15653 case GD_S_SWITCH_REPLICATOR:
15654 case GD_S_STIRRING:
15655 return ACTION_ACTIVATING;
15657 case GD_S_TELEPORTER:
15658 return ACTION_PASSING;
15660 case GD_S_EXPLODING:
15661 case GD_S_BOMB_EXPLODING:
15662 case GD_S_GHOST_EXPLODING:
15663 case GD_S_VOODOO_EXPLODING:
15664 case GD_S_NITRO_PACK_EXPLODING:
15665 return ACTION_EXPLODING;
15667 case GD_S_COVERING:
15669 case GD_S_MAGIC_WALL:
15670 case GD_S_PNEUMATIC_HAMMER:
15672 return ACTION_ACTIVE;
15674 case GD_S_DIAMOND_FALLING_RANDOM:
15675 case GD_S_DIAMOND_FALLING_1:
15676 case GD_S_DIAMOND_FALLING_2:
15677 case GD_S_DIAMOND_FALLING_3:
15678 case GD_S_DIAMOND_FALLING_4:
15679 case GD_S_DIAMOND_FALLING_5:
15680 case GD_S_DIAMOND_FALLING_6:
15681 case GD_S_DIAMOND_FALLING_7:
15682 case GD_S_DIAMOND_FALLING_8:
15683 case GD_S_DIAMOND_IMPACT_RANDOM:
15684 case GD_S_DIAMOND_IMPACT_1:
15685 case GD_S_DIAMOND_IMPACT_2:
15686 case GD_S_DIAMOND_IMPACT_3:
15687 case GD_S_DIAMOND_IMPACT_4:
15688 case GD_S_DIAMOND_IMPACT_5:
15689 case GD_S_DIAMOND_IMPACT_6:
15690 case GD_S_DIAMOND_IMPACT_7:
15691 case GD_S_DIAMOND_IMPACT_8:
15692 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15693 case GD_S_FLYING_DIAMOND_FALLING_1:
15694 case GD_S_FLYING_DIAMOND_FALLING_2:
15695 case GD_S_FLYING_DIAMOND_FALLING_3:
15696 case GD_S_FLYING_DIAMOND_FALLING_4:
15697 case GD_S_FLYING_DIAMOND_FALLING_5:
15698 case GD_S_FLYING_DIAMOND_FALLING_6:
15699 case GD_S_FLYING_DIAMOND_FALLING_7:
15700 case GD_S_FLYING_DIAMOND_FALLING_8:
15701 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15702 case GD_S_FLYING_DIAMOND_IMPACT_1:
15703 case GD_S_FLYING_DIAMOND_IMPACT_2:
15704 case GD_S_FLYING_DIAMOND_IMPACT_3:
15705 case GD_S_FLYING_DIAMOND_IMPACT_4:
15706 case GD_S_FLYING_DIAMOND_IMPACT_5:
15707 case GD_S_FLYING_DIAMOND_IMPACT_6:
15708 case GD_S_FLYING_DIAMOND_IMPACT_7:
15709 case GD_S_FLYING_DIAMOND_IMPACT_8:
15710 case GD_S_TIMEOUT_0:
15711 case GD_S_TIMEOUT_1:
15712 case GD_S_TIMEOUT_2:
15713 case GD_S_TIMEOUT_3:
15714 case GD_S_TIMEOUT_4:
15715 case GD_S_TIMEOUT_5:
15716 case GD_S_TIMEOUT_6:
15717 case GD_S_TIMEOUT_7:
15718 case GD_S_TIMEOUT_8:
15719 case GD_S_TIMEOUT_9:
15720 case GD_S_TIMEOUT_10:
15721 case GD_S_BONUS_LIFE:
15722 // trigger special post-processing (and force sound to be non-looping)
15723 return ACTION_OTHER;
15725 case GD_S_AMOEBA_MAGIC:
15726 case GD_S_FINISHED:
15727 // trigger special post-processing (and force sound to be looping)
15728 return ACTION_DEFAULT;
15731 return ACTION_DEFAULT;
15735 static int getSoundEffect_BD(int element_bd, int sample)
15737 int sound_action = getSoundAction_BD(sample);
15738 int sound_effect = element_info[SND_ELEMENT(element_bd)].sound[sound_action];
15742 if (sound_action != ACTION_OTHER &&
15743 sound_action != ACTION_DEFAULT)
15744 return sound_effect;
15746 // special post-processing for some sounds
15749 case GD_S_DIAMOND_FALLING_RANDOM:
15750 case GD_S_DIAMOND_FALLING_1:
15751 case GD_S_DIAMOND_FALLING_2:
15752 case GD_S_DIAMOND_FALLING_3:
15753 case GD_S_DIAMOND_FALLING_4:
15754 case GD_S_DIAMOND_FALLING_5:
15755 case GD_S_DIAMOND_FALLING_6:
15756 case GD_S_DIAMOND_FALLING_7:
15757 case GD_S_DIAMOND_FALLING_8:
15758 nr = (sample == GD_S_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15759 sample - GD_S_DIAMOND_FALLING_1);
15760 sound_effect = SND_BD_DIAMOND_FALLING_RANDOM_1 + nr;
15762 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15763 sound_effect = SND_BD_DIAMOND_FALLING;
15766 case GD_S_DIAMOND_IMPACT_RANDOM:
15767 case GD_S_DIAMOND_IMPACT_1:
15768 case GD_S_DIAMOND_IMPACT_2:
15769 case GD_S_DIAMOND_IMPACT_3:
15770 case GD_S_DIAMOND_IMPACT_4:
15771 case GD_S_DIAMOND_IMPACT_5:
15772 case GD_S_DIAMOND_IMPACT_6:
15773 case GD_S_DIAMOND_IMPACT_7:
15774 case GD_S_DIAMOND_IMPACT_8:
15775 nr = (sample == GD_S_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15776 sample - GD_S_DIAMOND_IMPACT_1);
15777 sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr;
15779 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15780 sound_effect = SND_BD_DIAMOND_IMPACT;
15783 case GD_S_FLYING_DIAMOND_FALLING_RANDOM:
15784 case GD_S_FLYING_DIAMOND_FALLING_1:
15785 case GD_S_FLYING_DIAMOND_FALLING_2:
15786 case GD_S_FLYING_DIAMOND_FALLING_3:
15787 case GD_S_FLYING_DIAMOND_FALLING_4:
15788 case GD_S_FLYING_DIAMOND_FALLING_5:
15789 case GD_S_FLYING_DIAMOND_FALLING_6:
15790 case GD_S_FLYING_DIAMOND_FALLING_7:
15791 case GD_S_FLYING_DIAMOND_FALLING_8:
15792 nr = (sample == GD_S_FLYING_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) :
15793 sample - GD_S_FLYING_DIAMOND_FALLING_1);
15794 sound_effect = SND_BD_FLYING_DIAMOND_FALLING_RANDOM_1 + nr;
15796 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15797 sound_effect = SND_BD_FLYING_DIAMOND_FALLING;
15800 case GD_S_FLYING_DIAMOND_IMPACT_RANDOM:
15801 case GD_S_FLYING_DIAMOND_IMPACT_1:
15802 case GD_S_FLYING_DIAMOND_IMPACT_2:
15803 case GD_S_FLYING_DIAMOND_IMPACT_3:
15804 case GD_S_FLYING_DIAMOND_IMPACT_4:
15805 case GD_S_FLYING_DIAMOND_IMPACT_5:
15806 case GD_S_FLYING_DIAMOND_IMPACT_6:
15807 case GD_S_FLYING_DIAMOND_IMPACT_7:
15808 case GD_S_FLYING_DIAMOND_IMPACT_8:
15809 nr = (sample == GD_S_FLYING_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) :
15810 sample - GD_S_FLYING_DIAMOND_IMPACT_1);
15811 sound_effect = SND_BD_FLYING_DIAMOND_IMPACT_RANDOM_1 + nr;
15813 if (getSoundInfoEntryFilename(sound_effect) == NULL)
15814 sound_effect = SND_BD_FLYING_DIAMOND_IMPACT;
15817 case GD_S_TIMEOUT_0:
15818 case GD_S_TIMEOUT_1:
15819 case GD_S_TIMEOUT_2:
15820 case GD_S_TIMEOUT_3:
15821 case GD_S_TIMEOUT_4:
15822 case GD_S_TIMEOUT_5:
15823 case GD_S_TIMEOUT_6:
15824 case GD_S_TIMEOUT_7:
15825 case GD_S_TIMEOUT_8:
15826 case GD_S_TIMEOUT_9:
15827 case GD_S_TIMEOUT_10:
15828 nr = sample - GD_S_TIMEOUT_0;
15829 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME_0 + nr;
15831 if (getSoundInfoEntryFilename(sound_effect) == NULL && sample != GD_S_TIMEOUT_0)
15832 sound_effect = SND_GAME_RUNNING_OUT_OF_TIME;
15835 case GD_S_BONUS_LIFE:
15836 sound_effect = SND_GAME_HEALTH_BONUS;
15839 case GD_S_AMOEBA_MAGIC:
15840 sound_effect = SND_BD_AMOEBA_OTHER;
15843 case GD_S_FINISHED:
15844 sound_effect = SND_GAME_LEVELTIME_BONUS;
15848 sound_effect = SND_UNDEFINED;
15852 return sound_effect;
15855 void PlayLevelSound_BD(int xx, int yy, int element_bd, int sample)
15857 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15858 int sound_effect = getSoundEffect_BD(element, sample);
15859 int sound_action = getSoundAction_BD(sample);
15860 boolean is_loop_sound = IS_LOOP_SOUND(sound_effect);
15862 int x = xx - offset;
15863 int y = yy - offset;
15865 // some sound actions are always looping in native BD game engine
15866 if (sound_action == ACTION_DEFAULT)
15867 is_loop_sound = TRUE;
15869 // some sound actions are always non-looping in native BD game engine
15870 if (sound_action == ACTION_FALLING ||
15871 sound_action == ACTION_MOVING ||
15872 sound_action == ACTION_OTHER)
15873 is_loop_sound = FALSE;
15875 if (sound_effect != SND_UNDEFINED)
15876 PlayLevelSoundExt(x, y, sound_effect, is_loop_sound);
15879 void StopSound_BD(int element_bd, int sample)
15881 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15882 int sound_effect = getSoundEffect_BD(element, sample);
15884 if (sound_effect != SND_UNDEFINED)
15885 StopSound(sound_effect);
15888 boolean isSoundPlaying_BD(int element_bd, int sample)
15890 int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0);
15891 int sound_effect = getSoundEffect_BD(element, sample);
15893 if (sound_effect != SND_UNDEFINED)
15894 return isSoundPlaying(sound_effect);
15899 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15901 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15903 int x = xx - offset;
15904 int y = yy - offset;
15909 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15913 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15917 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15921 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15925 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15929 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15933 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15936 case SOUND_android_clone:
15937 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15940 case SOUND_android_move:
15941 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15945 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15949 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15953 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15956 case SOUND_eater_eat:
15957 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15961 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15964 case SOUND_collect:
15965 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15968 case SOUND_diamond:
15969 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15973 // !!! CHECK THIS !!!
15975 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15977 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15981 case SOUND_wonderfall:
15982 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15986 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15990 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15994 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15998 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
16002 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16006 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
16010 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16014 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16017 case SOUND_exit_open:
16018 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
16021 case SOUND_exit_leave:
16022 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
16025 case SOUND_dynamite:
16026 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
16030 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16034 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
16038 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
16042 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
16046 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
16050 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
16054 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
16059 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
16061 int element = map_element_SP_to_RND(element_sp);
16062 int action = map_action_SP_to_RND(action_sp);
16063 int offset = (setup.sp_show_border_elements ? 0 : 1);
16064 int x = xx - offset;
16065 int y = yy - offset;
16067 PlayLevelSoundElementAction(x, y, element, action);
16070 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
16072 int element = map_element_MM_to_RND(element_mm);
16073 int action = map_action_MM_to_RND(action_mm);
16075 int x = xx - offset;
16076 int y = yy - offset;
16078 if (!IS_MM_ELEMENT(element))
16079 element = EL_MM_DEFAULT;
16081 PlayLevelSoundElementAction(x, y, element, action);
16084 void PlaySound_MM(int sound_mm)
16086 int sound = map_sound_MM_to_RND(sound_mm);
16088 if (sound == SND_UNDEFINED)
16094 void PlaySoundLoop_MM(int sound_mm)
16096 int sound = map_sound_MM_to_RND(sound_mm);
16098 if (sound == SND_UNDEFINED)
16101 PlaySoundLoop(sound);
16104 void StopSound_MM(int sound_mm)
16106 int sound = map_sound_MM_to_RND(sound_mm);
16108 if (sound == SND_UNDEFINED)
16114 void RaiseScore(int value)
16116 game.score += value;
16118 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
16120 DisplayGameControlValues();
16123 void RaiseScoreElement(int element)
16128 case EL_BD_DIAMOND:
16129 case EL_EMERALD_YELLOW:
16130 case EL_EMERALD_RED:
16131 case EL_EMERALD_PURPLE:
16132 case EL_SP_INFOTRON:
16133 RaiseScore(level.score[SC_EMERALD]);
16136 RaiseScore(level.score[SC_DIAMOND]);
16139 RaiseScore(level.score[SC_CRYSTAL]);
16142 RaiseScore(level.score[SC_PEARL]);
16145 case EL_BD_BUTTERFLY:
16146 case EL_SP_ELECTRON:
16147 RaiseScore(level.score[SC_BUG]);
16150 case EL_BD_FIREFLY:
16151 case EL_SP_SNIKSNAK:
16152 RaiseScore(level.score[SC_SPACESHIP]);
16155 case EL_DARK_YAMYAM:
16156 RaiseScore(level.score[SC_YAMYAM]);
16159 RaiseScore(level.score[SC_ROBOT]);
16162 RaiseScore(level.score[SC_PACMAN]);
16165 RaiseScore(level.score[SC_NUT]);
16168 case EL_EM_DYNAMITE:
16169 case EL_SP_DISK_RED:
16170 case EL_DYNABOMB_INCREASE_NUMBER:
16171 case EL_DYNABOMB_INCREASE_SIZE:
16172 case EL_DYNABOMB_INCREASE_POWER:
16173 RaiseScore(level.score[SC_DYNAMITE]);
16175 case EL_SHIELD_NORMAL:
16176 case EL_SHIELD_DEADLY:
16177 RaiseScore(level.score[SC_SHIELD]);
16179 case EL_EXTRA_TIME:
16180 RaiseScore(level.extra_time_score);
16194 case EL_DC_KEY_WHITE:
16195 RaiseScore(level.score[SC_KEY]);
16198 RaiseScore(element_info[element].collect_score);
16203 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
16205 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
16209 // prevent short reactivation of overlay buttons while closing door
16210 SetOverlayActive(FALSE);
16211 UnmapGameButtons();
16213 // door may still be open due to skipped or envelope style request
16214 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
16217 if (network.enabled)
16219 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
16223 // when using BD game engine, cover screen before fading out
16224 if (!quick_quit && level.game_engine_type == GAME_ENGINE_TYPE_BD)
16225 game_bd.cover_screen = TRUE;
16228 FadeSkipNextFadeIn();
16230 SetGameStatus(GAME_MODE_MAIN);
16235 else // continue playing the game
16237 if (tape.playing && tape.deactivate_display)
16238 TapeDeactivateDisplayOff(TRUE);
16240 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
16242 if (tape.playing && tape.deactivate_display)
16243 TapeDeactivateDisplayOn();
16247 void RequestQuitGame(boolean escape_key_pressed)
16249 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
16250 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
16251 level_editor_test_game);
16252 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
16253 quick_quit || score_info_tape_play);
16255 RequestQuitGameExt(skip_request, quick_quit,
16256 "Do you really want to quit the game?");
16259 static char *getRestartGameMessage(void)
16261 boolean play_again = hasStartedNetworkGame();
16262 static char message[MAX_OUTPUT_LINESIZE];
16263 char *game_over_text = "Game over!";
16264 char *play_again_text = " Play it again?";
16266 if (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
16267 game_mm.game_over_message != NULL)
16268 game_over_text = game_mm.game_over_message;
16270 snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text,
16271 (play_again ? play_again_text : ""));
16276 static void RequestRestartGame(void)
16278 char *message = getRestartGameMessage();
16279 boolean has_started_game = hasStartedNetworkGame();
16280 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
16281 int door_state = DOOR_CLOSE_1;
16283 if (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game)
16285 CloseDoor(door_state);
16287 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16291 // if game was invoked from level editor, also close tape recorder door
16292 if (level_editor_test_game)
16293 door_state = DOOR_CLOSE_ALL;
16295 CloseDoor(door_state);
16297 SetGameStatus(GAME_MODE_MAIN);
16303 boolean CheckRestartGame(void)
16305 static int game_over_delay = 0;
16306 int game_over_delay_value = 50;
16307 boolean game_over = checkGameFailed();
16311 game_over_delay = game_over_delay_value;
16316 if (game_over_delay > 0)
16318 if (game_over_delay == game_over_delay_value / 2)
16319 PlaySound(SND_GAME_LOSING);
16326 // do not ask to play again if request dialog is already active
16327 if (game.request_active)
16330 // do not ask to play again if request dialog already handled
16331 if (game.RestartGameRequested)
16334 // do not ask to play again if game was never actually played
16335 if (!game.GamePlayed)
16338 // do not ask to play again if this was disabled in setup menu
16339 if (!setup.ask_on_game_over)
16342 game.RestartGameRequested = TRUE;
16344 RequestRestartGame();
16349 boolean checkGameRunning(void)
16351 if (game_status != GAME_MODE_PLAYING)
16354 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGameRunning_BD())
16360 boolean checkGamePlaying(void)
16362 if (game_status != GAME_MODE_PLAYING)
16365 if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGamePlaying_BD())
16371 boolean checkGameSolved(void)
16373 // set for all game engines if level was solved
16374 return game.LevelSolved_GameEnd;
16377 boolean checkGameFailed(void)
16379 if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
16380 return (game_bd.game_over && !game_bd.level_solved);
16381 else if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16382 return (game_em.game_over && !game_em.level_solved);
16383 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16384 return (game_sp.game_over && !game_sp.level_solved);
16385 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16386 return (game_mm.game_over && !game_mm.level_solved);
16387 else // GAME_ENGINE_TYPE_RND
16388 return (game.GameOver && !game.LevelSolved);
16391 boolean checkGameEnded(void)
16393 return (checkGameSolved() || checkGameFailed());
16397 // ----------------------------------------------------------------------------
16398 // random generator functions
16399 // ----------------------------------------------------------------------------
16401 unsigned int InitEngineRandom_RND(int seed)
16403 game.num_random_calls = 0;
16405 return InitEngineRandom(seed);
16408 unsigned int RND(int max)
16412 game.num_random_calls++;
16414 return GetEngineRandom(max);
16421 // ----------------------------------------------------------------------------
16422 // game engine snapshot handling functions
16423 // ----------------------------------------------------------------------------
16425 struct EngineSnapshotInfo
16427 // runtime values for custom element collect score
16428 int collect_score[NUM_CUSTOM_ELEMENTS];
16430 // runtime values for group element choice position
16431 int choice_pos[NUM_GROUP_ELEMENTS];
16433 // runtime values for belt position animations
16434 int belt_graphic[4][NUM_BELT_PARTS];
16435 int belt_anim_mode[4][NUM_BELT_PARTS];
16438 static struct EngineSnapshotInfo engine_snapshot_rnd;
16439 static char *snapshot_level_identifier = NULL;
16440 static int snapshot_level_nr = -1;
16442 static void SaveEngineSnapshotValues_RND(void)
16444 static int belt_base_active_element[4] =
16446 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
16447 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
16448 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
16449 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
16453 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16455 int element = EL_CUSTOM_START + i;
16457 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
16460 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16462 int element = EL_GROUP_START + i;
16464 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
16467 for (i = 0; i < 4; i++)
16469 for (j = 0; j < NUM_BELT_PARTS; j++)
16471 int element = belt_base_active_element[i] + j;
16472 int graphic = el2img(element);
16473 int anim_mode = graphic_info[graphic].anim_mode;
16475 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
16476 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
16481 static void LoadEngineSnapshotValues_RND(void)
16483 unsigned int num_random_calls = game.num_random_calls;
16486 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
16488 int element = EL_CUSTOM_START + i;
16490 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
16493 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
16495 int element = EL_GROUP_START + i;
16497 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
16500 for (i = 0; i < 4; i++)
16502 for (j = 0; j < NUM_BELT_PARTS; j++)
16504 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
16505 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
16507 graphic_info[graphic].anim_mode = anim_mode;
16511 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16513 InitRND(tape.random_seed);
16514 for (i = 0; i < num_random_calls; i++)
16518 if (game.num_random_calls != num_random_calls)
16520 Error("number of random calls out of sync");
16521 Error("number of random calls should be %d", num_random_calls);
16522 Error("number of random calls is %d", game.num_random_calls);
16524 Fail("this should not happen -- please debug");
16528 void FreeEngineSnapshotSingle(void)
16530 FreeSnapshotSingle();
16532 setString(&snapshot_level_identifier, NULL);
16533 snapshot_level_nr = -1;
16536 void FreeEngineSnapshotList(void)
16538 FreeSnapshotList();
16541 static ListNode *SaveEngineSnapshotBuffers(void)
16543 ListNode *buffers = NULL;
16545 // copy some special values to a structure better suited for the snapshot
16547 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16548 SaveEngineSnapshotValues_RND();
16549 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16550 SaveEngineSnapshotValues_EM();
16551 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16552 SaveEngineSnapshotValues_SP(&buffers);
16553 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16554 SaveEngineSnapshotValues_MM();
16556 // save values stored in special snapshot structure
16558 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16559 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
16560 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16561 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
16562 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16563 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
16564 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16565 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
16567 // save further RND engine values
16569 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
16570 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
16571 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
16573 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
16574 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
16575 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
16576 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16577 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTimeFrames));
16578 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16580 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16581 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16582 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16584 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16586 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16587 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16589 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16590 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16591 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16592 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16593 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16594 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16595 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16596 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16597 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16598 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16599 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16600 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16601 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16602 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16603 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16604 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16605 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16606 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16608 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16609 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16611 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16612 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16613 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16615 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16616 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16618 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16619 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16620 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16621 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16622 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16623 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16625 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16626 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16629 ListNode *node = engine_snapshot_list_rnd;
16632 while (node != NULL)
16634 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16639 Debug("game:playing:SaveEngineSnapshotBuffers",
16640 "size of engine snapshot: %d bytes", num_bytes);
16646 void SaveEngineSnapshotSingle(void)
16648 ListNode *buffers = SaveEngineSnapshotBuffers();
16650 // finally save all snapshot buffers to single snapshot
16651 SaveSnapshotSingle(buffers);
16653 // save level identification information
16654 setString(&snapshot_level_identifier, leveldir_current->identifier);
16655 snapshot_level_nr = level_nr;
16658 boolean CheckSaveEngineSnapshotToList(void)
16660 boolean save_snapshot =
16661 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16662 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16663 game.snapshot.changed_action) ||
16664 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16665 game.snapshot.collected_item));
16667 game.snapshot.changed_action = FALSE;
16668 game.snapshot.collected_item = FALSE;
16669 game.snapshot.save_snapshot = save_snapshot;
16671 return save_snapshot;
16674 void SaveEngineSnapshotToList(void)
16676 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16680 ListNode *buffers = SaveEngineSnapshotBuffers();
16682 // finally save all snapshot buffers to snapshot list
16683 SaveSnapshotToList(buffers);
16686 void SaveEngineSnapshotToListInitial(void)
16688 FreeEngineSnapshotList();
16690 SaveEngineSnapshotToList();
16693 static void LoadEngineSnapshotValues(void)
16695 // restore special values from snapshot structure
16697 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16698 LoadEngineSnapshotValues_RND();
16699 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16700 LoadEngineSnapshotValues_EM();
16701 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16702 LoadEngineSnapshotValues_SP();
16703 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16704 LoadEngineSnapshotValues_MM();
16707 void LoadEngineSnapshotSingle(void)
16709 LoadSnapshotSingle();
16711 LoadEngineSnapshotValues();
16714 static void LoadEngineSnapshot_Undo(int steps)
16716 LoadSnapshotFromList_Older(steps);
16718 LoadEngineSnapshotValues();
16721 static void LoadEngineSnapshot_Redo(int steps)
16723 LoadSnapshotFromList_Newer(steps);
16725 LoadEngineSnapshotValues();
16728 boolean CheckEngineSnapshotSingle(void)
16730 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16731 snapshot_level_nr == level_nr);
16734 boolean CheckEngineSnapshotList(void)
16736 return CheckSnapshotList();
16740 // ---------- new game button stuff -------------------------------------------
16747 boolean *setup_value;
16748 boolean allowed_on_tape;
16749 boolean is_touch_button;
16751 } gamebutton_info[NUM_GAME_BUTTONS] =
16754 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16755 GAME_CTRL_ID_STOP, NULL,
16756 TRUE, FALSE, "stop game"
16759 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16760 GAME_CTRL_ID_PAUSE, NULL,
16761 TRUE, FALSE, "pause game"
16764 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16765 GAME_CTRL_ID_PLAY, NULL,
16766 TRUE, FALSE, "play game"
16769 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16770 GAME_CTRL_ID_UNDO, NULL,
16771 TRUE, FALSE, "undo step"
16774 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16775 GAME_CTRL_ID_REDO, NULL,
16776 TRUE, FALSE, "redo step"
16779 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16780 GAME_CTRL_ID_SAVE, NULL,
16781 TRUE, FALSE, "save game"
16784 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16785 GAME_CTRL_ID_PAUSE2, NULL,
16786 TRUE, FALSE, "pause game"
16789 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16790 GAME_CTRL_ID_LOAD, NULL,
16791 TRUE, FALSE, "load game"
16794 IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart,
16795 GAME_CTRL_ID_RESTART, NULL,
16796 TRUE, FALSE, "restart game"
16799 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16800 GAME_CTRL_ID_PANEL_STOP, NULL,
16801 FALSE, FALSE, "stop game"
16804 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16805 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16806 FALSE, FALSE, "pause game"
16809 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16810 GAME_CTRL_ID_PANEL_PLAY, NULL,
16811 FALSE, FALSE, "play game"
16814 IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart,
16815 GAME_CTRL_ID_PANEL_RESTART, NULL,
16816 FALSE, FALSE, "restart game"
16819 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16820 GAME_CTRL_ID_TOUCH_STOP, NULL,
16821 FALSE, TRUE, "stop game"
16824 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16825 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16826 FALSE, TRUE, "pause game"
16829 IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart,
16830 GAME_CTRL_ID_TOUCH_RESTART, NULL,
16831 FALSE, TRUE, "restart game"
16834 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16835 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16836 TRUE, FALSE, "background music on/off"
16839 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16840 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16841 TRUE, FALSE, "sound loops on/off"
16844 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16845 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16846 TRUE, FALSE, "normal sounds on/off"
16849 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16850 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16851 FALSE, FALSE, "background music on/off"
16854 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16855 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16856 FALSE, FALSE, "sound loops on/off"
16859 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16860 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16861 FALSE, FALSE, "normal sounds on/off"
16865 void CreateGameButtons(void)
16869 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16871 int graphic = gamebutton_info[i].graphic;
16872 struct GraphicInfo *gfx = &graphic_info[graphic];
16873 struct XY *pos = gamebutton_info[i].pos;
16874 struct GadgetInfo *gi;
16877 unsigned int event_mask;
16878 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16879 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16880 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16881 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16882 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16883 int gd_x = gfx->src_x;
16884 int gd_y = gfx->src_y;
16885 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16886 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16887 int gd_xa = gfx->src_x + gfx->active_xoffset;
16888 int gd_ya = gfx->src_y + gfx->active_yoffset;
16889 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16890 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16891 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16892 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16895 // do not use touch buttons if overlay touch buttons are disabled
16896 if (is_touch_button && !setup.touch.overlay_buttons)
16899 if (gfx->bitmap == NULL)
16901 game_gadget[id] = NULL;
16906 if (id == GAME_CTRL_ID_STOP ||
16907 id == GAME_CTRL_ID_PANEL_STOP ||
16908 id == GAME_CTRL_ID_TOUCH_STOP ||
16909 id == GAME_CTRL_ID_PLAY ||
16910 id == GAME_CTRL_ID_PANEL_PLAY ||
16911 id == GAME_CTRL_ID_SAVE ||
16912 id == GAME_CTRL_ID_LOAD ||
16913 id == GAME_CTRL_ID_RESTART ||
16914 id == GAME_CTRL_ID_PANEL_RESTART ||
16915 id == GAME_CTRL_ID_TOUCH_RESTART)
16917 button_type = GD_TYPE_NORMAL_BUTTON;
16919 event_mask = GD_EVENT_RELEASED;
16921 else if (id == GAME_CTRL_ID_UNDO ||
16922 id == GAME_CTRL_ID_REDO)
16924 button_type = GD_TYPE_NORMAL_BUTTON;
16926 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16930 button_type = GD_TYPE_CHECK_BUTTON;
16931 checked = (gamebutton_info[i].setup_value != NULL ?
16932 *gamebutton_info[i].setup_value : FALSE);
16933 event_mask = GD_EVENT_PRESSED;
16936 gi = CreateGadget(GDI_CUSTOM_ID, id,
16937 GDI_IMAGE_ID, graphic,
16938 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16941 GDI_WIDTH, gfx->width,
16942 GDI_HEIGHT, gfx->height,
16943 GDI_TYPE, button_type,
16944 GDI_STATE, GD_BUTTON_UNPRESSED,
16945 GDI_CHECKED, checked,
16946 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16947 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16948 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16949 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16950 GDI_DIRECT_DRAW, FALSE,
16951 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16952 GDI_EVENT_MASK, event_mask,
16953 GDI_CALLBACK_ACTION, HandleGameButtons,
16957 Fail("cannot create gadget");
16959 game_gadget[id] = gi;
16963 void FreeGameButtons(void)
16967 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16968 FreeGadget(game_gadget[i]);
16971 static void UnmapGameButtonsAtSamePosition(int id)
16975 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16977 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16978 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16979 UnmapGadget(game_gadget[i]);
16982 static void UnmapGameButtonsAtSamePosition_All(void)
16984 if (setup.show_load_save_buttons)
16986 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16987 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16988 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16990 else if (setup.show_undo_redo_buttons)
16992 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16993 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16994 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16998 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16999 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
17000 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
17002 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
17003 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
17004 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
17008 void MapLoadSaveButtons(void)
17010 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
17011 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
17013 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
17014 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
17017 void MapUndoRedoButtons(void)
17019 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
17020 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
17022 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
17023 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
17026 void ModifyPauseButtons(void)
17030 GAME_CTRL_ID_PAUSE,
17031 GAME_CTRL_ID_PAUSE2,
17032 GAME_CTRL_ID_PANEL_PAUSE,
17033 GAME_CTRL_ID_TOUCH_PAUSE,
17038 // do not redraw pause button on closed door (may happen when restarting game)
17039 if (!(GetDoorState() & DOOR_OPEN_1))
17042 for (i = 0; ids[i] > -1; i++)
17043 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
17046 static void MapGameButtonsExt(boolean on_tape)
17050 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17052 if ((i == GAME_CTRL_ID_UNDO ||
17053 i == GAME_CTRL_ID_REDO) &&
17054 game_status != GAME_MODE_PLAYING)
17057 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17058 MapGadget(game_gadget[i]);
17061 UnmapGameButtonsAtSamePosition_All();
17063 RedrawGameButtons();
17066 static void UnmapGameButtonsExt(boolean on_tape)
17070 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17071 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17072 UnmapGadget(game_gadget[i]);
17075 static void RedrawGameButtonsExt(boolean on_tape)
17079 for (i = 0; i < NUM_GAME_BUTTONS; i++)
17080 if (!on_tape || gamebutton_info[i].allowed_on_tape)
17081 RedrawGadget(game_gadget[i]);
17084 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
17089 gi->checked = state;
17092 static void RedrawSoundButtonGadget(int id)
17094 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
17095 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
17096 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
17097 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
17098 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
17099 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
17102 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
17103 RedrawGadget(game_gadget[id2]);
17106 void MapGameButtons(void)
17108 MapGameButtonsExt(FALSE);
17111 void UnmapGameButtons(void)
17113 UnmapGameButtonsExt(FALSE);
17116 void RedrawGameButtons(void)
17118 RedrawGameButtonsExt(FALSE);
17121 void MapGameButtonsOnTape(void)
17123 MapGameButtonsExt(TRUE);
17126 void UnmapGameButtonsOnTape(void)
17128 UnmapGameButtonsExt(TRUE);
17131 void RedrawGameButtonsOnTape(void)
17133 RedrawGameButtonsExt(TRUE);
17136 static void GameUndoRedoExt(void)
17138 ClearPlayerAction();
17140 tape.pausing = TRUE;
17143 UpdateAndDisplayGameControlValues();
17145 DrawCompleteVideoDisplay();
17146 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
17147 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
17148 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
17150 ModifyPauseButtons();
17155 static void GameUndo(int steps)
17157 if (!CheckEngineSnapshotList())
17160 int tape_property_bits = tape.property_bits;
17162 LoadEngineSnapshot_Undo(steps);
17164 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17169 static void GameRedo(int steps)
17171 if (!CheckEngineSnapshotList())
17174 int tape_property_bits = tape.property_bits;
17176 LoadEngineSnapshot_Redo(steps);
17178 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
17183 static void HandleGameButtonsExt(int id, int button)
17185 static boolean game_undo_executed = FALSE;
17186 int steps = BUTTON_STEPSIZE(button);
17187 boolean handle_game_buttons =
17188 (game_status == GAME_MODE_PLAYING ||
17189 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
17191 if (!handle_game_buttons)
17196 case GAME_CTRL_ID_STOP:
17197 case GAME_CTRL_ID_PANEL_STOP:
17198 case GAME_CTRL_ID_TOUCH_STOP:
17203 case GAME_CTRL_ID_PAUSE:
17204 case GAME_CTRL_ID_PAUSE2:
17205 case GAME_CTRL_ID_PANEL_PAUSE:
17206 case GAME_CTRL_ID_TOUCH_PAUSE:
17207 if (network.enabled && game_status == GAME_MODE_PLAYING)
17210 SendToServer_ContinuePlaying();
17212 SendToServer_PausePlaying();
17215 TapeTogglePause(TAPE_TOGGLE_MANUAL);
17217 game_undo_executed = FALSE;
17221 case GAME_CTRL_ID_PLAY:
17222 case GAME_CTRL_ID_PANEL_PLAY:
17223 if (game_status == GAME_MODE_MAIN)
17225 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
17227 else if (tape.pausing)
17229 if (network.enabled)
17230 SendToServer_ContinuePlaying();
17232 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
17236 case GAME_CTRL_ID_UNDO:
17237 // Important: When using "save snapshot when collecting an item" mode,
17238 // load last (current) snapshot for first "undo" after pressing "pause"
17239 // (else the last-but-one snapshot would be loaded, because the snapshot
17240 // pointer already points to the last snapshot when pressing "pause",
17241 // which is fine for "every step/move" mode, but not for "every collect")
17242 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
17243 !game_undo_executed)
17246 game_undo_executed = TRUE;
17251 case GAME_CTRL_ID_REDO:
17255 case GAME_CTRL_ID_SAVE:
17259 case GAME_CTRL_ID_LOAD:
17263 case GAME_CTRL_ID_RESTART:
17264 case GAME_CTRL_ID_PANEL_RESTART:
17265 case GAME_CTRL_ID_TOUCH_RESTART:
17270 case SOUND_CTRL_ID_MUSIC:
17271 case SOUND_CTRL_ID_PANEL_MUSIC:
17272 if (setup.sound_music)
17274 setup.sound_music = FALSE;
17278 else if (audio.music_available)
17280 setup.sound = setup.sound_music = TRUE;
17282 SetAudioMode(setup.sound);
17284 if (game_status == GAME_MODE_PLAYING)
17288 RedrawSoundButtonGadget(id);
17292 case SOUND_CTRL_ID_LOOPS:
17293 case SOUND_CTRL_ID_PANEL_LOOPS:
17294 if (setup.sound_loops)
17295 setup.sound_loops = FALSE;
17296 else if (audio.loops_available)
17298 setup.sound = setup.sound_loops = TRUE;
17300 SetAudioMode(setup.sound);
17303 RedrawSoundButtonGadget(id);
17307 case SOUND_CTRL_ID_SIMPLE:
17308 case SOUND_CTRL_ID_PANEL_SIMPLE:
17309 if (setup.sound_simple)
17310 setup.sound_simple = FALSE;
17311 else if (audio.sound_available)
17313 setup.sound = setup.sound_simple = TRUE;
17315 SetAudioMode(setup.sound);
17318 RedrawSoundButtonGadget(id);
17327 static void HandleGameButtons(struct GadgetInfo *gi)
17329 HandleGameButtonsExt(gi->custom_id, gi->event.button);
17332 void HandleSoundButtonKeys(Key key)
17334 if (key == setup.shortcut.sound_simple)
17335 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
17336 else if (key == setup.shortcut.sound_loops)
17337 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
17338 else if (key == setup.shortcut.sound_music)
17339 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);