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_INVENTORY_COUNT 2
93 #define GAME_PANEL_INVENTORY_FIRST_1 3
94 #define GAME_PANEL_INVENTORY_FIRST_2 4
95 #define GAME_PANEL_INVENTORY_FIRST_3 5
96 #define GAME_PANEL_INVENTORY_FIRST_4 6
97 #define GAME_PANEL_INVENTORY_FIRST_5 7
98 #define GAME_PANEL_INVENTORY_FIRST_6 8
99 #define GAME_PANEL_INVENTORY_FIRST_7 9
100 #define GAME_PANEL_INVENTORY_FIRST_8 10
101 #define GAME_PANEL_INVENTORY_LAST_1 11
102 #define GAME_PANEL_INVENTORY_LAST_2 12
103 #define GAME_PANEL_INVENTORY_LAST_3 13
104 #define GAME_PANEL_INVENTORY_LAST_4 14
105 #define GAME_PANEL_INVENTORY_LAST_5 15
106 #define GAME_PANEL_INVENTORY_LAST_6 16
107 #define GAME_PANEL_INVENTORY_LAST_7 17
108 #define GAME_PANEL_INVENTORY_LAST_8 18
109 #define GAME_PANEL_KEY_1 19
110 #define GAME_PANEL_KEY_2 20
111 #define GAME_PANEL_KEY_3 21
112 #define GAME_PANEL_KEY_4 22
113 #define GAME_PANEL_KEY_5 23
114 #define GAME_PANEL_KEY_6 24
115 #define GAME_PANEL_KEY_7 25
116 #define GAME_PANEL_KEY_8 26
117 #define GAME_PANEL_KEY_WHITE 27
118 #define GAME_PANEL_KEY_WHITE_COUNT 28
119 #define GAME_PANEL_SCORE 29
120 #define GAME_PANEL_HIGHSCORE 30
121 #define GAME_PANEL_TIME 31
122 #define GAME_PANEL_TIME_HH 32
123 #define GAME_PANEL_TIME_MM 33
124 #define GAME_PANEL_TIME_SS 34
125 #define GAME_PANEL_TIME_ANIM 35
126 #define GAME_PANEL_HEALTH 36
127 #define GAME_PANEL_HEALTH_ANIM 37
128 #define GAME_PANEL_FRAME 38
129 #define GAME_PANEL_SHIELD_NORMAL 39
130 #define GAME_PANEL_SHIELD_NORMAL_TIME 40
131 #define GAME_PANEL_SHIELD_DEADLY 41
132 #define GAME_PANEL_SHIELD_DEADLY_TIME 42
133 #define GAME_PANEL_EXIT 43
134 #define GAME_PANEL_EMC_MAGIC_BALL 44
135 #define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 45
136 #define GAME_PANEL_LIGHT_SWITCH 46
137 #define GAME_PANEL_LIGHT_SWITCH_TIME 47
138 #define GAME_PANEL_TIMEGATE_SWITCH 48
139 #define GAME_PANEL_TIMEGATE_SWITCH_TIME 49
140 #define GAME_PANEL_SWITCHGATE_SWITCH 50
141 #define GAME_PANEL_EMC_LENSES 51
142 #define GAME_PANEL_EMC_LENSES_TIME 52
143 #define GAME_PANEL_EMC_MAGNIFIER 53
144 #define GAME_PANEL_EMC_MAGNIFIER_TIME 54
145 #define GAME_PANEL_BALLOON_SWITCH 55
146 #define GAME_PANEL_DYNABOMB_NUMBER 56
147 #define GAME_PANEL_DYNABOMB_SIZE 57
148 #define GAME_PANEL_DYNABOMB_POWER 58
149 #define GAME_PANEL_PENGUINS 59
150 #define GAME_PANEL_SOKOBAN_OBJECTS 60
151 #define GAME_PANEL_SOKOBAN_FIELDS 61
152 #define GAME_PANEL_ROBOT_WHEEL 62
153 #define GAME_PANEL_CONVEYOR_BELT_1 63
154 #define GAME_PANEL_CONVEYOR_BELT_2 64
155 #define GAME_PANEL_CONVEYOR_BELT_3 65
156 #define GAME_PANEL_CONVEYOR_BELT_4 66
157 #define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 67
158 #define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 68
159 #define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 69
160 #define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 70
161 #define GAME_PANEL_MAGIC_WALL 71
162 #define GAME_PANEL_MAGIC_WALL_TIME 72
163 #define GAME_PANEL_GRAVITY_STATE 73
164 #define GAME_PANEL_GRAPHIC_1 74
165 #define GAME_PANEL_GRAPHIC_2 75
166 #define GAME_PANEL_GRAPHIC_3 76
167 #define GAME_PANEL_GRAPHIC_4 77
168 #define GAME_PANEL_GRAPHIC_5 78
169 #define GAME_PANEL_GRAPHIC_6 79
170 #define GAME_PANEL_GRAPHIC_7 80
171 #define GAME_PANEL_GRAPHIC_8 81
172 #define GAME_PANEL_ELEMENT_1 82
173 #define GAME_PANEL_ELEMENT_2 83
174 #define GAME_PANEL_ELEMENT_3 84
175 #define GAME_PANEL_ELEMENT_4 85
176 #define GAME_PANEL_ELEMENT_5 86
177 #define GAME_PANEL_ELEMENT_6 87
178 #define GAME_PANEL_ELEMENT_7 88
179 #define GAME_PANEL_ELEMENT_8 89
180 #define GAME_PANEL_ELEMENT_COUNT_1 90
181 #define GAME_PANEL_ELEMENT_COUNT_2 91
182 #define GAME_PANEL_ELEMENT_COUNT_3 92
183 #define GAME_PANEL_ELEMENT_COUNT_4 93
184 #define GAME_PANEL_ELEMENT_COUNT_5 94
185 #define GAME_PANEL_ELEMENT_COUNT_6 95
186 #define GAME_PANEL_ELEMENT_COUNT_7 96
187 #define GAME_PANEL_ELEMENT_COUNT_8 97
188 #define GAME_PANEL_CE_SCORE_1 98
189 #define GAME_PANEL_CE_SCORE_2 99
190 #define GAME_PANEL_CE_SCORE_3 100
191 #define GAME_PANEL_CE_SCORE_4 101
192 #define GAME_PANEL_CE_SCORE_5 102
193 #define GAME_PANEL_CE_SCORE_6 103
194 #define GAME_PANEL_CE_SCORE_7 104
195 #define GAME_PANEL_CE_SCORE_8 105
196 #define GAME_PANEL_CE_SCORE_1_ELEMENT 106
197 #define GAME_PANEL_CE_SCORE_2_ELEMENT 107
198 #define GAME_PANEL_CE_SCORE_3_ELEMENT 108
199 #define GAME_PANEL_CE_SCORE_4_ELEMENT 109
200 #define GAME_PANEL_CE_SCORE_5_ELEMENT 110
201 #define GAME_PANEL_CE_SCORE_6_ELEMENT 111
202 #define GAME_PANEL_CE_SCORE_7_ELEMENT 112
203 #define GAME_PANEL_CE_SCORE_8_ELEMENT 113
204 #define GAME_PANEL_PLAYER_NAME 114
205 #define GAME_PANEL_LEVEL_NAME 115
206 #define GAME_PANEL_LEVEL_AUTHOR 116
208 #define NUM_GAME_PANEL_CONTROLS 117
210 struct GamePanelOrderInfo
216 static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
218 struct GamePanelControlInfo
222 struct TextPosInfo *pos;
225 int graphic, graphic_active;
227 int value, last_value;
228 int frame, last_frame;
233 static struct GamePanelControlInfo game_panel_controls[] =
236 GAME_PANEL_LEVEL_NUMBER,
237 &game.panel.level_number,
246 GAME_PANEL_INVENTORY_COUNT,
247 &game.panel.inventory_count,
251 GAME_PANEL_INVENTORY_FIRST_1,
252 &game.panel.inventory_first[0],
256 GAME_PANEL_INVENTORY_FIRST_2,
257 &game.panel.inventory_first[1],
261 GAME_PANEL_INVENTORY_FIRST_3,
262 &game.panel.inventory_first[2],
266 GAME_PANEL_INVENTORY_FIRST_4,
267 &game.panel.inventory_first[3],
271 GAME_PANEL_INVENTORY_FIRST_5,
272 &game.panel.inventory_first[4],
276 GAME_PANEL_INVENTORY_FIRST_6,
277 &game.panel.inventory_first[5],
281 GAME_PANEL_INVENTORY_FIRST_7,
282 &game.panel.inventory_first[6],
286 GAME_PANEL_INVENTORY_FIRST_8,
287 &game.panel.inventory_first[7],
291 GAME_PANEL_INVENTORY_LAST_1,
292 &game.panel.inventory_last[0],
296 GAME_PANEL_INVENTORY_LAST_2,
297 &game.panel.inventory_last[1],
301 GAME_PANEL_INVENTORY_LAST_3,
302 &game.panel.inventory_last[2],
306 GAME_PANEL_INVENTORY_LAST_4,
307 &game.panel.inventory_last[3],
311 GAME_PANEL_INVENTORY_LAST_5,
312 &game.panel.inventory_last[4],
316 GAME_PANEL_INVENTORY_LAST_6,
317 &game.panel.inventory_last[5],
321 GAME_PANEL_INVENTORY_LAST_7,
322 &game.panel.inventory_last[6],
326 GAME_PANEL_INVENTORY_LAST_8,
327 &game.panel.inventory_last[7],
371 GAME_PANEL_KEY_WHITE,
372 &game.panel.key_white,
376 GAME_PANEL_KEY_WHITE_COUNT,
377 &game.panel.key_white_count,
386 GAME_PANEL_HIGHSCORE,
387 &game.panel.highscore,
411 GAME_PANEL_TIME_ANIM,
412 &game.panel.time_anim,
415 IMG_GFX_GAME_PANEL_TIME_ANIM,
416 IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
424 GAME_PANEL_HEALTH_ANIM,
425 &game.panel.health_anim,
428 IMG_GFX_GAME_PANEL_HEALTH_ANIM,
429 IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
437 GAME_PANEL_SHIELD_NORMAL,
438 &game.panel.shield_normal,
442 GAME_PANEL_SHIELD_NORMAL_TIME,
443 &game.panel.shield_normal_time,
447 GAME_PANEL_SHIELD_DEADLY,
448 &game.panel.shield_deadly,
452 GAME_PANEL_SHIELD_DEADLY_TIME,
453 &game.panel.shield_deadly_time,
462 GAME_PANEL_EMC_MAGIC_BALL,
463 &game.panel.emc_magic_ball,
467 GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
468 &game.panel.emc_magic_ball_switch,
472 GAME_PANEL_LIGHT_SWITCH,
473 &game.panel.light_switch,
477 GAME_PANEL_LIGHT_SWITCH_TIME,
478 &game.panel.light_switch_time,
482 GAME_PANEL_TIMEGATE_SWITCH,
483 &game.panel.timegate_switch,
487 GAME_PANEL_TIMEGATE_SWITCH_TIME,
488 &game.panel.timegate_switch_time,
492 GAME_PANEL_SWITCHGATE_SWITCH,
493 &game.panel.switchgate_switch,
497 GAME_PANEL_EMC_LENSES,
498 &game.panel.emc_lenses,
502 GAME_PANEL_EMC_LENSES_TIME,
503 &game.panel.emc_lenses_time,
507 GAME_PANEL_EMC_MAGNIFIER,
508 &game.panel.emc_magnifier,
512 GAME_PANEL_EMC_MAGNIFIER_TIME,
513 &game.panel.emc_magnifier_time,
517 GAME_PANEL_BALLOON_SWITCH,
518 &game.panel.balloon_switch,
522 GAME_PANEL_DYNABOMB_NUMBER,
523 &game.panel.dynabomb_number,
527 GAME_PANEL_DYNABOMB_SIZE,
528 &game.panel.dynabomb_size,
532 GAME_PANEL_DYNABOMB_POWER,
533 &game.panel.dynabomb_power,
538 &game.panel.penguins,
542 GAME_PANEL_SOKOBAN_OBJECTS,
543 &game.panel.sokoban_objects,
547 GAME_PANEL_SOKOBAN_FIELDS,
548 &game.panel.sokoban_fields,
552 GAME_PANEL_ROBOT_WHEEL,
553 &game.panel.robot_wheel,
557 GAME_PANEL_CONVEYOR_BELT_1,
558 &game.panel.conveyor_belt[0],
562 GAME_PANEL_CONVEYOR_BELT_2,
563 &game.panel.conveyor_belt[1],
567 GAME_PANEL_CONVEYOR_BELT_3,
568 &game.panel.conveyor_belt[2],
572 GAME_PANEL_CONVEYOR_BELT_4,
573 &game.panel.conveyor_belt[3],
577 GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
578 &game.panel.conveyor_belt_switch[0],
582 GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
583 &game.panel.conveyor_belt_switch[1],
587 GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
588 &game.panel.conveyor_belt_switch[2],
592 GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
593 &game.panel.conveyor_belt_switch[3],
597 GAME_PANEL_MAGIC_WALL,
598 &game.panel.magic_wall,
602 GAME_PANEL_MAGIC_WALL_TIME,
603 &game.panel.magic_wall_time,
607 GAME_PANEL_GRAVITY_STATE,
608 &game.panel.gravity_state,
612 GAME_PANEL_GRAPHIC_1,
613 &game.panel.graphic[0],
617 GAME_PANEL_GRAPHIC_2,
618 &game.panel.graphic[1],
622 GAME_PANEL_GRAPHIC_3,
623 &game.panel.graphic[2],
627 GAME_PANEL_GRAPHIC_4,
628 &game.panel.graphic[3],
632 GAME_PANEL_GRAPHIC_5,
633 &game.panel.graphic[4],
637 GAME_PANEL_GRAPHIC_6,
638 &game.panel.graphic[5],
642 GAME_PANEL_GRAPHIC_7,
643 &game.panel.graphic[6],
647 GAME_PANEL_GRAPHIC_8,
648 &game.panel.graphic[7],
652 GAME_PANEL_ELEMENT_1,
653 &game.panel.element[0],
657 GAME_PANEL_ELEMENT_2,
658 &game.panel.element[1],
662 GAME_PANEL_ELEMENT_3,
663 &game.panel.element[2],
667 GAME_PANEL_ELEMENT_4,
668 &game.panel.element[3],
672 GAME_PANEL_ELEMENT_5,
673 &game.panel.element[4],
677 GAME_PANEL_ELEMENT_6,
678 &game.panel.element[5],
682 GAME_PANEL_ELEMENT_7,
683 &game.panel.element[6],
687 GAME_PANEL_ELEMENT_8,
688 &game.panel.element[7],
692 GAME_PANEL_ELEMENT_COUNT_1,
693 &game.panel.element_count[0],
697 GAME_PANEL_ELEMENT_COUNT_2,
698 &game.panel.element_count[1],
702 GAME_PANEL_ELEMENT_COUNT_3,
703 &game.panel.element_count[2],
707 GAME_PANEL_ELEMENT_COUNT_4,
708 &game.panel.element_count[3],
712 GAME_PANEL_ELEMENT_COUNT_5,
713 &game.panel.element_count[4],
717 GAME_PANEL_ELEMENT_COUNT_6,
718 &game.panel.element_count[5],
722 GAME_PANEL_ELEMENT_COUNT_7,
723 &game.panel.element_count[6],
727 GAME_PANEL_ELEMENT_COUNT_8,
728 &game.panel.element_count[7],
732 GAME_PANEL_CE_SCORE_1,
733 &game.panel.ce_score[0],
737 GAME_PANEL_CE_SCORE_2,
738 &game.panel.ce_score[1],
742 GAME_PANEL_CE_SCORE_3,
743 &game.panel.ce_score[2],
747 GAME_PANEL_CE_SCORE_4,
748 &game.panel.ce_score[3],
752 GAME_PANEL_CE_SCORE_5,
753 &game.panel.ce_score[4],
757 GAME_PANEL_CE_SCORE_6,
758 &game.panel.ce_score[5],
762 GAME_PANEL_CE_SCORE_7,
763 &game.panel.ce_score[6],
767 GAME_PANEL_CE_SCORE_8,
768 &game.panel.ce_score[7],
772 GAME_PANEL_CE_SCORE_1_ELEMENT,
773 &game.panel.ce_score_element[0],
777 GAME_PANEL_CE_SCORE_2_ELEMENT,
778 &game.panel.ce_score_element[1],
782 GAME_PANEL_CE_SCORE_3_ELEMENT,
783 &game.panel.ce_score_element[2],
787 GAME_PANEL_CE_SCORE_4_ELEMENT,
788 &game.panel.ce_score_element[3],
792 GAME_PANEL_CE_SCORE_5_ELEMENT,
793 &game.panel.ce_score_element[4],
797 GAME_PANEL_CE_SCORE_6_ELEMENT,
798 &game.panel.ce_score_element[5],
802 GAME_PANEL_CE_SCORE_7_ELEMENT,
803 &game.panel.ce_score_element[6],
807 GAME_PANEL_CE_SCORE_8_ELEMENT,
808 &game.panel.ce_score_element[7],
812 GAME_PANEL_PLAYER_NAME,
813 &game.panel.player_name,
817 GAME_PANEL_LEVEL_NAME,
818 &game.panel.level_name,
822 GAME_PANEL_LEVEL_AUTHOR,
823 &game.panel.level_author,
834 // values for delayed check of falling and moving elements and for collision
835 #define CHECK_DELAY_MOVING 3
836 #define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
837 #define CHECK_DELAY_COLLISION 2
838 #define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
840 // values for initial player move delay (initial delay counter value)
841 #define INITIAL_MOVE_DELAY_OFF -1
842 #define INITIAL_MOVE_DELAY_ON 0
844 // values for player movement speed (which is in fact a delay value)
845 #define MOVE_DELAY_MIN_SPEED 32
846 #define MOVE_DELAY_NORMAL_SPEED 8
847 #define MOVE_DELAY_HIGH_SPEED 4
848 #define MOVE_DELAY_MAX_SPEED 1
850 #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
851 #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
853 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
854 #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
856 // values for scroll positions
857 #define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
858 (x) > SBX_Right + MIDPOSX ? SBX_Right :\
860 #define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
861 (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
864 // values for other actions
865 #define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
866 #define MOVE_STEPSIZE_MIN (1)
867 #define MOVE_STEPSIZE_MAX (TILEX)
869 #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
870 #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
872 #define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
874 #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
875 RND(element_info[e].push_delay_random))
876 #define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
877 RND(element_info[e].drop_delay_random))
878 #define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
879 RND(element_info[e].move_delay_random))
880 #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
881 (element_info[e].move_delay_random))
882 #define GET_NEW_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
883 RND(element_info[e].step_delay_random))
884 #define GET_MAX_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
885 (element_info[e].step_delay_random))
886 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
887 RND(element_info[e].ce_value_random_initial))
888 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
889 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
890 RND((c)->delay_random * (c)->delay_frames))
891 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
892 RND((c)->delay_random))
895 #define GET_VALID_RUNTIME_ELEMENT(e) \
896 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
898 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
899 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
900 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
901 (be) + (e) - EL_SELF)
903 #define GET_PLAYER_FROM_BITS(p) \
904 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
906 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
907 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
908 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
909 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
910 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
911 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
912 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
913 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
914 RESOLVED_REFERENCE_ELEMENT(be, e) : \
917 #define CAN_GROW_INTO(e) \
918 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
920 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
921 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
924 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
925 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
926 (CAN_MOVE_INTO_ACID(e) && \
927 Tile[x][y] == EL_ACID) || \
930 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
931 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
932 (CAN_MOVE_INTO_ACID(e) && \
933 Tile[x][y] == EL_ACID) || \
936 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
937 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
939 (CAN_MOVE_INTO_ACID(e) && \
940 Tile[x][y] == EL_ACID) || \
941 (DONT_COLLIDE_WITH(e) && \
943 !PLAYER_ENEMY_PROTECTED(x, y))))
945 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
946 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
948 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
949 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
951 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
952 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
954 #define ANDROID_CAN_CLONE_FIELD(x, y) \
955 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
956 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
958 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
959 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
961 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
962 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
964 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
965 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
967 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
968 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
970 #define PIG_CAN_ENTER_FIELD(e, x, y) \
971 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
973 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
974 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
975 Tile[x][y] == EL_EM_EXIT_OPEN || \
976 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
977 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
978 IS_FOOD_PENGUIN(Tile[x][y])))
979 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
980 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
982 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
983 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
985 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
986 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
988 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
989 (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
990 Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
992 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
994 #define CE_ENTER_FIELD_COND(e, x, y) \
995 (!IS_PLAYER(x, y) && \
996 IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
998 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
999 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
1001 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
1002 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1004 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1005 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1006 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1007 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1009 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1011 // game button identifiers
1012 #define GAME_CTRL_ID_STOP 0
1013 #define GAME_CTRL_ID_PAUSE 1
1014 #define GAME_CTRL_ID_PLAY 2
1015 #define GAME_CTRL_ID_UNDO 3
1016 #define GAME_CTRL_ID_REDO 4
1017 #define GAME_CTRL_ID_SAVE 5
1018 #define GAME_CTRL_ID_PAUSE2 6
1019 #define GAME_CTRL_ID_LOAD 7
1020 #define GAME_CTRL_ID_PANEL_STOP 8
1021 #define GAME_CTRL_ID_PANEL_PAUSE 9
1022 #define GAME_CTRL_ID_PANEL_PLAY 10
1023 #define GAME_CTRL_ID_TOUCH_STOP 11
1024 #define GAME_CTRL_ID_TOUCH_PAUSE 12
1025 #define SOUND_CTRL_ID_MUSIC 13
1026 #define SOUND_CTRL_ID_LOOPS 14
1027 #define SOUND_CTRL_ID_SIMPLE 15
1028 #define SOUND_CTRL_ID_PANEL_MUSIC 16
1029 #define SOUND_CTRL_ID_PANEL_LOOPS 17
1030 #define SOUND_CTRL_ID_PANEL_SIMPLE 18
1032 #define NUM_GAME_BUTTONS 19
1035 // forward declaration for internal use
1037 static void CreateField(int, int, int);
1039 static void ResetGfxAnimation(int, int);
1041 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1042 static void AdvanceFrameAndPlayerCounters(int);
1044 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1045 static boolean MovePlayer(struct PlayerInfo *, int, int);
1046 static void ScrollPlayer(struct PlayerInfo *, int);
1047 static void ScrollScreen(struct PlayerInfo *, int);
1049 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1050 static boolean DigFieldByCE(int, int, int);
1051 static boolean SnapField(struct PlayerInfo *, int, int);
1052 static boolean DropElement(struct PlayerInfo *);
1054 static void InitBeltMovement(void);
1055 static void CloseAllOpenTimegates(void);
1056 static void CheckGravityMovement(struct PlayerInfo *);
1057 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1058 static void KillPlayerUnlessEnemyProtected(int, int);
1059 static void KillPlayerUnlessExplosionProtected(int, int);
1061 static void CheckNextToConditions(int, int);
1062 static void TestIfPlayerNextToCustomElement(int, int);
1063 static void TestIfPlayerTouchesCustomElement(int, int);
1064 static void TestIfElementNextToCustomElement(int, int);
1065 static void TestIfElementTouchesCustomElement(int, int);
1066 static void TestIfElementHitsCustomElement(int, int, int);
1068 static void HandleElementChange(int, int, int);
1069 static void ExecuteCustomElementAction(int, int, int, int);
1070 static boolean ChangeElement(int, int, int, int);
1072 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int, int, int);
1073 #define CheckTriggeredElementChange(x, y, e, ev) \
1074 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, -1)
1075 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1076 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1077 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1078 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1079 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1080 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1081 #define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
1082 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1084 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1085 #define CheckElementChange(x, y, e, te, ev) \
1086 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1087 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1088 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1089 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1090 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1091 #define CheckElementChangeByMouse(x, y, e, ev, s) \
1092 CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
1094 static void PlayLevelSound(int, int, int);
1095 static void PlayLevelSoundNearest(int, int, int);
1096 static void PlayLevelSoundAction(int, int, int);
1097 static void PlayLevelSoundElementAction(int, int, int, int);
1098 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1099 static void PlayLevelSoundActionIfLoop(int, int, int);
1100 static void StopLevelSoundActionIfLoop(int, int, int);
1101 static void PlayLevelMusic(void);
1102 static void FadeLevelSoundsAndMusic(void);
1104 static void HandleGameButtons(struct GadgetInfo *);
1106 int AmoebaNeighbourNr(int, int);
1107 void AmoebaToDiamond(int, int);
1108 void ContinueMoving(int, int);
1109 void Bang(int, int);
1110 void InitMovDir(int, int);
1111 void InitAmoebaNr(int, int);
1112 void NewHighScore(int, boolean);
1114 void TestIfGoodThingHitsBadThing(int, int, int);
1115 void TestIfBadThingHitsGoodThing(int, int, int);
1116 void TestIfPlayerTouchesBadThing(int, int);
1117 void TestIfPlayerRunsIntoBadThing(int, int, int);
1118 void TestIfBadThingTouchesPlayer(int, int);
1119 void TestIfBadThingRunsIntoPlayer(int, int, int);
1120 void TestIfFriendTouchesBadThing(int, int);
1121 void TestIfBadThingTouchesFriend(int, int);
1122 void TestIfBadThingTouchesOtherBadThing(int, int);
1123 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1125 void KillPlayer(struct PlayerInfo *);
1126 void BuryPlayer(struct PlayerInfo *);
1127 void RemovePlayer(struct PlayerInfo *);
1128 void ExitPlayer(struct PlayerInfo *);
1130 static int getInvisibleActiveFromInvisibleElement(int);
1131 static int getInvisibleFromInvisibleActiveElement(int);
1133 static void TestFieldAfterSnapping(int, int, int, int, int);
1135 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1137 // for detection of endless loops, caused by custom element programming
1138 // (using maximal playfield width x 10 is just a rough approximation)
1139 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1141 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1143 if (recursion_loop_detected) \
1146 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1148 recursion_loop_detected = TRUE; \
1149 recursion_loop_element = (e); \
1152 recursion_loop_depth++; \
1155 #define RECURSION_LOOP_DETECTION_END() \
1157 recursion_loop_depth--; \
1160 static int recursion_loop_depth;
1161 static boolean recursion_loop_detected;
1162 static boolean recursion_loop_element;
1164 static int map_player_action[MAX_PLAYERS];
1167 // ----------------------------------------------------------------------------
1168 // definition of elements that automatically change to other elements after
1169 // a specified time, eventually calling a function when changing
1170 // ----------------------------------------------------------------------------
1172 // forward declaration for changer functions
1173 static void InitBuggyBase(int, int);
1174 static void WarnBuggyBase(int, int);
1176 static void InitTrap(int, int);
1177 static void ActivateTrap(int, int);
1178 static void ChangeActiveTrap(int, int);
1180 static void InitRobotWheel(int, int);
1181 static void RunRobotWheel(int, int);
1182 static void StopRobotWheel(int, int);
1184 static void InitTimegateWheel(int, int);
1185 static void RunTimegateWheel(int, int);
1187 static void InitMagicBallDelay(int, int);
1188 static void ActivateMagicBall(int, int);
1190 struct ChangingElementInfo
1195 void (*pre_change_function)(int x, int y);
1196 void (*change_function)(int x, int y);
1197 void (*post_change_function)(int x, int y);
1200 static struct ChangingElementInfo change_delay_list[] =
1235 EL_STEEL_EXIT_OPENING,
1243 EL_STEEL_EXIT_CLOSING,
1244 EL_STEEL_EXIT_CLOSED,
1267 EL_EM_STEEL_EXIT_OPENING,
1268 EL_EM_STEEL_EXIT_OPEN,
1275 EL_EM_STEEL_EXIT_CLOSING,
1299 EL_SWITCHGATE_OPENING,
1307 EL_SWITCHGATE_CLOSING,
1308 EL_SWITCHGATE_CLOSED,
1315 EL_TIMEGATE_OPENING,
1323 EL_TIMEGATE_CLOSING,
1332 EL_ACID_SPLASH_LEFT,
1340 EL_ACID_SPLASH_RIGHT,
1349 EL_SP_BUGGY_BASE_ACTIVATING,
1356 EL_SP_BUGGY_BASE_ACTIVATING,
1357 EL_SP_BUGGY_BASE_ACTIVE,
1364 EL_SP_BUGGY_BASE_ACTIVE,
1388 EL_ROBOT_WHEEL_ACTIVE,
1396 EL_TIMEGATE_SWITCH_ACTIVE,
1404 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1405 EL_DC_TIMEGATE_SWITCH,
1412 EL_EMC_MAGIC_BALL_ACTIVE,
1413 EL_EMC_MAGIC_BALL_ACTIVE,
1420 EL_EMC_SPRING_BUMPER_ACTIVE,
1421 EL_EMC_SPRING_BUMPER,
1428 EL_DIAGONAL_SHRINKING,
1436 EL_DIAGONAL_GROWING,
1457 int push_delay_fixed, push_delay_random;
1461 { EL_SPRING, 0, 0 },
1462 { EL_BALLOON, 0, 0 },
1464 { EL_SOKOBAN_OBJECT, 2, 0 },
1465 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1466 { EL_SATELLITE, 2, 0 },
1467 { EL_SP_DISK_YELLOW, 2, 0 },
1469 { EL_UNDEFINED, 0, 0 },
1477 move_stepsize_list[] =
1479 { EL_AMOEBA_DROP, 2 },
1480 { EL_AMOEBA_DROPPING, 2 },
1481 { EL_QUICKSAND_FILLING, 1 },
1482 { EL_QUICKSAND_EMPTYING, 1 },
1483 { EL_QUICKSAND_FAST_FILLING, 2 },
1484 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1485 { EL_MAGIC_WALL_FILLING, 2 },
1486 { EL_MAGIC_WALL_EMPTYING, 2 },
1487 { EL_BD_MAGIC_WALL_FILLING, 2 },
1488 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1489 { EL_DC_MAGIC_WALL_FILLING, 2 },
1490 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1492 { EL_UNDEFINED, 0 },
1500 collect_count_list[] =
1503 { EL_BD_DIAMOND, 1 },
1504 { EL_EMERALD_YELLOW, 1 },
1505 { EL_EMERALD_RED, 1 },
1506 { EL_EMERALD_PURPLE, 1 },
1508 { EL_SP_INFOTRON, 1 },
1512 { EL_UNDEFINED, 0 },
1520 access_direction_list[] =
1522 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1523 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1524 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1525 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1526 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1527 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1528 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1529 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1530 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1531 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1532 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1534 { EL_SP_PORT_LEFT, MV_RIGHT },
1535 { EL_SP_PORT_RIGHT, MV_LEFT },
1536 { EL_SP_PORT_UP, MV_DOWN },
1537 { EL_SP_PORT_DOWN, MV_UP },
1538 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1539 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1540 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1541 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1542 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1543 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1544 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1545 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1546 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1547 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1548 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1549 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1550 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1551 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1552 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1554 { EL_UNDEFINED, MV_NONE }
1557 static struct XY xy_topdown[] =
1565 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1567 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1568 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1569 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1570 IS_JUST_CHANGING(x, y))
1572 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1574 // static variables for playfield scan mode (scanning forward or backward)
1575 static int playfield_scan_start_x = 0;
1576 static int playfield_scan_start_y = 0;
1577 static int playfield_scan_delta_x = 1;
1578 static int playfield_scan_delta_y = 1;
1580 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1581 (y) >= 0 && (y) <= lev_fieldy - 1; \
1582 (y) += playfield_scan_delta_y) \
1583 for ((x) = playfield_scan_start_x; \
1584 (x) >= 0 && (x) <= lev_fieldx - 1; \
1585 (x) += playfield_scan_delta_x)
1588 void DEBUG_SetMaximumDynamite(void)
1592 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1593 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1594 local_player->inventory_element[local_player->inventory_size++] =
1599 static void InitPlayfieldScanModeVars(void)
1601 if (game.use_reverse_scan_direction)
1603 playfield_scan_start_x = lev_fieldx - 1;
1604 playfield_scan_start_y = lev_fieldy - 1;
1606 playfield_scan_delta_x = -1;
1607 playfield_scan_delta_y = -1;
1611 playfield_scan_start_x = 0;
1612 playfield_scan_start_y = 0;
1614 playfield_scan_delta_x = 1;
1615 playfield_scan_delta_y = 1;
1619 static void InitPlayfieldScanMode(int mode)
1621 game.use_reverse_scan_direction =
1622 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1624 InitPlayfieldScanModeVars();
1627 static int get_move_delay_from_stepsize(int move_stepsize)
1630 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1632 // make sure that stepsize value is always a power of 2
1633 move_stepsize = (1 << log_2(move_stepsize));
1635 return TILEX / move_stepsize;
1638 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1641 int player_nr = player->index_nr;
1642 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1643 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1645 // do no immediately change move delay -- the player might just be moving
1646 player->move_delay_value_next = move_delay;
1648 // information if player can move must be set separately
1649 player->cannot_move = cannot_move;
1653 player->move_delay = game.initial_move_delay[player_nr];
1654 player->move_delay_value = game.initial_move_delay_value[player_nr];
1656 player->move_delay_value_next = -1;
1658 player->move_delay_reset_counter = 0;
1662 void GetPlayerConfig(void)
1664 GameFrameDelay = setup.game_frame_delay;
1666 if (!audio.sound_available)
1667 setup.sound_simple = FALSE;
1669 if (!audio.loops_available)
1670 setup.sound_loops = FALSE;
1672 if (!audio.music_available)
1673 setup.sound_music = FALSE;
1675 if (!video.fullscreen_available)
1676 setup.fullscreen = FALSE;
1678 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1680 SetAudioMode(setup.sound);
1683 int GetElementFromGroupElement(int element)
1685 if (IS_GROUP_ELEMENT(element))
1687 struct ElementGroupInfo *group = element_info[element].group;
1688 int last_anim_random_frame = gfx.anim_random_frame;
1691 if (group->choice_mode == ANIM_RANDOM)
1692 gfx.anim_random_frame = RND(group->num_elements_resolved);
1694 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1695 group->choice_mode, 0,
1698 if (group->choice_mode == ANIM_RANDOM)
1699 gfx.anim_random_frame = last_anim_random_frame;
1701 group->choice_pos++;
1703 element = group->element_resolved[element_pos];
1709 static void IncrementSokobanFieldsNeeded(void)
1711 if (level.sb_fields_needed)
1712 game.sokoban_fields_still_needed++;
1715 static void IncrementSokobanObjectsNeeded(void)
1717 if (level.sb_objects_needed)
1718 game.sokoban_objects_still_needed++;
1721 static void DecrementSokobanFieldsNeeded(void)
1723 if (game.sokoban_fields_still_needed > 0)
1724 game.sokoban_fields_still_needed--;
1727 static void DecrementSokobanObjectsNeeded(void)
1729 if (game.sokoban_objects_still_needed > 0)
1730 game.sokoban_objects_still_needed--;
1733 static void InitPlayerField(int x, int y, int element, boolean init_game)
1735 if (element == EL_SP_MURPHY)
1739 if (stored_player[0].present)
1741 Tile[x][y] = EL_SP_MURPHY_CLONE;
1747 stored_player[0].initial_element = element;
1748 stored_player[0].use_murphy = TRUE;
1750 if (!level.use_artwork_element[0])
1751 stored_player[0].artwork_element = EL_SP_MURPHY;
1754 Tile[x][y] = EL_PLAYER_1;
1760 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1761 int jx = player->jx, jy = player->jy;
1763 player->present = TRUE;
1765 player->block_last_field = (element == EL_SP_MURPHY ?
1766 level.sp_block_last_field :
1767 level.block_last_field);
1769 // ---------- initialize player's last field block delay ------------------
1771 // always start with reliable default value (no adjustment needed)
1772 player->block_delay_adjustment = 0;
1774 // special case 1: in Supaplex, Murphy blocks last field one more frame
1775 if (player->block_last_field && element == EL_SP_MURPHY)
1776 player->block_delay_adjustment = 1;
1778 // special case 2: in game engines before 3.1.1, blocking was different
1779 if (game.use_block_last_field_bug)
1780 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1782 if (!network.enabled || player->connected_network)
1784 player->active = TRUE;
1786 // remove potentially duplicate players
1787 if (IN_LEV_FIELD(jx, jy) && StorePlayer[jx][jy] == Tile[x][y])
1788 StorePlayer[jx][jy] = 0;
1790 StorePlayer[x][y] = Tile[x][y];
1792 #if DEBUG_INIT_PLAYER
1793 Debug("game:init:player", "- player element %d activated",
1794 player->element_nr);
1795 Debug("game:init:player", " (local player is %d and currently %s)",
1796 local_player->element_nr,
1797 local_player->active ? "active" : "not active");
1801 Tile[x][y] = EL_EMPTY;
1803 player->jx = player->last_jx = x;
1804 player->jy = player->last_jy = y;
1807 // always check if player was just killed and should be reanimated
1809 int player_nr = GET_PLAYER_NR(element);
1810 struct PlayerInfo *player = &stored_player[player_nr];
1812 if (player->active && player->killed)
1813 player->reanimated = TRUE; // if player was just killed, reanimate him
1817 static void InitField(int x, int y, boolean init_game)
1819 int element = Tile[x][y];
1828 InitPlayerField(x, y, element, init_game);
1831 case EL_SOKOBAN_FIELD_PLAYER:
1832 element = Tile[x][y] = EL_PLAYER_1;
1833 InitField(x, y, init_game);
1835 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1836 InitField(x, y, init_game);
1839 case EL_SOKOBAN_FIELD_EMPTY:
1840 IncrementSokobanFieldsNeeded();
1843 case EL_SOKOBAN_OBJECT:
1844 IncrementSokobanObjectsNeeded();
1848 if (x < lev_fieldx - 1 && Tile[x + 1][y] == EL_ACID)
1849 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1850 else if (x > 0 && Tile[x - 1][y] == EL_ACID)
1851 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1852 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPLEFT)
1853 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1854 else if (y > 0 && Tile[x][y - 1] == EL_ACID)
1855 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1856 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPRIGHT)
1857 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1866 case EL_SPACESHIP_RIGHT:
1867 case EL_SPACESHIP_UP:
1868 case EL_SPACESHIP_LEFT:
1869 case EL_SPACESHIP_DOWN:
1870 case EL_BD_BUTTERFLY:
1871 case EL_BD_BUTTERFLY_RIGHT:
1872 case EL_BD_BUTTERFLY_UP:
1873 case EL_BD_BUTTERFLY_LEFT:
1874 case EL_BD_BUTTERFLY_DOWN:
1876 case EL_BD_FIREFLY_RIGHT:
1877 case EL_BD_FIREFLY_UP:
1878 case EL_BD_FIREFLY_LEFT:
1879 case EL_BD_FIREFLY_DOWN:
1880 case EL_PACMAN_RIGHT:
1882 case EL_PACMAN_LEFT:
1883 case EL_PACMAN_DOWN:
1885 case EL_YAMYAM_LEFT:
1886 case EL_YAMYAM_RIGHT:
1888 case EL_YAMYAM_DOWN:
1889 case EL_DARK_YAMYAM:
1892 case EL_SP_SNIKSNAK:
1893 case EL_SP_ELECTRON:
1899 case EL_SPRING_LEFT:
1900 case EL_SPRING_RIGHT:
1904 case EL_AMOEBA_FULL:
1909 case EL_AMOEBA_DROP:
1910 if (y == lev_fieldy - 1)
1912 Tile[x][y] = EL_AMOEBA_GROWING;
1913 Store[x][y] = EL_AMOEBA_WET;
1917 case EL_DYNAMITE_ACTIVE:
1918 case EL_SP_DISK_RED_ACTIVE:
1919 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1920 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1921 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1922 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1923 MovDelay[x][y] = 96;
1926 case EL_EM_DYNAMITE_ACTIVE:
1927 MovDelay[x][y] = 32;
1931 game.lights_still_needed++;
1935 game.friends_still_needed++;
1940 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1943 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1944 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1945 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1946 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1947 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1948 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1949 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1950 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1951 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1952 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1953 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1954 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1957 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1958 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1959 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1961 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1963 game.belt_dir[belt_nr] = belt_dir;
1964 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1966 else // more than one switch -- set it like the first switch
1968 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1973 case EL_LIGHT_SWITCH_ACTIVE:
1975 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1978 case EL_INVISIBLE_STEELWALL:
1979 case EL_INVISIBLE_WALL:
1980 case EL_INVISIBLE_SAND:
1981 if (game.light_time_left > 0 ||
1982 game.lenses_time_left > 0)
1983 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1986 case EL_EMC_MAGIC_BALL:
1987 if (game.ball_active)
1988 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1991 case EL_EMC_MAGIC_BALL_SWITCH:
1992 if (game.ball_active)
1993 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1996 case EL_TRIGGER_PLAYER:
1997 case EL_TRIGGER_ELEMENT:
1998 case EL_TRIGGER_CE_VALUE:
1999 case EL_TRIGGER_CE_SCORE:
2001 case EL_ANY_ELEMENT:
2002 case EL_CURRENT_CE_VALUE:
2003 case EL_CURRENT_CE_SCORE:
2020 // reference elements should not be used on the playfield
2021 Tile[x][y] = EL_EMPTY;
2025 if (IS_CUSTOM_ELEMENT(element))
2027 if (CAN_MOVE(element))
2030 if (!element_info[element].use_last_ce_value || init_game)
2031 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2033 else if (IS_GROUP_ELEMENT(element))
2035 Tile[x][y] = GetElementFromGroupElement(element);
2037 InitField(x, y, init_game);
2039 else if (IS_EMPTY_ELEMENT(element))
2041 GfxElementEmpty[x][y] = element;
2042 Tile[x][y] = EL_EMPTY;
2044 if (element_info[element].use_gfx_element)
2045 game.use_masked_elements = TRUE;
2052 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2055 static void InitField_WithBug1(int x, int y, boolean init_game)
2057 InitField(x, y, init_game);
2059 // not needed to call InitMovDir() -- already done by InitField()!
2060 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2061 CAN_MOVE(Tile[x][y]))
2065 static void InitField_WithBug2(int x, int y, boolean init_game)
2067 int old_element = Tile[x][y];
2069 InitField(x, y, init_game);
2071 // not needed to call InitMovDir() -- already done by InitField()!
2072 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2073 CAN_MOVE(old_element) &&
2074 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2077 /* this case is in fact a combination of not less than three bugs:
2078 first, it calls InitMovDir() for elements that can move, although this is
2079 already done by InitField(); then, it checks the element that was at this
2080 field _before_ the call to InitField() (which can change it); lastly, it
2081 was not called for "mole with direction" elements, which were treated as
2082 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2086 static int get_key_element_from_nr(int key_nr)
2088 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2089 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2090 EL_EM_KEY_1 : EL_KEY_1);
2092 return key_base_element + key_nr;
2095 static int get_next_dropped_element(struct PlayerInfo *player)
2097 return (player->inventory_size > 0 ?
2098 player->inventory_element[player->inventory_size - 1] :
2099 player->inventory_infinite_element != EL_UNDEFINED ?
2100 player->inventory_infinite_element :
2101 player->dynabombs_left > 0 ?
2102 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2106 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2108 // pos >= 0: get element from bottom of the stack;
2109 // pos < 0: get element from top of the stack
2113 int min_inventory_size = -pos;
2114 int inventory_pos = player->inventory_size - min_inventory_size;
2115 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2117 return (player->inventory_size >= min_inventory_size ?
2118 player->inventory_element[inventory_pos] :
2119 player->inventory_infinite_element != EL_UNDEFINED ?
2120 player->inventory_infinite_element :
2121 player->dynabombs_left >= min_dynabombs_left ?
2122 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2127 int min_dynabombs_left = pos + 1;
2128 int min_inventory_size = pos + 1 - player->dynabombs_left;
2129 int inventory_pos = pos - player->dynabombs_left;
2131 return (player->inventory_infinite_element != EL_UNDEFINED ?
2132 player->inventory_infinite_element :
2133 player->dynabombs_left >= min_dynabombs_left ?
2134 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2135 player->inventory_size >= min_inventory_size ?
2136 player->inventory_element[inventory_pos] :
2141 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2143 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2144 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2147 if (gpo1->sort_priority != gpo2->sort_priority)
2148 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2150 compare_result = gpo1->nr - gpo2->nr;
2152 return compare_result;
2155 int getPlayerInventorySize(int player_nr)
2157 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2158 return game_em.ply[player_nr]->dynamite;
2159 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2160 return game_sp.red_disk_count;
2162 return stored_player[player_nr].inventory_size;
2165 static void InitGameControlValues(void)
2169 for (i = 0; game_panel_controls[i].nr != -1; i++)
2171 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2172 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2173 struct TextPosInfo *pos = gpc->pos;
2175 int type = gpc->type;
2179 Error("'game_panel_controls' structure corrupted at %d", i);
2181 Fail("this should not happen -- please debug");
2184 // force update of game controls after initialization
2185 gpc->value = gpc->last_value = -1;
2186 gpc->frame = gpc->last_frame = -1;
2187 gpc->gfx_frame = -1;
2189 // determine panel value width for later calculation of alignment
2190 if (type == TYPE_INTEGER || type == TYPE_STRING)
2192 pos->width = pos->size * getFontWidth(pos->font);
2193 pos->height = getFontHeight(pos->font);
2195 else if (type == TYPE_ELEMENT)
2197 pos->width = pos->size;
2198 pos->height = pos->size;
2201 // fill structure for game panel draw order
2203 gpo->sort_priority = pos->sort_priority;
2206 // sort game panel controls according to sort_priority and control number
2207 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2208 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2211 static void UpdatePlayfieldElementCount(void)
2213 boolean use_element_count = FALSE;
2216 // first check if it is needed at all to calculate playfield element count
2217 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2218 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2219 use_element_count = TRUE;
2221 if (!use_element_count)
2224 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2225 element_info[i].element_count = 0;
2227 SCAN_PLAYFIELD(x, y)
2229 element_info[Tile[x][y]].element_count++;
2232 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2233 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2234 if (IS_IN_GROUP(j, i))
2235 element_info[EL_GROUP_START + i].element_count +=
2236 element_info[j].element_count;
2239 static void UpdateGameControlValues(void)
2242 int time = (game.LevelSolved ?
2243 game.LevelSolved_CountingTime :
2244 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2246 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2247 game_sp.time_played :
2248 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2249 game_mm.energy_left :
2250 game.no_level_time_limit ? TimePlayed : TimeLeft);
2251 int score = (game.LevelSolved ?
2252 game.LevelSolved_CountingScore :
2253 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2254 game_em.lev->score :
2255 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2257 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2260 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2261 game_em.lev->gems_needed :
2262 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2263 game_sp.infotrons_still_needed :
2264 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2265 game_mm.kettles_still_needed :
2266 game.gems_still_needed);
2267 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2268 game_em.lev->gems_needed > 0 :
2269 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2270 game_sp.infotrons_still_needed > 0 :
2271 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2272 game_mm.kettles_still_needed > 0 ||
2273 game_mm.lights_still_needed > 0 :
2274 game.gems_still_needed > 0 ||
2275 game.sokoban_fields_still_needed > 0 ||
2276 game.sokoban_objects_still_needed > 0 ||
2277 game.lights_still_needed > 0);
2278 int health = (game.LevelSolved ?
2279 game.LevelSolved_CountingHealth :
2280 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2281 MM_HEALTH(game_mm.laser_overload_value) :
2283 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2285 UpdatePlayfieldElementCount();
2287 // update game panel control values
2289 // used instead of "level_nr" (for network games)
2290 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2291 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2293 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2294 for (i = 0; i < MAX_NUM_KEYS; i++)
2295 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2296 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2297 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2299 if (game.centered_player_nr == -1)
2301 for (i = 0; i < MAX_PLAYERS; i++)
2303 // only one player in Supaplex game engine
2304 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2307 for (k = 0; k < MAX_NUM_KEYS; k++)
2309 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2311 if (game_em.ply[i]->keys & (1 << k))
2312 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2313 get_key_element_from_nr(k);
2315 else if (stored_player[i].key[k])
2316 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2317 get_key_element_from_nr(k);
2320 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2321 getPlayerInventorySize(i);
2323 if (stored_player[i].num_white_keys > 0)
2324 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2327 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2328 stored_player[i].num_white_keys;
2333 int player_nr = game.centered_player_nr;
2335 for (k = 0; k < MAX_NUM_KEYS; k++)
2337 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2339 if (game_em.ply[player_nr]->keys & (1 << k))
2340 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2341 get_key_element_from_nr(k);
2343 else if (stored_player[player_nr].key[k])
2344 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2345 get_key_element_from_nr(k);
2348 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2349 getPlayerInventorySize(player_nr);
2351 if (stored_player[player_nr].num_white_keys > 0)
2352 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2354 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2355 stored_player[player_nr].num_white_keys;
2358 // re-arrange keys on game panel, if needed or if defined by style settings
2359 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2361 int nr = GAME_PANEL_KEY_1 + i;
2362 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2363 struct TextPosInfo *pos = gpc->pos;
2365 // skip check if key is not in the player's inventory
2366 if (gpc->value == EL_EMPTY)
2369 // check if keys should be arranged on panel from left to right
2370 if (pos->style == STYLE_LEFTMOST_POSITION)
2372 // check previous key positions (left from current key)
2373 for (k = 0; k < i; k++)
2375 int nr_new = GAME_PANEL_KEY_1 + k;
2377 if (game_panel_controls[nr_new].value == EL_EMPTY)
2379 game_panel_controls[nr_new].value = gpc->value;
2380 gpc->value = EL_EMPTY;
2387 // check if "undefined" keys can be placed at some other position
2388 if (pos->x == -1 && pos->y == -1)
2390 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2392 // 1st try: display key at the same position as normal or EM keys
2393 if (game_panel_controls[nr_new].value == EL_EMPTY)
2395 game_panel_controls[nr_new].value = gpc->value;
2399 // 2nd try: display key at the next free position in the key panel
2400 for (k = 0; k < STD_NUM_KEYS; k++)
2402 nr_new = GAME_PANEL_KEY_1 + k;
2404 if (game_panel_controls[nr_new].value == EL_EMPTY)
2406 game_panel_controls[nr_new].value = gpc->value;
2415 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2417 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2418 get_inventory_element_from_pos(local_player, i);
2419 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2420 get_inventory_element_from_pos(local_player, -i - 1);
2423 game_panel_controls[GAME_PANEL_SCORE].value = score;
2424 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2426 game_panel_controls[GAME_PANEL_TIME].value = time;
2428 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2429 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2430 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2432 if (level.time == 0)
2433 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2435 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2437 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2438 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2440 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2442 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2443 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2445 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2446 local_player->shield_normal_time_left;
2447 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2448 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2450 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2451 local_player->shield_deadly_time_left;
2453 game_panel_controls[GAME_PANEL_EXIT].value =
2454 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2456 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2457 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2458 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2459 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2460 EL_EMC_MAGIC_BALL_SWITCH);
2462 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2463 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2464 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2465 game.light_time_left;
2467 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2468 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2469 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2470 game.timegate_time_left;
2472 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2473 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2475 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2476 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2477 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2478 game.lenses_time_left;
2480 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2481 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2482 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2483 game.magnify_time_left;
2485 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2486 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2487 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2488 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2489 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2490 EL_BALLOON_SWITCH_NONE);
2492 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2493 local_player->dynabomb_count;
2494 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2495 local_player->dynabomb_size;
2496 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2497 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2499 game_panel_controls[GAME_PANEL_PENGUINS].value =
2500 game.friends_still_needed;
2502 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2503 game.sokoban_objects_still_needed;
2504 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2505 game.sokoban_fields_still_needed;
2507 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2508 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2510 for (i = 0; i < NUM_BELTS; i++)
2512 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2513 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2514 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2515 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2516 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2519 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2520 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2521 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2522 game.magic_wall_time_left;
2524 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2525 local_player->gravity;
2527 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2528 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2530 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2531 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2532 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2533 game.panel.element[i].id : EL_UNDEFINED);
2535 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2536 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2537 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2538 element_info[game.panel.element_count[i].id].element_count : 0);
2540 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2541 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2542 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2543 element_info[game.panel.ce_score[i].id].collect_score : 0);
2545 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2546 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2547 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2548 element_info[game.panel.ce_score_element[i].id].collect_score :
2551 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2552 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2553 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2555 // update game panel control frames
2557 for (i = 0; game_panel_controls[i].nr != -1; i++)
2559 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2561 if (gpc->type == TYPE_ELEMENT)
2563 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2565 int last_anim_random_frame = gfx.anim_random_frame;
2566 int element = gpc->value;
2567 int graphic = el2panelimg(element);
2568 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2570 graphic_info[graphic].anim_global_anim_sync ?
2571 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2573 if (gpc->value != gpc->last_value)
2576 gpc->gfx_random = init_gfx_random;
2582 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2583 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2584 gpc->gfx_random = init_gfx_random;
2587 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2588 gfx.anim_random_frame = gpc->gfx_random;
2590 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2591 gpc->gfx_frame = element_info[element].collect_score;
2593 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2595 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2596 gfx.anim_random_frame = last_anim_random_frame;
2599 else if (gpc->type == TYPE_GRAPHIC)
2601 if (gpc->graphic != IMG_UNDEFINED)
2603 int last_anim_random_frame = gfx.anim_random_frame;
2604 int graphic = gpc->graphic;
2605 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2607 graphic_info[graphic].anim_global_anim_sync ?
2608 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2610 if (gpc->value != gpc->last_value)
2613 gpc->gfx_random = init_gfx_random;
2619 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2620 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2621 gpc->gfx_random = init_gfx_random;
2624 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2625 gfx.anim_random_frame = gpc->gfx_random;
2627 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2629 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2630 gfx.anim_random_frame = last_anim_random_frame;
2636 static void DisplayGameControlValues(void)
2638 boolean redraw_panel = FALSE;
2641 for (i = 0; game_panel_controls[i].nr != -1; i++)
2643 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2645 if (PANEL_DEACTIVATED(gpc->pos))
2648 if (gpc->value == gpc->last_value &&
2649 gpc->frame == gpc->last_frame)
2652 redraw_panel = TRUE;
2658 // copy default game door content to main double buffer
2660 // !!! CHECK AGAIN !!!
2661 SetPanelBackground();
2662 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2663 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2665 // redraw game control buttons
2666 RedrawGameButtons();
2668 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2670 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2672 int nr = game_panel_order[i].nr;
2673 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2674 struct TextPosInfo *pos = gpc->pos;
2675 int type = gpc->type;
2676 int value = gpc->value;
2677 int frame = gpc->frame;
2678 int size = pos->size;
2679 int font = pos->font;
2680 boolean draw_masked = pos->draw_masked;
2681 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2683 if (PANEL_DEACTIVATED(pos))
2686 if (pos->class == get_hash_from_key("extra_panel_items") &&
2687 !setup.prefer_extra_panel_items)
2690 gpc->last_value = value;
2691 gpc->last_frame = frame;
2693 if (type == TYPE_INTEGER)
2695 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2696 nr == GAME_PANEL_INVENTORY_COUNT ||
2697 nr == GAME_PANEL_SCORE ||
2698 nr == GAME_PANEL_HIGHSCORE ||
2699 nr == GAME_PANEL_TIME)
2701 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2703 if (use_dynamic_size) // use dynamic number of digits
2705 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 :
2706 nr == GAME_PANEL_INVENTORY_COUNT ||
2707 nr == GAME_PANEL_TIME ? 1000 : 100000);
2708 int size_add = (nr == GAME_PANEL_LEVEL_NUMBER ||
2709 nr == GAME_PANEL_INVENTORY_COUNT ||
2710 nr == GAME_PANEL_TIME ? 1 : 2);
2711 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 :
2712 nr == GAME_PANEL_INVENTORY_COUNT ||
2713 nr == GAME_PANEL_TIME ? 3 : 5);
2714 int size2 = size1 + size_add;
2715 int font1 = pos->font;
2716 int font2 = pos->font_alt;
2718 size = (value < value_change ? size1 : size2);
2719 font = (value < value_change ? font1 : font2);
2723 // correct text size if "digits" is zero or less
2725 size = strlen(int2str(value, size));
2727 // dynamically correct text alignment
2728 pos->width = size * getFontWidth(font);
2730 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2731 int2str(value, size), font, mask_mode);
2733 else if (type == TYPE_ELEMENT)
2735 int element, graphic;
2739 int dst_x = PANEL_XPOS(pos);
2740 int dst_y = PANEL_YPOS(pos);
2742 if (value != EL_UNDEFINED && value != EL_EMPTY)
2745 graphic = el2panelimg(value);
2748 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2749 element, EL_NAME(element), size);
2752 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2755 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2758 width = graphic_info[graphic].width * size / TILESIZE;
2759 height = graphic_info[graphic].height * size / TILESIZE;
2762 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2765 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2769 else if (type == TYPE_GRAPHIC)
2771 int graphic = gpc->graphic;
2772 int graphic_active = gpc->graphic_active;
2776 int dst_x = PANEL_XPOS(pos);
2777 int dst_y = PANEL_YPOS(pos);
2778 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2779 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2781 if (graphic != IMG_UNDEFINED && !skip)
2783 if (pos->style == STYLE_REVERSE)
2784 value = 100 - value;
2786 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2788 if (pos->direction & MV_HORIZONTAL)
2790 width = graphic_info[graphic_active].width * value / 100;
2791 height = graphic_info[graphic_active].height;
2793 if (pos->direction == MV_LEFT)
2795 src_x += graphic_info[graphic_active].width - width;
2796 dst_x += graphic_info[graphic_active].width - width;
2801 width = graphic_info[graphic_active].width;
2802 height = graphic_info[graphic_active].height * value / 100;
2804 if (pos->direction == MV_UP)
2806 src_y += graphic_info[graphic_active].height - height;
2807 dst_y += graphic_info[graphic_active].height - height;
2812 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2815 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2818 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2820 if (pos->direction & MV_HORIZONTAL)
2822 if (pos->direction == MV_RIGHT)
2829 dst_x = PANEL_XPOS(pos);
2832 width = graphic_info[graphic].width - width;
2836 if (pos->direction == MV_DOWN)
2843 dst_y = PANEL_YPOS(pos);
2846 height = graphic_info[graphic].height - height;
2850 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2853 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2857 else if (type == TYPE_STRING)
2859 boolean active = (value != 0);
2860 char *state_normal = "off";
2861 char *state_active = "on";
2862 char *state = (active ? state_active : state_normal);
2863 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2864 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2865 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2866 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2868 if (nr == GAME_PANEL_GRAVITY_STATE)
2870 int font1 = pos->font; // (used for normal state)
2871 int font2 = pos->font_alt; // (used for active state)
2873 font = (active ? font2 : font1);
2882 // don't truncate output if "chars" is zero or less
2885 // dynamically correct text alignment
2886 pos->width = size * getFontWidth(font);
2889 s_cut = getStringCopyN(s, size);
2891 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2892 s_cut, font, mask_mode);
2898 redraw_mask |= REDRAW_DOOR_1;
2901 SetGameStatus(GAME_MODE_PLAYING);
2904 void UpdateAndDisplayGameControlValues(void)
2906 if (tape.deactivate_display)
2909 UpdateGameControlValues();
2910 DisplayGameControlValues();
2913 void UpdateGameDoorValues(void)
2915 UpdateGameControlValues();
2918 void DrawGameDoorValues(void)
2920 DisplayGameControlValues();
2924 // ============================================================================
2926 // ----------------------------------------------------------------------------
2927 // initialize game engine due to level / tape version number
2928 // ============================================================================
2930 static void InitGameEngine(void)
2932 int i, j, k, l, x, y;
2934 // set game engine from tape file when re-playing, else from level file
2935 game.engine_version = (tape.playing ? tape.engine_version :
2936 level.game_version);
2938 // set single or multi-player game mode (needed for re-playing tapes)
2939 game.team_mode = setup.team_mode;
2943 int num_players = 0;
2945 for (i = 0; i < MAX_PLAYERS; i++)
2946 if (tape.player_participates[i])
2949 // multi-player tapes contain input data for more than one player
2950 game.team_mode = (num_players > 1);
2954 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2955 level.game_version);
2956 Debug("game:init:level", " tape.file_version == %06d",
2958 Debug("game:init:level", " tape.game_version == %06d",
2960 Debug("game:init:level", " tape.engine_version == %06d",
2961 tape.engine_version);
2962 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2963 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2966 // --------------------------------------------------------------------------
2967 // set flags for bugs and changes according to active game engine version
2968 // --------------------------------------------------------------------------
2972 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2974 Bug was introduced in version:
2977 Bug was fixed in version:
2981 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2982 but the property "can fall" was missing, which caused some levels to be
2983 unsolvable. This was fixed in version 4.2.0.0.
2985 Affected levels/tapes:
2986 An example for a tape that was fixed by this bugfix is tape 029 from the
2987 level set "rnd_sam_bateman".
2988 The wrong behaviour will still be used for all levels or tapes that were
2989 created/recorded with it. An example for this is tape 023 from the level
2990 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2993 boolean use_amoeba_dropping_cannot_fall_bug =
2994 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2995 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2997 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2998 tape.game_version < VERSION_IDENT(4,2,0,0)));
3001 Summary of bugfix/change:
3002 Fixed move speed of elements entering or leaving magic wall.
3004 Fixed/changed in version:
3008 Before 2.0.1, move speed of elements entering or leaving magic wall was
3009 twice as fast as it is now.
3010 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
3012 Affected levels/tapes:
3013 The first condition is generally needed for all levels/tapes before version
3014 2.0.1, which might use the old behaviour before it was changed; known tapes
3015 that are affected: Tape 014 from the level set "rnd_conor_mancone".
3016 The second condition is an exception from the above case and is needed for
3017 the special case of tapes recorded with game (not engine!) version 2.0.1 or
3018 above, but before it was known that this change would break tapes like the
3019 above and was fixed in 4.2.0.0, so that the changed behaviour was active
3020 although the engine version while recording maybe was before 2.0.1. There
3021 are a lot of tapes that are affected by this exception, like tape 006 from
3022 the level set "rnd_conor_mancone".
3025 boolean use_old_move_stepsize_for_magic_wall =
3026 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
3028 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3029 tape.game_version < VERSION_IDENT(4,2,0,0)));
3032 Summary of bugfix/change:
3033 Fixed handling for custom elements that change when pushed by the player.
3035 Fixed/changed in version:
3039 Before 3.1.0, custom elements that "change when pushing" changed directly
3040 after the player started pushing them (until then handled in "DigField()").
3041 Since 3.1.0, these custom elements are not changed until the "pushing"
3042 move of the element is finished (now handled in "ContinueMoving()").
3044 Affected levels/tapes:
3045 The first condition is generally needed for all levels/tapes before version
3046 3.1.0, which might use the old behaviour before it was changed; known tapes
3047 that are affected are some tapes from the level set "Walpurgis Gardens" by
3049 The second condition is an exception from the above case and is needed for
3050 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3051 above (including some development versions of 3.1.0), but before it was
3052 known that this change would break tapes like the above and was fixed in
3053 3.1.1, so that the changed behaviour was active although the engine version
3054 while recording maybe was before 3.1.0. There is at least one tape that is
3055 affected by this exception, which is the tape for the one-level set "Bug
3056 Machine" by Juergen Bonhagen.
3059 game.use_change_when_pushing_bug =
3060 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3062 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3063 tape.game_version < VERSION_IDENT(3,1,1,0)));
3066 Summary of bugfix/change:
3067 Fixed handling for blocking the field the player leaves when moving.
3069 Fixed/changed in version:
3073 Before 3.1.1, when "block last field when moving" was enabled, the field
3074 the player is leaving when moving was blocked for the time of the move,
3075 and was directly unblocked afterwards. This resulted in the last field
3076 being blocked for exactly one less than the number of frames of one player
3077 move. Additionally, even when blocking was disabled, the last field was
3078 blocked for exactly one frame.
3079 Since 3.1.1, due to changes in player movement handling, the last field
3080 is not blocked at all when blocking is disabled. When blocking is enabled,
3081 the last field is blocked for exactly the number of frames of one player
3082 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3083 last field is blocked for exactly one more than the number of frames of
3086 Affected levels/tapes:
3087 (!!! yet to be determined -- probably many !!!)
3090 game.use_block_last_field_bug =
3091 (game.engine_version < VERSION_IDENT(3,1,1,0));
3093 /* various special flags and settings for native Emerald Mine game engine */
3095 game_em.use_single_button =
3096 (game.engine_version > VERSION_IDENT(4,0,0,2));
3098 game_em.use_snap_key_bug =
3099 (game.engine_version < VERSION_IDENT(4,0,1,0));
3101 game_em.use_random_bug =
3102 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3104 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3106 game_em.use_old_explosions = use_old_em_engine;
3107 game_em.use_old_android = use_old_em_engine;
3108 game_em.use_old_push_elements = use_old_em_engine;
3109 game_em.use_old_push_into_acid = use_old_em_engine;
3111 game_em.use_wrap_around = !use_old_em_engine;
3113 // --------------------------------------------------------------------------
3115 // set maximal allowed number of custom element changes per game frame
3116 game.max_num_changes_per_frame = 1;
3118 // default scan direction: scan playfield from top/left to bottom/right
3119 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3121 // dynamically adjust element properties according to game engine version
3122 InitElementPropertiesEngine(game.engine_version);
3124 // ---------- initialize special element properties -------------------------
3126 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3127 if (use_amoeba_dropping_cannot_fall_bug)
3128 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3130 // ---------- initialize player's initial move delay ------------------------
3132 // dynamically adjust player properties according to level information
3133 for (i = 0; i < MAX_PLAYERS; i++)
3134 game.initial_move_delay_value[i] =
3135 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3137 // dynamically adjust player properties according to game engine version
3138 for (i = 0; i < MAX_PLAYERS; i++)
3139 game.initial_move_delay[i] =
3140 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3141 game.initial_move_delay_value[i] : 0);
3143 // ---------- initialize player's initial push delay ------------------------
3145 // dynamically adjust player properties according to game engine version
3146 game.initial_push_delay_value =
3147 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3149 // ---------- initialize changing elements ----------------------------------
3151 // initialize changing elements information
3152 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3154 struct ElementInfo *ei = &element_info[i];
3156 // this pointer might have been changed in the level editor
3157 ei->change = &ei->change_page[0];
3159 if (!IS_CUSTOM_ELEMENT(i))
3161 ei->change->target_element = EL_EMPTY_SPACE;
3162 ei->change->delay_fixed = 0;
3163 ei->change->delay_random = 0;
3164 ei->change->delay_frames = 1;
3167 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3169 ei->has_change_event[j] = FALSE;
3171 ei->event_page_nr[j] = 0;
3172 ei->event_page[j] = &ei->change_page[0];
3176 // add changing elements from pre-defined list
3177 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3179 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3180 struct ElementInfo *ei = &element_info[ch_delay->element];
3182 ei->change->target_element = ch_delay->target_element;
3183 ei->change->delay_fixed = ch_delay->change_delay;
3185 ei->change->pre_change_function = ch_delay->pre_change_function;
3186 ei->change->change_function = ch_delay->change_function;
3187 ei->change->post_change_function = ch_delay->post_change_function;
3189 ei->change->can_change = TRUE;
3190 ei->change->can_change_or_has_action = TRUE;
3192 ei->has_change_event[CE_DELAY] = TRUE;
3194 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3195 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3198 // ---------- initialize if element can trigger global animations -----------
3200 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3202 struct ElementInfo *ei = &element_info[i];
3204 ei->has_anim_event = FALSE;
3207 InitGlobalAnimEventsForCustomElements();
3209 // ---------- initialize internal run-time variables ------------------------
3211 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3213 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3215 for (j = 0; j < ei->num_change_pages; j++)
3217 ei->change_page[j].can_change_or_has_action =
3218 (ei->change_page[j].can_change |
3219 ei->change_page[j].has_action);
3223 // add change events from custom element configuration
3224 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3226 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3228 for (j = 0; j < ei->num_change_pages; j++)
3230 if (!ei->change_page[j].can_change_or_has_action)
3233 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3235 // only add event page for the first page found with this event
3236 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3238 ei->has_change_event[k] = TRUE;
3240 ei->event_page_nr[k] = j;
3241 ei->event_page[k] = &ei->change_page[j];
3247 // ---------- initialize reference elements in change conditions ------------
3249 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3251 int element = EL_CUSTOM_START + i;
3252 struct ElementInfo *ei = &element_info[element];
3254 for (j = 0; j < ei->num_change_pages; j++)
3256 int trigger_element = ei->change_page[j].initial_trigger_element;
3258 if (trigger_element >= EL_PREV_CE_8 &&
3259 trigger_element <= EL_NEXT_CE_8)
3260 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3262 ei->change_page[j].trigger_element = trigger_element;
3266 // ---------- initialize run-time trigger player and element ----------------
3268 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3270 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3272 for (j = 0; j < ei->num_change_pages; j++)
3274 struct ElementChangeInfo *change = &ei->change_page[j];
3276 change->actual_trigger_element = EL_EMPTY;
3277 change->actual_trigger_player = EL_EMPTY;
3278 change->actual_trigger_player_bits = CH_PLAYER_NONE;
3279 change->actual_trigger_side = CH_SIDE_NONE;
3280 change->actual_trigger_ce_value = 0;
3281 change->actual_trigger_ce_score = 0;
3282 change->actual_trigger_x = -1;
3283 change->actual_trigger_y = -1;
3287 // ---------- initialize trigger events -------------------------------------
3289 // initialize trigger events information
3290 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3291 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3292 trigger_events[i][j] = FALSE;
3294 // add trigger events from element change event properties
3295 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3297 struct ElementInfo *ei = &element_info[i];
3299 for (j = 0; j < ei->num_change_pages; j++)
3301 struct ElementChangeInfo *change = &ei->change_page[j];
3303 if (!change->can_change_or_has_action)
3306 if (change->has_event[CE_BY_OTHER_ACTION])
3308 int trigger_element = change->trigger_element;
3310 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3312 if (change->has_event[k])
3314 if (IS_GROUP_ELEMENT(trigger_element))
3316 struct ElementGroupInfo *group =
3317 element_info[trigger_element].group;
3319 for (l = 0; l < group->num_elements_resolved; l++)
3320 trigger_events[group->element_resolved[l]][k] = TRUE;
3322 else if (trigger_element == EL_ANY_ELEMENT)
3323 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3324 trigger_events[l][k] = TRUE;
3326 trigger_events[trigger_element][k] = TRUE;
3333 // ---------- initialize push delay -----------------------------------------
3335 // initialize push delay values to default
3336 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3338 if (!IS_CUSTOM_ELEMENT(i))
3340 // set default push delay values (corrected since version 3.0.7-1)
3341 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3343 element_info[i].push_delay_fixed = 2;
3344 element_info[i].push_delay_random = 8;
3348 element_info[i].push_delay_fixed = 8;
3349 element_info[i].push_delay_random = 8;
3354 // set push delay value for certain elements from pre-defined list
3355 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3357 int e = push_delay_list[i].element;
3359 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3360 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3363 // set push delay value for Supaplex elements for newer engine versions
3364 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3366 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3368 if (IS_SP_ELEMENT(i))
3370 // set SP push delay to just enough to push under a falling zonk
3371 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3373 element_info[i].push_delay_fixed = delay;
3374 element_info[i].push_delay_random = 0;
3379 // ---------- initialize move stepsize --------------------------------------
3381 // initialize move stepsize values to default
3382 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3383 if (!IS_CUSTOM_ELEMENT(i))
3384 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3386 // set move stepsize value for certain elements from pre-defined list
3387 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3389 int e = move_stepsize_list[i].element;
3391 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3393 // set move stepsize value for certain elements for older engine versions
3394 if (use_old_move_stepsize_for_magic_wall)
3396 if (e == EL_MAGIC_WALL_FILLING ||
3397 e == EL_MAGIC_WALL_EMPTYING ||
3398 e == EL_BD_MAGIC_WALL_FILLING ||
3399 e == EL_BD_MAGIC_WALL_EMPTYING)
3400 element_info[e].move_stepsize *= 2;
3404 // ---------- initialize collect score --------------------------------------
3406 // initialize collect score values for custom elements from initial value
3407 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3408 if (IS_CUSTOM_ELEMENT(i))
3409 element_info[i].collect_score = element_info[i].collect_score_initial;
3411 // ---------- initialize collect count --------------------------------------
3413 // initialize collect count values for non-custom elements
3414 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3415 if (!IS_CUSTOM_ELEMENT(i))
3416 element_info[i].collect_count_initial = 0;
3418 // add collect count values for all elements from pre-defined list
3419 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3420 element_info[collect_count_list[i].element].collect_count_initial =
3421 collect_count_list[i].count;
3423 // ---------- initialize access direction -----------------------------------
3425 // initialize access direction values to default (access from every side)
3426 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3427 if (!IS_CUSTOM_ELEMENT(i))
3428 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3430 // set access direction value for certain elements from pre-defined list
3431 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3432 element_info[access_direction_list[i].element].access_direction =
3433 access_direction_list[i].direction;
3435 // ---------- initialize explosion content ----------------------------------
3436 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3438 if (IS_CUSTOM_ELEMENT(i))
3441 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3443 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3445 element_info[i].content.e[x][y] =
3446 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3447 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3448 i == EL_PLAYER_3 ? EL_EMERALD :
3449 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3450 i == EL_MOLE ? EL_EMERALD_RED :
3451 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3452 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3453 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3454 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3455 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3456 i == EL_WALL_EMERALD ? EL_EMERALD :
3457 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3458 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3459 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3460 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3461 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3462 i == EL_WALL_PEARL ? EL_PEARL :
3463 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3468 // ---------- initialize recursion detection --------------------------------
3469 recursion_loop_depth = 0;
3470 recursion_loop_detected = FALSE;
3471 recursion_loop_element = EL_UNDEFINED;
3473 // ---------- initialize graphics engine ------------------------------------
3474 game.scroll_delay_value =
3475 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3476 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3477 !setup.forced_scroll_delay ? 0 :
3478 setup.scroll_delay ? setup.scroll_delay_value : 0);
3479 if (game.forced_scroll_delay_value == -1)
3480 game.scroll_delay_value =
3481 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3483 // ---------- initialize game engine snapshots ------------------------------
3484 for (i = 0; i < MAX_PLAYERS; i++)
3485 game.snapshot.last_action[i] = 0;
3486 game.snapshot.changed_action = FALSE;
3487 game.snapshot.collected_item = FALSE;
3488 game.snapshot.mode =
3489 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3490 SNAPSHOT_MODE_EVERY_STEP :
3491 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3492 SNAPSHOT_MODE_EVERY_MOVE :
3493 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3494 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3495 game.snapshot.save_snapshot = FALSE;
3497 // ---------- initialize level time for Supaplex engine ---------------------
3498 // Supaplex levels with time limit currently unsupported -- should be added
3499 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3502 // ---------- initialize flags for handling game actions --------------------
3504 // set flags for game actions to default values
3505 game.use_key_actions = TRUE;
3506 game.use_mouse_actions = FALSE;
3508 // when using Mirror Magic game engine, handle mouse events only
3509 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3511 game.use_key_actions = FALSE;
3512 game.use_mouse_actions = TRUE;
3515 // check for custom elements with mouse click events
3516 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3518 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3520 int element = EL_CUSTOM_START + i;
3522 if (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3523 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3524 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3525 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3526 game.use_mouse_actions = TRUE;
3531 static int get_num_special_action(int element, int action_first,
3534 int num_special_action = 0;
3537 for (i = action_first; i <= action_last; i++)
3539 boolean found = FALSE;
3541 for (j = 0; j < NUM_DIRECTIONS; j++)
3542 if (el_act_dir2img(element, i, j) !=
3543 el_act_dir2img(element, ACTION_DEFAULT, j))
3547 num_special_action++;
3552 return num_special_action;
3556 // ============================================================================
3558 // ----------------------------------------------------------------------------
3559 // initialize and start new game
3560 // ============================================================================
3562 #if DEBUG_INIT_PLAYER
3563 static void DebugPrintPlayerStatus(char *message)
3570 Debug("game:init:player", "%s:", message);
3572 for (i = 0; i < MAX_PLAYERS; i++)
3574 struct PlayerInfo *player = &stored_player[i];
3576 Debug("game:init:player",
3577 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3581 player->connected_locally,
3582 player->connected_network,
3584 (local_player == player ? " (local player)" : ""));
3591 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3592 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3593 int fade_mask = REDRAW_FIELD;
3594 boolean restarting = (game_status == GAME_MODE_PLAYING);
3595 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3596 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3597 int initial_move_dir = MV_DOWN;
3600 // required here to update video display before fading (FIX THIS)
3601 DrawMaskedBorder(REDRAW_DOOR_2);
3603 if (!game.restart_level)
3604 CloseDoor(DOOR_CLOSE_1);
3608 // force fading out global animations displayed during game play
3609 SetGameStatus(GAME_MODE_PSEUDO_RESTARTING);
3613 SetGameStatus(GAME_MODE_PLAYING);
3616 if (level_editor_test_game)
3617 FadeSkipNextFadeOut();
3619 FadeSetEnterScreen();
3622 fade_mask = REDRAW_ALL;
3624 FadeLevelSoundsAndMusic();
3626 ExpireSoundLoops(TRUE);
3632 // force restarting global animations displayed during game play
3633 RestartGlobalAnimsByStatus(GAME_MODE_PSEUDO_RESTARTING);
3635 // this is required for "transforming" fade modes like cross-fading
3636 // (else global animations will be stopped, but not restarted here)
3637 SetAnimStatusBeforeFading(GAME_MODE_PSEUDO_RESTARTING);
3639 SetGameStatus(GAME_MODE_PLAYING);
3642 if (level_editor_test_game)
3643 FadeSkipNextFadeIn();
3645 // needed if different viewport properties defined for playing
3646 ChangeViewportPropertiesIfNeeded();
3650 DrawCompleteVideoDisplay();
3652 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3655 InitGameControlValues();
3659 // initialize tape actions from game when recording tape
3660 tape.use_key_actions = game.use_key_actions;
3661 tape.use_mouse_actions = game.use_mouse_actions;
3663 // initialize visible playfield size when recording tape (for team mode)
3664 tape.scr_fieldx = SCR_FIELDX;
3665 tape.scr_fieldy = SCR_FIELDY;
3668 // don't play tapes over network
3669 network_playing = (network.enabled && !tape.playing);
3671 for (i = 0; i < MAX_PLAYERS; i++)
3673 struct PlayerInfo *player = &stored_player[i];
3675 player->index_nr = i;
3676 player->index_bit = (1 << i);
3677 player->element_nr = EL_PLAYER_1 + i;
3679 player->present = FALSE;
3680 player->active = FALSE;
3681 player->mapped = FALSE;
3683 player->killed = FALSE;
3684 player->reanimated = FALSE;
3685 player->buried = FALSE;
3688 player->effective_action = 0;
3689 player->programmed_action = 0;
3690 player->snap_action = 0;
3692 player->mouse_action.lx = 0;
3693 player->mouse_action.ly = 0;
3694 player->mouse_action.button = 0;
3695 player->mouse_action.button_hint = 0;
3697 player->effective_mouse_action.lx = 0;
3698 player->effective_mouse_action.ly = 0;
3699 player->effective_mouse_action.button = 0;
3700 player->effective_mouse_action.button_hint = 0;
3702 for (j = 0; j < MAX_NUM_KEYS; j++)
3703 player->key[j] = FALSE;
3705 player->num_white_keys = 0;
3707 player->dynabomb_count = 0;
3708 player->dynabomb_size = 1;
3709 player->dynabombs_left = 0;
3710 player->dynabomb_xl = FALSE;
3712 player->MovDir = initial_move_dir;
3715 player->GfxDir = initial_move_dir;
3716 player->GfxAction = ACTION_DEFAULT;
3718 player->StepFrame = 0;
3720 player->initial_element = player->element_nr;
3721 player->artwork_element =
3722 (level.use_artwork_element[i] ? level.artwork_element[i] :
3723 player->element_nr);
3724 player->use_murphy = FALSE;
3726 player->block_last_field = FALSE; // initialized in InitPlayerField()
3727 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3729 player->gravity = level.initial_player_gravity[i];
3731 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3733 player->actual_frame_counter.count = 0;
3734 player->actual_frame_counter.value = 1;
3736 player->step_counter = 0;
3738 player->last_move_dir = initial_move_dir;
3740 player->is_active = FALSE;
3742 player->is_waiting = FALSE;
3743 player->is_moving = FALSE;
3744 player->is_auto_moving = FALSE;
3745 player->is_digging = FALSE;
3746 player->is_snapping = FALSE;
3747 player->is_collecting = FALSE;
3748 player->is_pushing = FALSE;
3749 player->is_switching = FALSE;
3750 player->is_dropping = FALSE;
3751 player->is_dropping_pressed = FALSE;
3753 player->is_bored = FALSE;
3754 player->is_sleeping = FALSE;
3756 player->was_waiting = TRUE;
3757 player->was_moving = FALSE;
3758 player->was_snapping = FALSE;
3759 player->was_dropping = FALSE;
3761 player->force_dropping = FALSE;
3763 player->frame_counter_bored = -1;
3764 player->frame_counter_sleeping = -1;
3766 player->anim_delay_counter = 0;
3767 player->post_delay_counter = 0;
3769 player->dir_waiting = initial_move_dir;
3770 player->action_waiting = ACTION_DEFAULT;
3771 player->last_action_waiting = ACTION_DEFAULT;
3772 player->special_action_bored = ACTION_DEFAULT;
3773 player->special_action_sleeping = ACTION_DEFAULT;
3775 player->switch_x = -1;
3776 player->switch_y = -1;
3778 player->drop_x = -1;
3779 player->drop_y = -1;
3781 player->show_envelope = 0;
3783 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3785 player->push_delay = -1; // initialized when pushing starts
3786 player->push_delay_value = game.initial_push_delay_value;
3788 player->drop_delay = 0;
3789 player->drop_pressed_delay = 0;
3791 player->last_jx = -1;
3792 player->last_jy = -1;
3796 player->shield_normal_time_left = 0;
3797 player->shield_deadly_time_left = 0;
3799 player->last_removed_element = EL_UNDEFINED;
3801 player->inventory_infinite_element = EL_UNDEFINED;
3802 player->inventory_size = 0;
3804 if (level.use_initial_inventory[i])
3806 for (j = 0; j < level.initial_inventory_size[i]; j++)
3808 int element = level.initial_inventory_content[i][j];
3809 int collect_count = element_info[element].collect_count_initial;
3812 if (!IS_CUSTOM_ELEMENT(element))
3815 if (collect_count == 0)
3816 player->inventory_infinite_element = element;
3818 for (k = 0; k < collect_count; k++)
3819 if (player->inventory_size < MAX_INVENTORY_SIZE)
3820 player->inventory_element[player->inventory_size++] = element;
3824 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3825 SnapField(player, 0, 0);
3827 map_player_action[i] = i;
3830 network_player_action_received = FALSE;
3832 // initial null action
3833 if (network_playing)
3834 SendToServer_MovePlayer(MV_NONE);
3839 TimeLeft = level.time;
3842 ScreenMovDir = MV_NONE;
3846 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3848 game.robot_wheel_x = -1;
3849 game.robot_wheel_y = -1;
3854 game.all_players_gone = FALSE;
3856 game.LevelSolved = FALSE;
3857 game.GameOver = FALSE;
3859 game.GamePlayed = !tape.playing;
3861 game.LevelSolved_GameWon = FALSE;
3862 game.LevelSolved_GameEnd = FALSE;
3863 game.LevelSolved_SaveTape = FALSE;
3864 game.LevelSolved_SaveScore = FALSE;
3866 game.LevelSolved_CountingTime = 0;
3867 game.LevelSolved_CountingScore = 0;
3868 game.LevelSolved_CountingHealth = 0;
3870 game.panel.active = TRUE;
3872 game.no_level_time_limit = (level.time == 0);
3873 game.time_limit = (leveldir_current->time_limit && setup.time_limit);
3875 game.yamyam_content_nr = 0;
3876 game.robot_wheel_active = FALSE;
3877 game.magic_wall_active = FALSE;
3878 game.magic_wall_time_left = 0;
3879 game.light_time_left = 0;
3880 game.timegate_time_left = 0;
3881 game.switchgate_pos = 0;
3882 game.wind_direction = level.wind_direction_initial;
3884 game.time_final = 0;
3885 game.score_time_final = 0;
3888 game.score_final = 0;
3890 game.health = MAX_HEALTH;
3891 game.health_final = MAX_HEALTH;
3893 game.gems_still_needed = level.gems_needed;
3894 game.sokoban_fields_still_needed = 0;
3895 game.sokoban_objects_still_needed = 0;
3896 game.lights_still_needed = 0;
3897 game.players_still_needed = 0;
3898 game.friends_still_needed = 0;
3900 game.lenses_time_left = 0;
3901 game.magnify_time_left = 0;
3903 game.ball_active = level.ball_active_initial;
3904 game.ball_content_nr = 0;
3906 game.explosions_delayed = TRUE;
3908 game.envelope_active = FALSE;
3910 // special case: set custom artwork setting to initial value
3911 game.use_masked_elements = game.use_masked_elements_initial;
3913 for (i = 0; i < NUM_BELTS; i++)
3915 game.belt_dir[i] = MV_NONE;
3916 game.belt_dir_nr[i] = 3; // not moving, next moving left
3919 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3920 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3922 #if DEBUG_INIT_PLAYER
3923 DebugPrintPlayerStatus("Player status at level initialization");
3926 SCAN_PLAYFIELD(x, y)
3928 Tile[x][y] = Last[x][y] = level.field[x][y];
3929 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3930 ChangeDelay[x][y] = 0;
3931 ChangePage[x][y] = -1;
3932 CustomValue[x][y] = 0; // initialized in InitField()
3933 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3935 WasJustMoving[x][y] = 0;
3936 WasJustFalling[x][y] = 0;
3937 CheckCollision[x][y] = 0;
3938 CheckImpact[x][y] = 0;
3940 Pushed[x][y] = FALSE;
3942 ChangeCount[x][y] = 0;
3943 ChangeEvent[x][y] = -1;
3945 ExplodePhase[x][y] = 0;
3946 ExplodeDelay[x][y] = 0;
3947 ExplodeField[x][y] = EX_TYPE_NONE;
3949 RunnerVisit[x][y] = 0;
3950 PlayerVisit[x][y] = 0;
3953 GfxRandom[x][y] = INIT_GFX_RANDOM();
3954 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
3955 GfxElement[x][y] = EL_UNDEFINED;
3956 GfxElementEmpty[x][y] = EL_EMPTY;
3957 GfxAction[x][y] = ACTION_DEFAULT;
3958 GfxDir[x][y] = MV_NONE;
3959 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3962 SCAN_PLAYFIELD(x, y)
3964 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3966 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3969 InitField(x, y, TRUE);
3971 ResetGfxAnimation(x, y);
3976 for (i = 0; i < MAX_PLAYERS; i++)
3978 struct PlayerInfo *player = &stored_player[i];
3980 // set number of special actions for bored and sleeping animation
3981 player->num_special_action_bored =
3982 get_num_special_action(player->artwork_element,
3983 ACTION_BORING_1, ACTION_BORING_LAST);
3984 player->num_special_action_sleeping =
3985 get_num_special_action(player->artwork_element,
3986 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3989 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3990 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3992 // initialize type of slippery elements
3993 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3995 if (!IS_CUSTOM_ELEMENT(i))
3997 // default: elements slip down either to the left or right randomly
3998 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
4000 // SP style elements prefer to slip down on the left side
4001 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
4002 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4004 // BD style elements prefer to slip down on the left side
4005 if (game.emulation == EMU_BOULDERDASH)
4006 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4010 // initialize explosion and ignition delay
4011 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4013 if (!IS_CUSTOM_ELEMENT(i))
4016 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
4017 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
4018 game.emulation == EMU_SUPAPLEX ? 3 : 2);
4019 int last_phase = (num_phase + 1) * delay;
4020 int half_phase = (num_phase / 2) * delay;
4022 element_info[i].explosion_delay = last_phase - 1;
4023 element_info[i].ignition_delay = half_phase;
4025 if (i == EL_BLACK_ORB)
4026 element_info[i].ignition_delay = 1;
4030 // correct non-moving belts to start moving left
4031 for (i = 0; i < NUM_BELTS; i++)
4032 if (game.belt_dir[i] == MV_NONE)
4033 game.belt_dir_nr[i] = 3; // not moving, next moving left
4035 #if USE_NEW_PLAYER_ASSIGNMENTS
4036 // use preferred player also in local single-player mode
4037 if (!network.enabled && !game.team_mode)
4039 int new_index_nr = setup.network_player_nr;
4041 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
4043 for (i = 0; i < MAX_PLAYERS; i++)
4044 stored_player[i].connected_locally = FALSE;
4046 stored_player[new_index_nr].connected_locally = TRUE;
4050 for (i = 0; i < MAX_PLAYERS; i++)
4052 stored_player[i].connected = FALSE;
4054 // in network game mode, the local player might not be the first player
4055 if (stored_player[i].connected_locally)
4056 local_player = &stored_player[i];
4059 if (!network.enabled)
4060 local_player->connected = TRUE;
4064 for (i = 0; i < MAX_PLAYERS; i++)
4065 stored_player[i].connected = tape.player_participates[i];
4067 else if (network.enabled)
4069 // add team mode players connected over the network (needed for correct
4070 // assignment of player figures from level to locally playing players)
4072 for (i = 0; i < MAX_PLAYERS; i++)
4073 if (stored_player[i].connected_network)
4074 stored_player[i].connected = TRUE;
4076 else if (game.team_mode)
4078 // try to guess locally connected team mode players (needed for correct
4079 // assignment of player figures from level to locally playing players)
4081 for (i = 0; i < MAX_PLAYERS; i++)
4082 if (setup.input[i].use_joystick ||
4083 setup.input[i].key.left != KSYM_UNDEFINED)
4084 stored_player[i].connected = TRUE;
4087 #if DEBUG_INIT_PLAYER
4088 DebugPrintPlayerStatus("Player status after level initialization");
4091 #if DEBUG_INIT_PLAYER
4092 Debug("game:init:player", "Reassigning players ...");
4095 // check if any connected player was not found in playfield
4096 for (i = 0; i < MAX_PLAYERS; i++)
4098 struct PlayerInfo *player = &stored_player[i];
4100 if (player->connected && !player->present)
4102 struct PlayerInfo *field_player = NULL;
4104 #if DEBUG_INIT_PLAYER
4105 Debug("game:init:player",
4106 "- looking for field player for player %d ...", i + 1);
4109 // assign first free player found that is present in the playfield
4111 // first try: look for unmapped playfield player that is not connected
4112 for (j = 0; j < MAX_PLAYERS; j++)
4113 if (field_player == NULL &&
4114 stored_player[j].present &&
4115 !stored_player[j].mapped &&
4116 !stored_player[j].connected)
4117 field_player = &stored_player[j];
4119 // second try: look for *any* unmapped playfield player
4120 for (j = 0; j < MAX_PLAYERS; j++)
4121 if (field_player == NULL &&
4122 stored_player[j].present &&
4123 !stored_player[j].mapped)
4124 field_player = &stored_player[j];
4126 if (field_player != NULL)
4128 int jx = field_player->jx, jy = field_player->jy;
4130 #if DEBUG_INIT_PLAYER
4131 Debug("game:init:player", "- found player %d",
4132 field_player->index_nr + 1);
4135 player->present = FALSE;
4136 player->active = FALSE;
4138 field_player->present = TRUE;
4139 field_player->active = TRUE;
4142 player->initial_element = field_player->initial_element;
4143 player->artwork_element = field_player->artwork_element;
4145 player->block_last_field = field_player->block_last_field;
4146 player->block_delay_adjustment = field_player->block_delay_adjustment;
4149 StorePlayer[jx][jy] = field_player->element_nr;
4151 field_player->jx = field_player->last_jx = jx;
4152 field_player->jy = field_player->last_jy = jy;
4154 if (local_player == player)
4155 local_player = field_player;
4157 map_player_action[field_player->index_nr] = i;
4159 field_player->mapped = TRUE;
4161 #if DEBUG_INIT_PLAYER
4162 Debug("game:init:player", "- map_player_action[%d] == %d",
4163 field_player->index_nr + 1, i + 1);
4168 if (player->connected && player->present)
4169 player->mapped = TRUE;
4172 #if DEBUG_INIT_PLAYER
4173 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4178 // check if any connected player was not found in playfield
4179 for (i = 0; i < MAX_PLAYERS; i++)
4181 struct PlayerInfo *player = &stored_player[i];
4183 if (player->connected && !player->present)
4185 for (j = 0; j < MAX_PLAYERS; j++)
4187 struct PlayerInfo *field_player = &stored_player[j];
4188 int jx = field_player->jx, jy = field_player->jy;
4190 // assign first free player found that is present in the playfield
4191 if (field_player->present && !field_player->connected)
4193 player->present = TRUE;
4194 player->active = TRUE;
4196 field_player->present = FALSE;
4197 field_player->active = FALSE;
4199 player->initial_element = field_player->initial_element;
4200 player->artwork_element = field_player->artwork_element;
4202 player->block_last_field = field_player->block_last_field;
4203 player->block_delay_adjustment = field_player->block_delay_adjustment;
4205 StorePlayer[jx][jy] = player->element_nr;
4207 player->jx = player->last_jx = jx;
4208 player->jy = player->last_jy = jy;
4218 Debug("game:init:player", "local_player->present == %d",
4219 local_player->present);
4222 // set focus to local player for network games, else to all players
4223 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4224 game.centered_player_nr_next = game.centered_player_nr;
4225 game.set_centered_player = FALSE;
4226 game.set_centered_player_wrap = FALSE;
4228 if (network_playing && tape.recording)
4230 // store client dependent player focus when recording network games
4231 tape.centered_player_nr_next = game.centered_player_nr_next;
4232 tape.set_centered_player = TRUE;
4237 // when playing a tape, eliminate all players who do not participate
4239 #if USE_NEW_PLAYER_ASSIGNMENTS
4241 if (!game.team_mode)
4243 for (i = 0; i < MAX_PLAYERS; i++)
4245 if (stored_player[i].active &&
4246 !tape.player_participates[map_player_action[i]])
4248 struct PlayerInfo *player = &stored_player[i];
4249 int jx = player->jx, jy = player->jy;
4251 #if DEBUG_INIT_PLAYER
4252 Debug("game:init:player", "Removing player %d at (%d, %d)",
4256 player->active = FALSE;
4257 StorePlayer[jx][jy] = 0;
4258 Tile[jx][jy] = EL_EMPTY;
4265 for (i = 0; i < MAX_PLAYERS; i++)
4267 if (stored_player[i].active &&
4268 !tape.player_participates[i])
4270 struct PlayerInfo *player = &stored_player[i];
4271 int jx = player->jx, jy = player->jy;
4273 player->active = FALSE;
4274 StorePlayer[jx][jy] = 0;
4275 Tile[jx][jy] = EL_EMPTY;
4280 else if (!network.enabled && !game.team_mode) // && !tape.playing
4282 // when in single player mode, eliminate all but the local player
4284 for (i = 0; i < MAX_PLAYERS; i++)
4286 struct PlayerInfo *player = &stored_player[i];
4288 if (player->active && player != local_player)
4290 int jx = player->jx, jy = player->jy;
4292 player->active = FALSE;
4293 player->present = FALSE;
4295 StorePlayer[jx][jy] = 0;
4296 Tile[jx][jy] = EL_EMPTY;
4301 for (i = 0; i < MAX_PLAYERS; i++)
4302 if (stored_player[i].active)
4303 game.players_still_needed++;
4305 if (level.solved_by_one_player)
4306 game.players_still_needed = 1;
4308 // when recording the game, store which players take part in the game
4311 #if USE_NEW_PLAYER_ASSIGNMENTS
4312 for (i = 0; i < MAX_PLAYERS; i++)
4313 if (stored_player[i].connected)
4314 tape.player_participates[i] = TRUE;
4316 for (i = 0; i < MAX_PLAYERS; i++)
4317 if (stored_player[i].active)
4318 tape.player_participates[i] = TRUE;
4322 #if DEBUG_INIT_PLAYER
4323 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4326 if (BorderElement == EL_EMPTY)
4329 SBX_Right = lev_fieldx - SCR_FIELDX;
4331 SBY_Lower = lev_fieldy - SCR_FIELDY;
4336 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4338 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4341 if (full_lev_fieldx <= SCR_FIELDX)
4342 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4343 if (full_lev_fieldy <= SCR_FIELDY)
4344 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4346 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4348 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4351 // if local player not found, look for custom element that might create
4352 // the player (make some assumptions about the right custom element)
4353 if (!local_player->present)
4355 int start_x = 0, start_y = 0;
4356 int found_rating = 0;
4357 int found_element = EL_UNDEFINED;
4358 int player_nr = local_player->index_nr;
4360 SCAN_PLAYFIELD(x, y)
4362 int element = Tile[x][y];
4367 if (level.use_start_element[player_nr] &&
4368 level.start_element[player_nr] == element &&
4375 found_element = element;
4378 if (!IS_CUSTOM_ELEMENT(element))
4381 if (CAN_CHANGE(element))
4383 for (i = 0; i < element_info[element].num_change_pages; i++)
4385 // check for player created from custom element as single target
4386 content = element_info[element].change_page[i].target_element;
4387 is_player = IS_PLAYER_ELEMENT(content);
4389 if (is_player && (found_rating < 3 ||
4390 (found_rating == 3 && element < found_element)))
4396 found_element = element;
4401 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4403 // check for player created from custom element as explosion content
4404 content = element_info[element].content.e[xx][yy];
4405 is_player = IS_PLAYER_ELEMENT(content);
4407 if (is_player && (found_rating < 2 ||
4408 (found_rating == 2 && element < found_element)))
4410 start_x = x + xx - 1;
4411 start_y = y + yy - 1;
4414 found_element = element;
4417 if (!CAN_CHANGE(element))
4420 for (i = 0; i < element_info[element].num_change_pages; i++)
4422 // check for player created from custom element as extended target
4424 element_info[element].change_page[i].target_content.e[xx][yy];
4426 is_player = IS_PLAYER_ELEMENT(content);
4428 if (is_player && (found_rating < 1 ||
4429 (found_rating == 1 && element < found_element)))
4431 start_x = x + xx - 1;
4432 start_y = y + yy - 1;
4435 found_element = element;
4441 scroll_x = SCROLL_POSITION_X(start_x);
4442 scroll_y = SCROLL_POSITION_Y(start_y);
4446 scroll_x = SCROLL_POSITION_X(local_player->jx);
4447 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4450 if (game.forced_scroll_x != ARG_UNDEFINED_VALUE)
4451 scroll_x = game.forced_scroll_x;
4452 if (game.forced_scroll_y != ARG_UNDEFINED_VALUE)
4453 scroll_y = game.forced_scroll_y;
4455 // !!! FIX THIS (START) !!!
4456 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4458 InitGameEngine_EM();
4460 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4462 InitGameEngine_SP();
4464 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4466 InitGameEngine_MM();
4470 DrawLevel(REDRAW_FIELD);
4473 // after drawing the level, correct some elements
4474 if (game.timegate_time_left == 0)
4475 CloseAllOpenTimegates();
4478 // blit playfield from scroll buffer to normal back buffer for fading in
4479 BlitScreenToBitmap(backbuffer);
4480 // !!! FIX THIS (END) !!!
4482 DrawMaskedBorder(fade_mask);
4487 // full screen redraw is required at this point in the following cases:
4488 // - special editor door undrawn when game was started from level editor
4489 // - drawing area (playfield) was changed and has to be removed completely
4490 redraw_mask = REDRAW_ALL;
4494 if (!game.restart_level)
4496 // copy default game door content to main double buffer
4498 // !!! CHECK AGAIN !!!
4499 SetPanelBackground();
4500 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4501 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4504 SetPanelBackground();
4505 SetDrawBackgroundMask(REDRAW_DOOR_1);
4507 UpdateAndDisplayGameControlValues();
4509 if (!game.restart_level)
4515 CreateGameButtons();
4520 // copy actual game door content to door double buffer for OpenDoor()
4521 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4523 OpenDoor(DOOR_OPEN_ALL);
4525 KeyboardAutoRepeatOffUnlessAutoplay();
4527 #if DEBUG_INIT_PLAYER
4528 DebugPrintPlayerStatus("Player status (final)");
4537 if (!game.restart_level && !tape.playing)
4539 LevelStats_incPlayed(level_nr);
4541 SaveLevelSetup_SeriesInfo();
4544 game.restart_level = FALSE;
4546 game.request_active = FALSE;
4547 game.request_active_or_moving = FALSE;
4549 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4550 InitGameActions_MM();
4552 SaveEngineSnapshotToListInitial();
4554 if (!game.restart_level)
4556 PlaySound(SND_GAME_STARTING);
4558 if (setup.sound_music)
4562 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4565 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4566 int actual_player_x, int actual_player_y)
4568 // this is used for non-R'n'D game engines to update certain engine values
4570 // needed to determine if sounds are played within the visible screen area
4571 scroll_x = actual_scroll_x;
4572 scroll_y = actual_scroll_y;
4574 // needed to get player position for "follow finger" playing input method
4575 local_player->jx = actual_player_x;
4576 local_player->jy = actual_player_y;
4579 void InitMovDir(int x, int y)
4581 int i, element = Tile[x][y];
4582 static int xy[4][2] =
4589 static int direction[3][4] =
4591 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4592 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4593 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4602 Tile[x][y] = EL_BUG;
4603 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4606 case EL_SPACESHIP_RIGHT:
4607 case EL_SPACESHIP_UP:
4608 case EL_SPACESHIP_LEFT:
4609 case EL_SPACESHIP_DOWN:
4610 Tile[x][y] = EL_SPACESHIP;
4611 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4614 case EL_BD_BUTTERFLY_RIGHT:
4615 case EL_BD_BUTTERFLY_UP:
4616 case EL_BD_BUTTERFLY_LEFT:
4617 case EL_BD_BUTTERFLY_DOWN:
4618 Tile[x][y] = EL_BD_BUTTERFLY;
4619 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4622 case EL_BD_FIREFLY_RIGHT:
4623 case EL_BD_FIREFLY_UP:
4624 case EL_BD_FIREFLY_LEFT:
4625 case EL_BD_FIREFLY_DOWN:
4626 Tile[x][y] = EL_BD_FIREFLY;
4627 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4630 case EL_PACMAN_RIGHT:
4632 case EL_PACMAN_LEFT:
4633 case EL_PACMAN_DOWN:
4634 Tile[x][y] = EL_PACMAN;
4635 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4638 case EL_YAMYAM_LEFT:
4639 case EL_YAMYAM_RIGHT:
4641 case EL_YAMYAM_DOWN:
4642 Tile[x][y] = EL_YAMYAM;
4643 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4646 case EL_SP_SNIKSNAK:
4647 MovDir[x][y] = MV_UP;
4650 case EL_SP_ELECTRON:
4651 MovDir[x][y] = MV_LEFT;
4658 Tile[x][y] = EL_MOLE;
4659 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4662 case EL_SPRING_LEFT:
4663 case EL_SPRING_RIGHT:
4664 Tile[x][y] = EL_SPRING;
4665 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4669 if (IS_CUSTOM_ELEMENT(element))
4671 struct ElementInfo *ei = &element_info[element];
4672 int move_direction_initial = ei->move_direction_initial;
4673 int move_pattern = ei->move_pattern;
4675 if (move_direction_initial == MV_START_PREVIOUS)
4677 if (MovDir[x][y] != MV_NONE)
4680 move_direction_initial = MV_START_AUTOMATIC;
4683 if (move_direction_initial == MV_START_RANDOM)
4684 MovDir[x][y] = 1 << RND(4);
4685 else if (move_direction_initial & MV_ANY_DIRECTION)
4686 MovDir[x][y] = move_direction_initial;
4687 else if (move_pattern == MV_ALL_DIRECTIONS ||
4688 move_pattern == MV_TURNING_LEFT ||
4689 move_pattern == MV_TURNING_RIGHT ||
4690 move_pattern == MV_TURNING_LEFT_RIGHT ||
4691 move_pattern == MV_TURNING_RIGHT_LEFT ||
4692 move_pattern == MV_TURNING_RANDOM)
4693 MovDir[x][y] = 1 << RND(4);
4694 else if (move_pattern == MV_HORIZONTAL)
4695 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4696 else if (move_pattern == MV_VERTICAL)
4697 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4698 else if (move_pattern & MV_ANY_DIRECTION)
4699 MovDir[x][y] = element_info[element].move_pattern;
4700 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4701 move_pattern == MV_ALONG_RIGHT_SIDE)
4703 // use random direction as default start direction
4704 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4705 MovDir[x][y] = 1 << RND(4);
4707 for (i = 0; i < NUM_DIRECTIONS; i++)
4709 int x1 = x + xy[i][0];
4710 int y1 = y + xy[i][1];
4712 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4714 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4715 MovDir[x][y] = direction[0][i];
4717 MovDir[x][y] = direction[1][i];
4726 MovDir[x][y] = 1 << RND(4);
4728 if (element != EL_BUG &&
4729 element != EL_SPACESHIP &&
4730 element != EL_BD_BUTTERFLY &&
4731 element != EL_BD_FIREFLY)
4734 for (i = 0; i < NUM_DIRECTIONS; i++)
4736 int x1 = x + xy[i][0];
4737 int y1 = y + xy[i][1];
4739 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4741 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4743 MovDir[x][y] = direction[0][i];
4746 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4747 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4749 MovDir[x][y] = direction[1][i];
4758 GfxDir[x][y] = MovDir[x][y];
4761 void InitAmoebaNr(int x, int y)
4764 int group_nr = AmoebaNeighbourNr(x, y);
4768 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4770 if (AmoebaCnt[i] == 0)
4778 AmoebaNr[x][y] = group_nr;
4779 AmoebaCnt[group_nr]++;
4780 AmoebaCnt2[group_nr]++;
4783 static void LevelSolved_SetFinalGameValues(void)
4785 game.time_final = (game.no_level_time_limit ? TimePlayed : TimeLeft);
4786 game.score_time_final = (level.use_step_counter ? TimePlayed :
4787 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4789 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4790 game_em.lev->score :
4791 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4795 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4796 MM_HEALTH(game_mm.laser_overload_value) :
4799 game.LevelSolved_CountingTime = game.time_final;
4800 game.LevelSolved_CountingScore = game.score_final;
4801 game.LevelSolved_CountingHealth = game.health_final;
4804 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4806 game.LevelSolved_CountingTime = time;
4807 game.LevelSolved_CountingScore = score;
4808 game.LevelSolved_CountingHealth = health;
4810 game_panel_controls[GAME_PANEL_TIME].value = time;
4811 game_panel_controls[GAME_PANEL_SCORE].value = score;
4812 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4814 DisplayGameControlValues();
4817 static void LevelSolved(void)
4819 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4820 game.players_still_needed > 0)
4823 game.LevelSolved = TRUE;
4824 game.GameOver = TRUE;
4828 // needed here to display correct panel values while player walks into exit
4829 LevelSolved_SetFinalGameValues();
4834 static int time_count_steps;
4835 static int time, time_final;
4836 static float score, score_final; // needed for time score < 10 for 10 seconds
4837 static int health, health_final;
4838 static int game_over_delay_1 = 0;
4839 static int game_over_delay_2 = 0;
4840 static int game_over_delay_3 = 0;
4841 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4842 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4844 if (!game.LevelSolved_GameWon)
4848 // do not start end game actions before the player stops moving (to exit)
4849 if (local_player->active && local_player->MovPos)
4852 // calculate final game values after player finished walking into exit
4853 LevelSolved_SetFinalGameValues();
4855 game.LevelSolved_GameWon = TRUE;
4856 game.LevelSolved_SaveTape = tape.recording;
4857 game.LevelSolved_SaveScore = !tape.playing;
4861 LevelStats_incSolved(level_nr);
4863 SaveLevelSetup_SeriesInfo();
4866 if (tape.auto_play) // tape might already be stopped here
4867 tape.auto_play_level_solved = TRUE;
4871 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4872 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4873 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4875 time = time_final = game.time_final;
4876 score = score_final = game.score_final;
4877 health = health_final = game.health_final;
4879 // update game panel values before (delayed) counting of score (if any)
4880 LevelSolved_DisplayFinalGameValues(time, score, health);
4882 // if level has time score defined, calculate new final game values
4885 int time_final_max = 999;
4886 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4887 int time_frames = 0;
4888 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4889 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4894 time_frames = time_frames_left;
4896 else if (game.no_level_time_limit && TimePlayed < time_final_max)
4898 time_final = time_final_max;
4899 time_frames = time_frames_final_max - time_frames_played;
4902 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4904 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4906 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4909 score_final += health * time_score;
4912 game.score_final = score_final;
4913 game.health_final = health_final;
4916 // if not counting score after game, immediately update game panel values
4917 if (level_editor_test_game || !setup.count_score_after_game)
4920 score = score_final;
4922 LevelSolved_DisplayFinalGameValues(time, score, health);
4925 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4927 // check if last player has left the level
4928 if (game.exit_x >= 0 &&
4931 int x = game.exit_x;
4932 int y = game.exit_y;
4933 int element = Tile[x][y];
4935 // close exit door after last player
4936 if ((game.all_players_gone &&
4937 (element == EL_EXIT_OPEN ||
4938 element == EL_SP_EXIT_OPEN ||
4939 element == EL_STEEL_EXIT_OPEN)) ||
4940 element == EL_EM_EXIT_OPEN ||
4941 element == EL_EM_STEEL_EXIT_OPEN)
4945 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4946 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4947 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4948 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4949 EL_EM_STEEL_EXIT_CLOSING);
4951 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4954 // player disappears
4955 DrawLevelField(x, y);
4958 for (i = 0; i < MAX_PLAYERS; i++)
4960 struct PlayerInfo *player = &stored_player[i];
4962 if (player->present)
4964 RemovePlayer(player);
4966 // player disappears
4967 DrawLevelField(player->jx, player->jy);
4972 PlaySound(SND_GAME_WINNING);
4975 if (setup.count_score_after_game)
4977 if (time != time_final)
4979 if (game_over_delay_1 > 0)
4981 game_over_delay_1--;
4986 int time_to_go = ABS(time_final - time);
4987 int time_count_dir = (time < time_final ? +1 : -1);
4989 if (time_to_go < time_count_steps)
4990 time_count_steps = 1;
4992 time += time_count_steps * time_count_dir;
4993 score += time_count_steps * time_score;
4995 // set final score to correct rounding differences after counting score
4996 if (time == time_final)
4997 score = score_final;
4999 LevelSolved_DisplayFinalGameValues(time, score, health);
5001 if (time == time_final)
5002 StopSound(SND_GAME_LEVELTIME_BONUS);
5003 else if (setup.sound_loops)
5004 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5006 PlaySound(SND_GAME_LEVELTIME_BONUS);
5011 if (health != health_final)
5013 if (game_over_delay_2 > 0)
5015 game_over_delay_2--;
5020 int health_count_dir = (health < health_final ? +1 : -1);
5022 health += health_count_dir;
5023 score += time_score;
5025 LevelSolved_DisplayFinalGameValues(time, score, health);
5027 if (health == health_final)
5028 StopSound(SND_GAME_LEVELTIME_BONUS);
5029 else if (setup.sound_loops)
5030 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5032 PlaySound(SND_GAME_LEVELTIME_BONUS);
5038 game.panel.active = FALSE;
5040 if (game_over_delay_3 > 0)
5042 game_over_delay_3--;
5052 // used instead of "level_nr" (needed for network games)
5053 int last_level_nr = levelset.level_nr;
5054 boolean tape_saved = FALSE;
5056 game.LevelSolved_GameEnd = TRUE;
5058 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5060 // make sure that request dialog to save tape does not open door again
5061 if (!global.use_envelope_request)
5062 CloseDoor(DOOR_CLOSE_1);
5065 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5067 // set unique basename for score tape (also saved in high score table)
5068 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5071 // if no tape is to be saved, close both doors simultaneously
5072 CloseDoor(DOOR_CLOSE_ALL);
5074 if (level_editor_test_game || score_info_tape_play)
5076 SetGameStatus(GAME_MODE_MAIN);
5083 if (!game.LevelSolved_SaveScore)
5085 SetGameStatus(GAME_MODE_MAIN);
5092 if (level_nr == leveldir_current->handicap_level)
5094 leveldir_current->handicap_level++;
5096 SaveLevelSetup_SeriesInfo();
5099 // save score and score tape before potentially erasing tape below
5100 NewHighScore(last_level_nr, tape_saved);
5102 if (setup.increment_levels &&
5103 level_nr < leveldir_current->last_level &&
5106 level_nr++; // advance to next level
5107 TapeErase(); // start with empty tape
5109 if (setup.auto_play_next_level)
5111 scores.continue_playing = TRUE;
5112 scores.next_level_nr = level_nr;
5114 LoadLevel(level_nr);
5116 SaveLevelSetup_SeriesInfo();
5120 if (scores.last_added >= 0 && setup.show_scores_after_game)
5122 SetGameStatus(GAME_MODE_SCORES);
5124 DrawHallOfFame(last_level_nr);
5126 else if (scores.continue_playing)
5128 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5132 SetGameStatus(GAME_MODE_MAIN);
5138 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5139 boolean one_score_entry_per_name)
5143 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5146 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5148 struct ScoreEntry *entry = &list->entry[i];
5149 boolean score_is_better = (new_entry->score > entry->score);
5150 boolean score_is_equal = (new_entry->score == entry->score);
5151 boolean time_is_better = (new_entry->time < entry->time);
5152 boolean time_is_equal = (new_entry->time == entry->time);
5153 boolean better_by_score = (score_is_better ||
5154 (score_is_equal && time_is_better));
5155 boolean better_by_time = (time_is_better ||
5156 (time_is_equal && score_is_better));
5157 boolean is_better = (level.rate_time_over_score ? better_by_time :
5159 boolean entry_is_empty = (entry->score == 0 &&
5162 // prevent adding server score entries if also existing in local score file
5163 // (special case: historic score entries have an empty tape basename entry)
5164 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5165 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5167 // add fields from server score entry not stored in local score entry
5168 // (currently, this means setting platform, version and country fields;
5169 // in rare cases, this may also correct an invalid score value, as
5170 // historic scores might have been truncated to 16-bit values locally)
5171 *entry = *new_entry;
5176 if (is_better || entry_is_empty)
5178 // player has made it to the hall of fame
5180 if (i < MAX_SCORE_ENTRIES - 1)
5182 int m = MAX_SCORE_ENTRIES - 1;
5185 if (one_score_entry_per_name)
5187 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5188 if (strEqual(list->entry[l].name, new_entry->name))
5191 if (m == i) // player's new highscore overwrites his old one
5195 for (l = m; l > i; l--)
5196 list->entry[l] = list->entry[l - 1];
5201 *entry = *new_entry;
5205 else if (one_score_entry_per_name &&
5206 strEqual(entry->name, new_entry->name))
5208 // player already in high score list with better score or time
5214 // special case: new score is beyond the last high score list position
5215 return MAX_SCORE_ENTRIES;
5218 void NewHighScore(int level_nr, boolean tape_saved)
5220 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5221 boolean one_per_name = FALSE;
5223 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5224 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5226 new_entry.score = game.score_final;
5227 new_entry.time = game.score_time_final;
5229 LoadScore(level_nr);
5231 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5233 if (scores.last_added >= MAX_SCORE_ENTRIES)
5235 scores.last_added = MAX_SCORE_ENTRIES - 1;
5236 scores.force_last_added = TRUE;
5238 scores.entry[scores.last_added] = new_entry;
5240 // store last added local score entry (before merging server scores)
5241 scores.last_added_local = scores.last_added;
5246 if (scores.last_added < 0)
5249 SaveScore(level_nr);
5251 // store last added local score entry (before merging server scores)
5252 scores.last_added_local = scores.last_added;
5254 if (!game.LevelSolved_SaveTape)
5257 SaveScoreTape(level_nr);
5259 if (setup.ask_for_using_api_server)
5261 setup.use_api_server =
5262 Request("Upload your score and tape to the high score server?", REQ_ASK);
5264 if (!setup.use_api_server)
5265 Request("Not using high score server! Use setup menu to enable again!",
5268 runtime.use_api_server = setup.use_api_server;
5270 // after asking for using API server once, do not ask again
5271 setup.ask_for_using_api_server = FALSE;
5273 SaveSetup_ServerSetup();
5276 SaveServerScore(level_nr, tape_saved);
5279 void MergeServerScore(void)
5281 struct ScoreEntry last_added_entry;
5282 boolean one_per_name = FALSE;
5285 if (scores.last_added >= 0)
5286 last_added_entry = scores.entry[scores.last_added];
5288 for (i = 0; i < server_scores.num_entries; i++)
5290 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5292 if (pos >= 0 && pos <= scores.last_added)
5293 scores.last_added++;
5296 if (scores.last_added >= MAX_SCORE_ENTRIES)
5298 scores.last_added = MAX_SCORE_ENTRIES - 1;
5299 scores.force_last_added = TRUE;
5301 scores.entry[scores.last_added] = last_added_entry;
5305 static int getElementMoveStepsizeExt(int x, int y, int direction)
5307 int element = Tile[x][y];
5308 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5309 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5310 int horiz_move = (dx != 0);
5311 int sign = (horiz_move ? dx : dy);
5312 int step = sign * element_info[element].move_stepsize;
5314 // special values for move stepsize for spring and things on conveyor belt
5317 if (CAN_FALL(element) &&
5318 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5319 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5320 else if (element == EL_SPRING)
5321 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5327 static int getElementMoveStepsize(int x, int y)
5329 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5332 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5334 if (player->GfxAction != action || player->GfxDir != dir)
5336 player->GfxAction = action;
5337 player->GfxDir = dir;
5339 player->StepFrame = 0;
5343 static void ResetGfxFrame(int x, int y)
5345 // profiling showed that "autotest" spends 10~20% of its time in this function
5346 if (DrawingDeactivatedField())
5349 int element = Tile[x][y];
5350 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5352 if (graphic_info[graphic].anim_global_sync)
5353 GfxFrame[x][y] = FrameCounter;
5354 else if (graphic_info[graphic].anim_global_anim_sync)
5355 GfxFrame[x][y] = getGlobalAnimSyncFrame();
5356 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5357 GfxFrame[x][y] = CustomValue[x][y];
5358 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5359 GfxFrame[x][y] = element_info[element].collect_score;
5360 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5361 GfxFrame[x][y] = ChangeDelay[x][y];
5364 static void ResetGfxAnimation(int x, int y)
5366 GfxAction[x][y] = ACTION_DEFAULT;
5367 GfxDir[x][y] = MovDir[x][y];
5370 ResetGfxFrame(x, y);
5373 static void ResetRandomAnimationValue(int x, int y)
5375 GfxRandom[x][y] = INIT_GFX_RANDOM();
5378 static void InitMovingField(int x, int y, int direction)
5380 int element = Tile[x][y];
5381 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5382 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5385 boolean is_moving_before, is_moving_after;
5387 // check if element was/is moving or being moved before/after mode change
5388 is_moving_before = (WasJustMoving[x][y] != 0);
5389 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5391 // reset animation only for moving elements which change direction of moving
5392 // or which just started or stopped moving
5393 // (else CEs with property "can move" / "not moving" are reset each frame)
5394 if (is_moving_before != is_moving_after ||
5395 direction != MovDir[x][y])
5396 ResetGfxAnimation(x, y);
5398 MovDir[x][y] = direction;
5399 GfxDir[x][y] = direction;
5401 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5402 direction == MV_DOWN && CAN_FALL(element) ?
5403 ACTION_FALLING : ACTION_MOVING);
5405 // this is needed for CEs with property "can move" / "not moving"
5407 if (is_moving_after)
5409 if (Tile[newx][newy] == EL_EMPTY)
5410 Tile[newx][newy] = EL_BLOCKED;
5412 MovDir[newx][newy] = MovDir[x][y];
5414 CustomValue[newx][newy] = CustomValue[x][y];
5416 GfxFrame[newx][newy] = GfxFrame[x][y];
5417 GfxRandom[newx][newy] = GfxRandom[x][y];
5418 GfxAction[newx][newy] = GfxAction[x][y];
5419 GfxDir[newx][newy] = GfxDir[x][y];
5423 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5425 int direction = MovDir[x][y];
5426 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5427 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5433 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5435 int direction = MovDir[x][y];
5436 int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0);
5437 int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0);
5439 *comes_from_x = oldx;
5440 *comes_from_y = oldy;
5443 static int MovingOrBlocked2Element(int x, int y)
5445 int element = Tile[x][y];
5447 if (element == EL_BLOCKED)
5451 Blocked2Moving(x, y, &oldx, &oldy);
5453 return Tile[oldx][oldy];
5459 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5461 // like MovingOrBlocked2Element(), but if element is moving
5462 // and (x, y) is the field the moving element is just leaving,
5463 // return EL_BLOCKED instead of the element value
5464 int element = Tile[x][y];
5466 if (IS_MOVING(x, y))
5468 if (element == EL_BLOCKED)
5472 Blocked2Moving(x, y, &oldx, &oldy);
5473 return Tile[oldx][oldy];
5482 static void RemoveField(int x, int y)
5484 Tile[x][y] = EL_EMPTY;
5490 CustomValue[x][y] = 0;
5493 ChangeDelay[x][y] = 0;
5494 ChangePage[x][y] = -1;
5495 Pushed[x][y] = FALSE;
5497 GfxElement[x][y] = EL_UNDEFINED;
5498 GfxAction[x][y] = ACTION_DEFAULT;
5499 GfxDir[x][y] = MV_NONE;
5502 static void RemoveMovingField(int x, int y)
5504 int oldx = x, oldy = y, newx = x, newy = y;
5505 int element = Tile[x][y];
5506 int next_element = EL_UNDEFINED;
5508 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5511 if (IS_MOVING(x, y))
5513 Moving2Blocked(x, y, &newx, &newy);
5515 if (Tile[newx][newy] != EL_BLOCKED)
5517 // element is moving, but target field is not free (blocked), but
5518 // already occupied by something different (example: acid pool);
5519 // in this case, only remove the moving field, but not the target
5521 RemoveField(oldx, oldy);
5523 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5525 TEST_DrawLevelField(oldx, oldy);
5530 else if (element == EL_BLOCKED)
5532 Blocked2Moving(x, y, &oldx, &oldy);
5533 if (!IS_MOVING(oldx, oldy))
5537 if (element == EL_BLOCKED &&
5538 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5539 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5540 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5541 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5542 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5543 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5544 next_element = get_next_element(Tile[oldx][oldy]);
5546 RemoveField(oldx, oldy);
5547 RemoveField(newx, newy);
5549 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5551 if (next_element != EL_UNDEFINED)
5552 Tile[oldx][oldy] = next_element;
5554 TEST_DrawLevelField(oldx, oldy);
5555 TEST_DrawLevelField(newx, newy);
5558 void DrawDynamite(int x, int y)
5560 int sx = SCREENX(x), sy = SCREENY(y);
5561 int graphic = el2img(Tile[x][y]);
5564 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5567 if (IS_WALKABLE_INSIDE(Back[x][y]))
5571 DrawLevelElement(x, y, Back[x][y]);
5572 else if (Store[x][y])
5573 DrawLevelElement(x, y, Store[x][y]);
5574 else if (game.use_masked_elements)
5575 DrawLevelElement(x, y, EL_EMPTY);
5577 frame = getGraphicAnimationFrameXY(graphic, x, y);
5579 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5580 DrawGraphicThruMask(sx, sy, graphic, frame);
5582 DrawGraphic(sx, sy, graphic, frame);
5585 static void CheckDynamite(int x, int y)
5587 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5591 if (MovDelay[x][y] != 0)
5594 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5600 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5605 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5607 boolean num_checked_players = 0;
5610 for (i = 0; i < MAX_PLAYERS; i++)
5612 if (stored_player[i].active)
5614 int sx = stored_player[i].jx;
5615 int sy = stored_player[i].jy;
5617 if (num_checked_players == 0)
5624 *sx1 = MIN(*sx1, sx);
5625 *sy1 = MIN(*sy1, sy);
5626 *sx2 = MAX(*sx2, sx);
5627 *sy2 = MAX(*sy2, sy);
5630 num_checked_players++;
5635 static boolean checkIfAllPlayersFitToScreen_RND(void)
5637 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5639 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5641 return (sx2 - sx1 < SCR_FIELDX &&
5642 sy2 - sy1 < SCR_FIELDY);
5645 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5647 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5649 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5651 *sx = (sx1 + sx2) / 2;
5652 *sy = (sy1 + sy2) / 2;
5655 static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
5656 boolean center_screen, boolean quick_relocation)
5658 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5659 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5660 boolean no_delay = (tape.warp_forward);
5661 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5662 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5663 int new_scroll_x, new_scroll_y;
5665 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5667 // case 1: quick relocation inside visible screen (without scrolling)
5674 if (!level.shifted_relocation || center_screen)
5676 // relocation _with_ centering of screen
5678 new_scroll_x = SCROLL_POSITION_X(x);
5679 new_scroll_y = SCROLL_POSITION_Y(y);
5683 // relocation _without_ centering of screen
5685 // apply distance between old and new player position to scroll position
5686 int shifted_scroll_x = scroll_x + (x - old_x);
5687 int shifted_scroll_y = scroll_y + (y - old_y);
5689 // make sure that shifted scroll position does not scroll beyond screen
5690 new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
5691 new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
5693 // special case for teleporting from one end of the playfield to the other
5694 // (this kludge prevents the destination area to be shifted by half a tile
5695 // against the source destination for even screen width or screen height;
5696 // probably most useful when used with high "game.forced_scroll_delay_value"
5697 // in combination with "game.forced_scroll_x" and "game.forced_scroll_y")
5698 if (quick_relocation)
5700 if (EVEN(SCR_FIELDX))
5702 // relocate (teleport) between left and right border (half or full)
5703 if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1)
5704 new_scroll_x = SBX_Right;
5705 else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right)
5706 new_scroll_x = SBX_Right - 1;
5707 else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1)
5708 new_scroll_x = SBX_Left;
5709 else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left)
5710 new_scroll_x = SBX_Left + 1;
5713 if (EVEN(SCR_FIELDY))
5715 // relocate (teleport) between top and bottom border (half or full)
5716 if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1)
5717 new_scroll_y = SBY_Lower;
5718 else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower)
5719 new_scroll_y = SBY_Lower - 1;
5720 else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1)
5721 new_scroll_y = SBY_Upper;
5722 else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper)
5723 new_scroll_y = SBY_Upper + 1;
5728 if (quick_relocation)
5730 // case 2: quick relocation (redraw without visible scrolling)
5732 scroll_x = new_scroll_x;
5733 scroll_y = new_scroll_y;
5740 // case 3: visible relocation (with scrolling to new position)
5742 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5744 SetVideoFrameDelay(wait_delay_value);
5746 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5748 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5749 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5751 if (dx == 0 && dy == 0) // no scrolling needed at all
5757 // set values for horizontal/vertical screen scrolling (half tile size)
5758 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5759 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5760 int pos_x = dx * TILEX / 2;
5761 int pos_y = dy * TILEY / 2;
5762 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5763 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5765 ScrollLevel(dx, dy);
5768 // scroll in two steps of half tile size to make things smoother
5769 BlitScreenToBitmapExt_RND(window, fx, fy);
5771 // scroll second step to align at full tile size
5772 BlitScreenToBitmap(window);
5778 SetVideoFrameDelay(frame_delay_value_old);
5781 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5783 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5784 int player_nr = GET_PLAYER_NR(el_player);
5785 struct PlayerInfo *player = &stored_player[player_nr];
5786 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5787 boolean no_delay = (tape.warp_forward);
5788 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5789 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5790 int old_jx = player->jx;
5791 int old_jy = player->jy;
5792 int old_element = Tile[old_jx][old_jy];
5793 int element = Tile[jx][jy];
5794 boolean player_relocated = (old_jx != jx || old_jy != jy);
5796 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5797 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5798 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5799 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5800 int leave_side_horiz = move_dir_horiz;
5801 int leave_side_vert = move_dir_vert;
5802 int enter_side = enter_side_horiz | enter_side_vert;
5803 int leave_side = leave_side_horiz | leave_side_vert;
5805 if (player->buried) // do not reanimate dead player
5808 if (!player_relocated) // no need to relocate the player
5811 if (IS_PLAYER(jx, jy)) // player already placed at new position
5813 RemoveField(jx, jy); // temporarily remove newly placed player
5814 DrawLevelField(jx, jy);
5817 if (player->present)
5819 while (player->MovPos)
5821 ScrollPlayer(player, SCROLL_GO_ON);
5822 ScrollScreen(NULL, SCROLL_GO_ON);
5824 AdvanceFrameAndPlayerCounters(player->index_nr);
5828 BackToFront_WithFrameDelay(wait_delay_value);
5831 DrawPlayer(player); // needed here only to cleanup last field
5832 DrawLevelField(player->jx, player->jy); // remove player graphic
5834 player->is_moving = FALSE;
5837 if (IS_CUSTOM_ELEMENT(old_element))
5838 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5840 player->index_bit, leave_side);
5842 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5844 player->index_bit, leave_side);
5846 Tile[jx][jy] = el_player;
5847 InitPlayerField(jx, jy, el_player, TRUE);
5849 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5850 possible that the relocation target field did not contain a player element,
5851 but a walkable element, to which the new player was relocated -- in this
5852 case, restore that (already initialized!) element on the player field */
5853 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5855 Tile[jx][jy] = element; // restore previously existing element
5858 // only visually relocate centered player
5859 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy,
5860 FALSE, level.instant_relocation);
5862 TestIfPlayerTouchesBadThing(jx, jy);
5863 TestIfPlayerTouchesCustomElement(jx, jy);
5865 if (IS_CUSTOM_ELEMENT(element))
5866 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5867 player->index_bit, enter_side);
5869 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5870 player->index_bit, enter_side);
5872 if (player->is_switching)
5874 /* ensure that relocation while still switching an element does not cause
5875 a new element to be treated as also switched directly after relocation
5876 (this is important for teleporter switches that teleport the player to
5877 a place where another teleporter switch is in the same direction, which
5878 would then incorrectly be treated as immediately switched before the
5879 direction key that caused the switch was released) */
5881 player->switch_x += jx - old_jx;
5882 player->switch_y += jy - old_jy;
5886 static void Explode(int ex, int ey, int phase, int mode)
5892 if (game.explosions_delayed)
5894 ExplodeField[ex][ey] = mode;
5898 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5900 int center_element = Tile[ex][ey];
5901 int ce_value = CustomValue[ex][ey];
5902 int ce_score = element_info[center_element].collect_score;
5903 int artwork_element, explosion_element; // set these values later
5905 // remove things displayed in background while burning dynamite
5906 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5909 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5911 // put moving element to center field (and let it explode there)
5912 center_element = MovingOrBlocked2Element(ex, ey);
5913 RemoveMovingField(ex, ey);
5914 Tile[ex][ey] = center_element;
5917 // now "center_element" is finally determined -- set related values now
5918 artwork_element = center_element; // for custom player artwork
5919 explosion_element = center_element; // for custom player artwork
5921 if (IS_PLAYER(ex, ey))
5923 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5925 artwork_element = stored_player[player_nr].artwork_element;
5927 if (level.use_explosion_element[player_nr])
5929 explosion_element = level.explosion_element[player_nr];
5930 artwork_element = explosion_element;
5934 if (mode == EX_TYPE_NORMAL ||
5935 mode == EX_TYPE_CENTER ||
5936 mode == EX_TYPE_CROSS)
5937 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5939 last_phase = element_info[explosion_element].explosion_delay + 1;
5941 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5943 int xx = x - ex + 1;
5944 int yy = y - ey + 1;
5947 if (!IN_LEV_FIELD(x, y) ||
5948 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5949 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5952 element = Tile[x][y];
5954 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5956 element = MovingOrBlocked2Element(x, y);
5958 if (!IS_EXPLOSION_PROOF(element))
5959 RemoveMovingField(x, y);
5962 // indestructible elements can only explode in center (but not flames)
5963 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5964 mode == EX_TYPE_BORDER)) ||
5965 element == EL_FLAMES)
5968 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5969 behaviour, for example when touching a yamyam that explodes to rocks
5970 with active deadly shield, a rock is created under the player !!! */
5971 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5973 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5974 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5975 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5977 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5980 if (IS_ACTIVE_BOMB(element))
5982 // re-activate things under the bomb like gate or penguin
5983 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5990 // save walkable background elements while explosion on same tile
5991 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5992 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5993 Back[x][y] = element;
5995 // ignite explodable elements reached by other explosion
5996 if (element == EL_EXPLOSION)
5997 element = Store2[x][y];
5999 if (AmoebaNr[x][y] &&
6000 (element == EL_AMOEBA_FULL ||
6001 element == EL_BD_AMOEBA ||
6002 element == EL_AMOEBA_GROWING))
6004 AmoebaCnt[AmoebaNr[x][y]]--;
6005 AmoebaCnt2[AmoebaNr[x][y]]--;
6010 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
6012 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
6014 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
6016 if (PLAYERINFO(ex, ey)->use_murphy)
6017 Store[x][y] = EL_EMPTY;
6020 // !!! check this case -- currently needed for rnd_rado_negundo_v,
6021 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
6022 else if (IS_PLAYER_ELEMENT(center_element))
6023 Store[x][y] = EL_EMPTY;
6024 else if (center_element == EL_YAMYAM)
6025 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
6026 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
6027 Store[x][y] = element_info[center_element].content.e[xx][yy];
6029 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
6030 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
6031 // otherwise) -- FIX THIS !!!
6032 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
6033 Store[x][y] = element_info[element].content.e[1][1];
6035 else if (!CAN_EXPLODE(element))
6036 Store[x][y] = element_info[element].content.e[1][1];
6039 Store[x][y] = EL_EMPTY;
6041 if (IS_CUSTOM_ELEMENT(center_element))
6042 Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value :
6043 Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score :
6044 Store[x][y] >= EL_PREV_CE_8 &&
6045 Store[x][y] <= EL_NEXT_CE_8 ?
6046 RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) :
6049 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
6050 center_element == EL_AMOEBA_TO_DIAMOND)
6051 Store2[x][y] = element;
6053 Tile[x][y] = EL_EXPLOSION;
6054 GfxElement[x][y] = artwork_element;
6056 ExplodePhase[x][y] = 1;
6057 ExplodeDelay[x][y] = last_phase;
6062 if (center_element == EL_YAMYAM)
6063 game.yamyam_content_nr =
6064 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
6076 GfxFrame[x][y] = 0; // restart explosion animation
6078 last_phase = ExplodeDelay[x][y];
6080 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
6082 // this can happen if the player leaves an explosion just in time
6083 if (GfxElement[x][y] == EL_UNDEFINED)
6084 GfxElement[x][y] = EL_EMPTY;
6086 border_element = Store2[x][y];
6087 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6088 border_element = StorePlayer[x][y];
6090 if (phase == element_info[border_element].ignition_delay ||
6091 phase == last_phase)
6093 boolean border_explosion = FALSE;
6095 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6096 !PLAYER_EXPLOSION_PROTECTED(x, y))
6098 KillPlayerUnlessExplosionProtected(x, y);
6099 border_explosion = TRUE;
6101 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6103 Tile[x][y] = Store2[x][y];
6106 border_explosion = TRUE;
6108 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6110 AmoebaToDiamond(x, y);
6112 border_explosion = TRUE;
6115 // if an element just explodes due to another explosion (chain-reaction),
6116 // do not immediately end the new explosion when it was the last frame of
6117 // the explosion (as it would be done in the following "if"-statement!)
6118 if (border_explosion && phase == last_phase)
6122 // this can happen if the player was just killed by an explosion
6123 if (GfxElement[x][y] == EL_UNDEFINED)
6124 GfxElement[x][y] = EL_EMPTY;
6126 if (phase == last_phase)
6130 element = Tile[x][y] = Store[x][y];
6131 Store[x][y] = Store2[x][y] = 0;
6132 GfxElement[x][y] = EL_UNDEFINED;
6134 // player can escape from explosions and might therefore be still alive
6135 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6136 element <= EL_PLAYER_IS_EXPLODING_4)
6138 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6139 int explosion_element = EL_PLAYER_1 + player_nr;
6140 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6141 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6143 if (level.use_explosion_element[player_nr])
6144 explosion_element = level.explosion_element[player_nr];
6146 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6147 element_info[explosion_element].content.e[xx][yy]);
6150 // restore probably existing indestructible background element
6151 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6152 element = Tile[x][y] = Back[x][y];
6155 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6156 GfxDir[x][y] = MV_NONE;
6157 ChangeDelay[x][y] = 0;
6158 ChangePage[x][y] = -1;
6160 CustomValue[x][y] = 0;
6162 InitField_WithBug2(x, y, FALSE);
6164 TEST_DrawLevelField(x, y);
6166 TestIfElementTouchesCustomElement(x, y);
6168 if (GFX_CRUMBLED(element))
6169 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6171 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6172 StorePlayer[x][y] = 0;
6174 if (IS_PLAYER_ELEMENT(element))
6175 RelocatePlayer(x, y, element);
6177 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6179 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6180 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6183 TEST_DrawLevelFieldCrumbled(x, y);
6185 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6187 DrawLevelElement(x, y, Back[x][y]);
6188 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6190 else if (IS_WALKABLE_UNDER(Back[x][y]))
6192 DrawLevelGraphic(x, y, graphic, frame);
6193 DrawLevelElementThruMask(x, y, Back[x][y]);
6195 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6196 DrawLevelGraphic(x, y, graphic, frame);
6200 static void DynaExplode(int ex, int ey)
6203 int dynabomb_element = Tile[ex][ey];
6204 int dynabomb_size = 1;
6205 boolean dynabomb_xl = FALSE;
6206 struct PlayerInfo *player;
6207 struct XY *xy = xy_topdown;
6209 if (IS_ACTIVE_BOMB(dynabomb_element))
6211 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6212 dynabomb_size = player->dynabomb_size;
6213 dynabomb_xl = player->dynabomb_xl;
6214 player->dynabombs_left++;
6217 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6219 for (i = 0; i < NUM_DIRECTIONS; i++)
6221 for (j = 1; j <= dynabomb_size; j++)
6223 int x = ex + j * xy[i].x;
6224 int y = ey + j * xy[i].y;
6227 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6230 element = Tile[x][y];
6232 // do not restart explosions of fields with active bombs
6233 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6236 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6238 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6239 !IS_DIGGABLE(element) && !dynabomb_xl)
6245 void Bang(int x, int y)
6247 int element = MovingOrBlocked2Element(x, y);
6248 int explosion_type = EX_TYPE_NORMAL;
6250 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6252 struct PlayerInfo *player = PLAYERINFO(x, y);
6254 element = Tile[x][y] = player->initial_element;
6256 if (level.use_explosion_element[player->index_nr])
6258 int explosion_element = level.explosion_element[player->index_nr];
6260 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6261 explosion_type = EX_TYPE_CROSS;
6262 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6263 explosion_type = EX_TYPE_CENTER;
6271 case EL_BD_BUTTERFLY:
6274 case EL_DARK_YAMYAM:
6278 RaiseScoreElement(element);
6281 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6282 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6283 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6284 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6285 case EL_DYNABOMB_INCREASE_NUMBER:
6286 case EL_DYNABOMB_INCREASE_SIZE:
6287 case EL_DYNABOMB_INCREASE_POWER:
6288 explosion_type = EX_TYPE_DYNA;
6291 case EL_DC_LANDMINE:
6292 explosion_type = EX_TYPE_CENTER;
6297 case EL_LAMP_ACTIVE:
6298 case EL_AMOEBA_TO_DIAMOND:
6299 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6300 explosion_type = EX_TYPE_CENTER;
6304 if (element_info[element].explosion_type == EXPLODES_CROSS)
6305 explosion_type = EX_TYPE_CROSS;
6306 else if (element_info[element].explosion_type == EXPLODES_1X1)
6307 explosion_type = EX_TYPE_CENTER;
6311 if (explosion_type == EX_TYPE_DYNA)
6314 Explode(x, y, EX_PHASE_START, explosion_type);
6316 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6319 static void SplashAcid(int x, int y)
6321 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6322 (!IN_LEV_FIELD(x - 1, y - 2) ||
6323 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6324 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6326 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6327 (!IN_LEV_FIELD(x + 1, y - 2) ||
6328 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6329 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6331 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6334 static void InitBeltMovement(void)
6336 static int belt_base_element[4] =
6338 EL_CONVEYOR_BELT_1_LEFT,
6339 EL_CONVEYOR_BELT_2_LEFT,
6340 EL_CONVEYOR_BELT_3_LEFT,
6341 EL_CONVEYOR_BELT_4_LEFT
6343 static int belt_base_active_element[4] =
6345 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6346 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6347 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6348 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6353 // set frame order for belt animation graphic according to belt direction
6354 for (i = 0; i < NUM_BELTS; i++)
6358 for (j = 0; j < NUM_BELT_PARTS; j++)
6360 int element = belt_base_active_element[belt_nr] + j;
6361 int graphic_1 = el2img(element);
6362 int graphic_2 = el2panelimg(element);
6364 if (game.belt_dir[i] == MV_LEFT)
6366 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6367 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6371 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6372 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6377 SCAN_PLAYFIELD(x, y)
6379 int element = Tile[x][y];
6381 for (i = 0; i < NUM_BELTS; i++)
6383 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6385 int e_belt_nr = getBeltNrFromBeltElement(element);
6388 if (e_belt_nr == belt_nr)
6390 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6392 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6399 static void ToggleBeltSwitch(int x, int y)
6401 static int belt_base_element[4] =
6403 EL_CONVEYOR_BELT_1_LEFT,
6404 EL_CONVEYOR_BELT_2_LEFT,
6405 EL_CONVEYOR_BELT_3_LEFT,
6406 EL_CONVEYOR_BELT_4_LEFT
6408 static int belt_base_active_element[4] =
6410 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6411 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6412 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6413 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6415 static int belt_base_switch_element[4] =
6417 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6418 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6419 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6420 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6422 static int belt_move_dir[4] =
6430 int element = Tile[x][y];
6431 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6432 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6433 int belt_dir = belt_move_dir[belt_dir_nr];
6436 if (!IS_BELT_SWITCH(element))
6439 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6440 game.belt_dir[belt_nr] = belt_dir;
6442 if (belt_dir_nr == 3)
6445 // set frame order for belt animation graphic according to belt direction
6446 for (i = 0; i < NUM_BELT_PARTS; i++)
6448 int element = belt_base_active_element[belt_nr] + i;
6449 int graphic_1 = el2img(element);
6450 int graphic_2 = el2panelimg(element);
6452 if (belt_dir == MV_LEFT)
6454 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6455 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6459 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6460 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6464 SCAN_PLAYFIELD(xx, yy)
6466 int element = Tile[xx][yy];
6468 if (IS_BELT_SWITCH(element))
6470 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6472 if (e_belt_nr == belt_nr)
6474 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6475 TEST_DrawLevelField(xx, yy);
6478 else if (IS_BELT(element) && belt_dir != MV_NONE)
6480 int e_belt_nr = getBeltNrFromBeltElement(element);
6482 if (e_belt_nr == belt_nr)
6484 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6486 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6487 TEST_DrawLevelField(xx, yy);
6490 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6492 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6494 if (e_belt_nr == belt_nr)
6496 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6498 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6499 TEST_DrawLevelField(xx, yy);
6505 static void ToggleSwitchgateSwitch(void)
6509 game.switchgate_pos = !game.switchgate_pos;
6511 SCAN_PLAYFIELD(xx, yy)
6513 int element = Tile[xx][yy];
6515 if (element == EL_SWITCHGATE_SWITCH_UP)
6517 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6518 TEST_DrawLevelField(xx, yy);
6520 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6522 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6523 TEST_DrawLevelField(xx, yy);
6525 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6527 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6528 TEST_DrawLevelField(xx, yy);
6530 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6532 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6533 TEST_DrawLevelField(xx, yy);
6535 else if (element == EL_SWITCHGATE_OPEN ||
6536 element == EL_SWITCHGATE_OPENING)
6538 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6540 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6542 else if (element == EL_SWITCHGATE_CLOSED ||
6543 element == EL_SWITCHGATE_CLOSING)
6545 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6547 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6552 static int getInvisibleActiveFromInvisibleElement(int element)
6554 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6555 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6556 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6560 static int getInvisibleFromInvisibleActiveElement(int element)
6562 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6563 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6564 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6568 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6572 SCAN_PLAYFIELD(x, y)
6574 int element = Tile[x][y];
6576 if (element == EL_LIGHT_SWITCH &&
6577 game.light_time_left > 0)
6579 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6580 TEST_DrawLevelField(x, y);
6582 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6583 game.light_time_left == 0)
6585 Tile[x][y] = EL_LIGHT_SWITCH;
6586 TEST_DrawLevelField(x, y);
6588 else if (element == EL_EMC_DRIPPER &&
6589 game.light_time_left > 0)
6591 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6592 TEST_DrawLevelField(x, y);
6594 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6595 game.light_time_left == 0)
6597 Tile[x][y] = EL_EMC_DRIPPER;
6598 TEST_DrawLevelField(x, y);
6600 else if (element == EL_INVISIBLE_STEELWALL ||
6601 element == EL_INVISIBLE_WALL ||
6602 element == EL_INVISIBLE_SAND)
6604 if (game.light_time_left > 0)
6605 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6607 TEST_DrawLevelField(x, y);
6609 // uncrumble neighbour fields, if needed
6610 if (element == EL_INVISIBLE_SAND)
6611 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6613 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6614 element == EL_INVISIBLE_WALL_ACTIVE ||
6615 element == EL_INVISIBLE_SAND_ACTIVE)
6617 if (game.light_time_left == 0)
6618 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6620 TEST_DrawLevelField(x, y);
6622 // re-crumble neighbour fields, if needed
6623 if (element == EL_INVISIBLE_SAND)
6624 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6629 static void RedrawAllInvisibleElementsForLenses(void)
6633 SCAN_PLAYFIELD(x, y)
6635 int element = Tile[x][y];
6637 if (element == EL_EMC_DRIPPER &&
6638 game.lenses_time_left > 0)
6640 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6641 TEST_DrawLevelField(x, y);
6643 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6644 game.lenses_time_left == 0)
6646 Tile[x][y] = EL_EMC_DRIPPER;
6647 TEST_DrawLevelField(x, y);
6649 else if (element == EL_INVISIBLE_STEELWALL ||
6650 element == EL_INVISIBLE_WALL ||
6651 element == EL_INVISIBLE_SAND)
6653 if (game.lenses_time_left > 0)
6654 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6656 TEST_DrawLevelField(x, y);
6658 // uncrumble neighbour fields, if needed
6659 if (element == EL_INVISIBLE_SAND)
6660 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6662 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6663 element == EL_INVISIBLE_WALL_ACTIVE ||
6664 element == EL_INVISIBLE_SAND_ACTIVE)
6666 if (game.lenses_time_left == 0)
6667 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6669 TEST_DrawLevelField(x, y);
6671 // re-crumble neighbour fields, if needed
6672 if (element == EL_INVISIBLE_SAND)
6673 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6678 static void RedrawAllInvisibleElementsForMagnifier(void)
6682 SCAN_PLAYFIELD(x, y)
6684 int element = Tile[x][y];
6686 if (element == EL_EMC_FAKE_GRASS &&
6687 game.magnify_time_left > 0)
6689 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6690 TEST_DrawLevelField(x, y);
6692 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6693 game.magnify_time_left == 0)
6695 Tile[x][y] = EL_EMC_FAKE_GRASS;
6696 TEST_DrawLevelField(x, y);
6698 else if (IS_GATE_GRAY(element) &&
6699 game.magnify_time_left > 0)
6701 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6702 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6703 IS_EM_GATE_GRAY(element) ?
6704 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6705 IS_EMC_GATE_GRAY(element) ?
6706 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6707 IS_DC_GATE_GRAY(element) ?
6708 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6710 TEST_DrawLevelField(x, y);
6712 else if (IS_GATE_GRAY_ACTIVE(element) &&
6713 game.magnify_time_left == 0)
6715 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6716 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6717 IS_EM_GATE_GRAY_ACTIVE(element) ?
6718 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6719 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6720 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6721 IS_DC_GATE_GRAY_ACTIVE(element) ?
6722 EL_DC_GATE_WHITE_GRAY :
6724 TEST_DrawLevelField(x, y);
6729 static void ToggleLightSwitch(int x, int y)
6731 int element = Tile[x][y];
6733 game.light_time_left =
6734 (element == EL_LIGHT_SWITCH ?
6735 level.time_light * FRAMES_PER_SECOND : 0);
6737 RedrawAllLightSwitchesAndInvisibleElements();
6740 static void ActivateTimegateSwitch(int x, int y)
6744 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6746 SCAN_PLAYFIELD(xx, yy)
6748 int element = Tile[xx][yy];
6750 if (element == EL_TIMEGATE_CLOSED ||
6751 element == EL_TIMEGATE_CLOSING)
6753 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6754 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6758 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6760 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6761 TEST_DrawLevelField(xx, yy);
6767 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6768 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6771 static void Impact(int x, int y)
6773 boolean last_line = (y == lev_fieldy - 1);
6774 boolean object_hit = FALSE;
6775 boolean impact = (last_line || object_hit);
6776 int element = Tile[x][y];
6777 int smashed = EL_STEELWALL;
6779 if (!last_line) // check if element below was hit
6781 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6784 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6785 MovDir[x][y + 1] != MV_DOWN ||
6786 MovPos[x][y + 1] <= TILEY / 2));
6788 // do not smash moving elements that left the smashed field in time
6789 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6790 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6793 #if USE_QUICKSAND_IMPACT_BUGFIX
6794 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6796 RemoveMovingField(x, y + 1);
6797 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6798 Tile[x][y + 2] = EL_ROCK;
6799 TEST_DrawLevelField(x, y + 2);
6804 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6806 RemoveMovingField(x, y + 1);
6807 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6808 Tile[x][y + 2] = EL_ROCK;
6809 TEST_DrawLevelField(x, y + 2);
6816 smashed = MovingOrBlocked2Element(x, y + 1);
6818 impact = (last_line || object_hit);
6821 if (!last_line && smashed == EL_ACID) // element falls into acid
6823 SplashAcid(x, y + 1);
6827 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6828 // only reset graphic animation if graphic really changes after impact
6830 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6832 ResetGfxAnimation(x, y);
6833 TEST_DrawLevelField(x, y);
6836 if (impact && CAN_EXPLODE_IMPACT(element))
6841 else if (impact && element == EL_PEARL &&
6842 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6844 ResetGfxAnimation(x, y);
6846 Tile[x][y] = EL_PEARL_BREAKING;
6847 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6850 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6852 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6857 if (impact && element == EL_AMOEBA_DROP)
6859 if (object_hit && IS_PLAYER(x, y + 1))
6860 KillPlayerUnlessEnemyProtected(x, y + 1);
6861 else if (object_hit && smashed == EL_PENGUIN)
6865 Tile[x][y] = EL_AMOEBA_GROWING;
6866 Store[x][y] = EL_AMOEBA_WET;
6868 ResetRandomAnimationValue(x, y);
6873 if (object_hit) // check which object was hit
6875 if ((CAN_PASS_MAGIC_WALL(element) &&
6876 (smashed == EL_MAGIC_WALL ||
6877 smashed == EL_BD_MAGIC_WALL)) ||
6878 (CAN_PASS_DC_MAGIC_WALL(element) &&
6879 smashed == EL_DC_MAGIC_WALL))
6882 int activated_magic_wall =
6883 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6884 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6885 EL_DC_MAGIC_WALL_ACTIVE);
6887 // activate magic wall / mill
6888 SCAN_PLAYFIELD(xx, yy)
6890 if (Tile[xx][yy] == smashed)
6891 Tile[xx][yy] = activated_magic_wall;
6894 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6895 game.magic_wall_active = TRUE;
6897 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6898 SND_MAGIC_WALL_ACTIVATING :
6899 smashed == EL_BD_MAGIC_WALL ?
6900 SND_BD_MAGIC_WALL_ACTIVATING :
6901 SND_DC_MAGIC_WALL_ACTIVATING));
6904 if (IS_PLAYER(x, y + 1))
6906 if (CAN_SMASH_PLAYER(element))
6908 KillPlayerUnlessEnemyProtected(x, y + 1);
6912 else if (smashed == EL_PENGUIN)
6914 if (CAN_SMASH_PLAYER(element))
6920 else if (element == EL_BD_DIAMOND)
6922 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6928 else if (((element == EL_SP_INFOTRON ||
6929 element == EL_SP_ZONK) &&
6930 (smashed == EL_SP_SNIKSNAK ||
6931 smashed == EL_SP_ELECTRON ||
6932 smashed == EL_SP_DISK_ORANGE)) ||
6933 (element == EL_SP_INFOTRON &&
6934 smashed == EL_SP_DISK_YELLOW))
6939 else if (CAN_SMASH_EVERYTHING(element))
6941 if (IS_CLASSIC_ENEMY(smashed) ||
6942 CAN_EXPLODE_SMASHED(smashed))
6947 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6949 if (smashed == EL_LAMP ||
6950 smashed == EL_LAMP_ACTIVE)
6955 else if (smashed == EL_NUT)
6957 Tile[x][y + 1] = EL_NUT_BREAKING;
6958 PlayLevelSound(x, y, SND_NUT_BREAKING);
6959 RaiseScoreElement(EL_NUT);
6962 else if (smashed == EL_PEARL)
6964 ResetGfxAnimation(x, y);
6966 Tile[x][y + 1] = EL_PEARL_BREAKING;
6967 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6970 else if (smashed == EL_DIAMOND)
6972 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6973 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6976 else if (IS_BELT_SWITCH(smashed))
6978 ToggleBeltSwitch(x, y + 1);
6980 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6981 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6982 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6983 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6985 ToggleSwitchgateSwitch();
6987 else if (smashed == EL_LIGHT_SWITCH ||
6988 smashed == EL_LIGHT_SWITCH_ACTIVE)
6990 ToggleLightSwitch(x, y + 1);
6994 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6996 CheckElementChangeBySide(x, y + 1, smashed, element,
6997 CE_SWITCHED, CH_SIDE_TOP);
6998 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
7004 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7009 // play sound of magic wall / mill
7011 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7012 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
7013 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
7015 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7016 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
7017 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7018 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
7019 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7020 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
7025 // play sound of object that hits the ground
7026 if (last_line || object_hit)
7027 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
7030 static void TurnRoundExt(int x, int y)
7042 { 0, 0 }, { 0, 0 }, { 0, 0 },
7047 int left, right, back;
7051 { MV_DOWN, MV_UP, MV_RIGHT },
7052 { MV_UP, MV_DOWN, MV_LEFT },
7054 { MV_LEFT, MV_RIGHT, MV_DOWN },
7058 { MV_RIGHT, MV_LEFT, MV_UP }
7061 int element = Tile[x][y];
7062 int move_pattern = element_info[element].move_pattern;
7064 int old_move_dir = MovDir[x][y];
7065 int left_dir = turn[old_move_dir].left;
7066 int right_dir = turn[old_move_dir].right;
7067 int back_dir = turn[old_move_dir].back;
7069 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
7070 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
7071 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
7072 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
7074 int left_x = x + left_dx, left_y = y + left_dy;
7075 int right_x = x + right_dx, right_y = y + right_dy;
7076 int move_x = x + move_dx, move_y = y + move_dy;
7080 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
7082 TestIfBadThingTouchesOtherBadThing(x, y);
7084 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
7085 MovDir[x][y] = right_dir;
7086 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7087 MovDir[x][y] = left_dir;
7089 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7091 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7094 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7096 TestIfBadThingTouchesOtherBadThing(x, y);
7098 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7099 MovDir[x][y] = left_dir;
7100 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7101 MovDir[x][y] = right_dir;
7103 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7105 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7108 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7110 TestIfBadThingTouchesOtherBadThing(x, y);
7112 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7113 MovDir[x][y] = left_dir;
7114 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7115 MovDir[x][y] = right_dir;
7117 if (MovDir[x][y] != old_move_dir)
7120 else if (element == EL_YAMYAM)
7122 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7123 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7125 if (can_turn_left && can_turn_right)
7126 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7127 else if (can_turn_left)
7128 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7129 else if (can_turn_right)
7130 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7132 MovDir[x][y] = back_dir;
7134 MovDelay[x][y] = 16 + 16 * RND(3);
7136 else if (element == EL_DARK_YAMYAM)
7138 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7140 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7143 if (can_turn_left && can_turn_right)
7144 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7145 else if (can_turn_left)
7146 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7147 else if (can_turn_right)
7148 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7150 MovDir[x][y] = back_dir;
7152 MovDelay[x][y] = 16 + 16 * RND(3);
7154 else if (element == EL_PACMAN)
7156 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7157 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7159 if (can_turn_left && can_turn_right)
7160 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7161 else if (can_turn_left)
7162 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7163 else if (can_turn_right)
7164 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7166 MovDir[x][y] = back_dir;
7168 MovDelay[x][y] = 6 + RND(40);
7170 else if (element == EL_PIG)
7172 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7173 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7174 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7175 boolean should_turn_left, should_turn_right, should_move_on;
7177 int rnd = RND(rnd_value);
7179 should_turn_left = (can_turn_left &&
7181 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7182 y + back_dy + left_dy)));
7183 should_turn_right = (can_turn_right &&
7185 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7186 y + back_dy + right_dy)));
7187 should_move_on = (can_move_on &&
7190 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7191 y + move_dy + left_dy) ||
7192 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7193 y + move_dy + right_dy)));
7195 if (should_turn_left || should_turn_right || should_move_on)
7197 if (should_turn_left && should_turn_right && should_move_on)
7198 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7199 rnd < 2 * rnd_value / 3 ? right_dir :
7201 else if (should_turn_left && should_turn_right)
7202 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7203 else if (should_turn_left && should_move_on)
7204 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7205 else if (should_turn_right && should_move_on)
7206 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7207 else if (should_turn_left)
7208 MovDir[x][y] = left_dir;
7209 else if (should_turn_right)
7210 MovDir[x][y] = right_dir;
7211 else if (should_move_on)
7212 MovDir[x][y] = old_move_dir;
7214 else if (can_move_on && rnd > rnd_value / 8)
7215 MovDir[x][y] = old_move_dir;
7216 else if (can_turn_left && can_turn_right)
7217 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7218 else if (can_turn_left && rnd > rnd_value / 8)
7219 MovDir[x][y] = left_dir;
7220 else if (can_turn_right && rnd > rnd_value/8)
7221 MovDir[x][y] = right_dir;
7223 MovDir[x][y] = back_dir;
7225 xx = x + move_xy[MovDir[x][y]].dx;
7226 yy = y + move_xy[MovDir[x][y]].dy;
7228 if (!IN_LEV_FIELD(xx, yy) ||
7229 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7230 MovDir[x][y] = old_move_dir;
7234 else if (element == EL_DRAGON)
7236 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7237 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7238 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7240 int rnd = RND(rnd_value);
7242 if (can_move_on && rnd > rnd_value / 8)
7243 MovDir[x][y] = old_move_dir;
7244 else if (can_turn_left && can_turn_right)
7245 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7246 else if (can_turn_left && rnd > rnd_value / 8)
7247 MovDir[x][y] = left_dir;
7248 else if (can_turn_right && rnd > rnd_value / 8)
7249 MovDir[x][y] = right_dir;
7251 MovDir[x][y] = back_dir;
7253 xx = x + move_xy[MovDir[x][y]].dx;
7254 yy = y + move_xy[MovDir[x][y]].dy;
7256 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7257 MovDir[x][y] = old_move_dir;
7261 else if (element == EL_MOLE)
7263 boolean can_move_on =
7264 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7265 IS_AMOEBOID(Tile[move_x][move_y]) ||
7266 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7269 boolean can_turn_left =
7270 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7271 IS_AMOEBOID(Tile[left_x][left_y])));
7273 boolean can_turn_right =
7274 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7275 IS_AMOEBOID(Tile[right_x][right_y])));
7277 if (can_turn_left && can_turn_right)
7278 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7279 else if (can_turn_left)
7280 MovDir[x][y] = left_dir;
7282 MovDir[x][y] = right_dir;
7285 if (MovDir[x][y] != old_move_dir)
7288 else if (element == EL_BALLOON)
7290 MovDir[x][y] = game.wind_direction;
7293 else if (element == EL_SPRING)
7295 if (MovDir[x][y] & MV_HORIZONTAL)
7297 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7298 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7300 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7301 ResetGfxAnimation(move_x, move_y);
7302 TEST_DrawLevelField(move_x, move_y);
7304 MovDir[x][y] = back_dir;
7306 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7307 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7308 MovDir[x][y] = MV_NONE;
7313 else if (element == EL_ROBOT ||
7314 element == EL_SATELLITE ||
7315 element == EL_PENGUIN ||
7316 element == EL_EMC_ANDROID)
7318 int attr_x = -1, attr_y = -1;
7320 if (game.all_players_gone)
7322 attr_x = game.exit_x;
7323 attr_y = game.exit_y;
7329 for (i = 0; i < MAX_PLAYERS; i++)
7331 struct PlayerInfo *player = &stored_player[i];
7332 int jx = player->jx, jy = player->jy;
7334 if (!player->active)
7338 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7346 if (element == EL_ROBOT &&
7347 game.robot_wheel_x >= 0 &&
7348 game.robot_wheel_y >= 0 &&
7349 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7350 game.engine_version < VERSION_IDENT(3,1,0,0)))
7352 attr_x = game.robot_wheel_x;
7353 attr_y = game.robot_wheel_y;
7356 if (element == EL_PENGUIN)
7359 struct XY *xy = xy_topdown;
7361 for (i = 0; i < NUM_DIRECTIONS; i++)
7363 int ex = x + xy[i].x;
7364 int ey = y + xy[i].y;
7366 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7367 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7368 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7369 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7378 MovDir[x][y] = MV_NONE;
7380 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7381 else if (attr_x > x)
7382 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7384 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7385 else if (attr_y > y)
7386 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7388 if (element == EL_ROBOT)
7392 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7393 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7394 Moving2Blocked(x, y, &newx, &newy);
7396 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7397 MovDelay[x][y] = 8 + 8 * !RND(3);
7399 MovDelay[x][y] = 16;
7401 else if (element == EL_PENGUIN)
7407 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7409 boolean first_horiz = RND(2);
7410 int new_move_dir = MovDir[x][y];
7413 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7414 Moving2Blocked(x, y, &newx, &newy);
7416 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7420 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7421 Moving2Blocked(x, y, &newx, &newy);
7423 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7426 MovDir[x][y] = old_move_dir;
7430 else if (element == EL_SATELLITE)
7436 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7438 boolean first_horiz = RND(2);
7439 int new_move_dir = MovDir[x][y];
7442 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7443 Moving2Blocked(x, y, &newx, &newy);
7445 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7449 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7450 Moving2Blocked(x, y, &newx, &newy);
7452 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7455 MovDir[x][y] = old_move_dir;
7459 else if (element == EL_EMC_ANDROID)
7461 static int check_pos[16] =
7463 -1, // 0 => (invalid)
7466 -1, // 3 => (invalid)
7468 0, // 5 => MV_LEFT | MV_UP
7469 2, // 6 => MV_RIGHT | MV_UP
7470 -1, // 7 => (invalid)
7472 6, // 9 => MV_LEFT | MV_DOWN
7473 4, // 10 => MV_RIGHT | MV_DOWN
7474 -1, // 11 => (invalid)
7475 -1, // 12 => (invalid)
7476 -1, // 13 => (invalid)
7477 -1, // 14 => (invalid)
7478 -1, // 15 => (invalid)
7486 { -1, -1, MV_LEFT | MV_UP },
7488 { +1, -1, MV_RIGHT | MV_UP },
7489 { +1, 0, MV_RIGHT },
7490 { +1, +1, MV_RIGHT | MV_DOWN },
7492 { -1, +1, MV_LEFT | MV_DOWN },
7495 int start_pos, check_order;
7496 boolean can_clone = FALSE;
7499 // check if there is any free field around current position
7500 for (i = 0; i < 8; i++)
7502 int newx = x + check_xy[i].dx;
7503 int newy = y + check_xy[i].dy;
7505 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7513 if (can_clone) // randomly find an element to clone
7517 start_pos = check_pos[RND(8)];
7518 check_order = (RND(2) ? -1 : +1);
7520 for (i = 0; i < 8; i++)
7522 int pos_raw = start_pos + i * check_order;
7523 int pos = (pos_raw + 8) % 8;
7524 int newx = x + check_xy[pos].dx;
7525 int newy = y + check_xy[pos].dy;
7527 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7529 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7530 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7532 Store[x][y] = Tile[newx][newy];
7541 if (can_clone) // randomly find a direction to move
7545 start_pos = check_pos[RND(8)];
7546 check_order = (RND(2) ? -1 : +1);
7548 for (i = 0; i < 8; i++)
7550 int pos_raw = start_pos + i * check_order;
7551 int pos = (pos_raw + 8) % 8;
7552 int newx = x + check_xy[pos].dx;
7553 int newy = y + check_xy[pos].dy;
7554 int new_move_dir = check_xy[pos].dir;
7556 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7558 MovDir[x][y] = new_move_dir;
7559 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7568 if (can_clone) // cloning and moving successful
7571 // cannot clone -- try to move towards player
7573 start_pos = check_pos[MovDir[x][y] & 0x0f];
7574 check_order = (RND(2) ? -1 : +1);
7576 for (i = 0; i < 3; i++)
7578 // first check start_pos, then previous/next or (next/previous) pos
7579 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7580 int pos = (pos_raw + 8) % 8;
7581 int newx = x + check_xy[pos].dx;
7582 int newy = y + check_xy[pos].dy;
7583 int new_move_dir = check_xy[pos].dir;
7585 if (IS_PLAYER(newx, newy))
7588 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7590 MovDir[x][y] = new_move_dir;
7591 MovDelay[x][y] = level.android_move_time * 8 + 1;
7598 else if (move_pattern == MV_TURNING_LEFT ||
7599 move_pattern == MV_TURNING_RIGHT ||
7600 move_pattern == MV_TURNING_LEFT_RIGHT ||
7601 move_pattern == MV_TURNING_RIGHT_LEFT ||
7602 move_pattern == MV_TURNING_RANDOM ||
7603 move_pattern == MV_ALL_DIRECTIONS)
7605 boolean can_turn_left =
7606 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7607 boolean can_turn_right =
7608 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y);
7610 if (element_info[element].move_stepsize == 0) // "not moving"
7613 if (move_pattern == MV_TURNING_LEFT)
7614 MovDir[x][y] = left_dir;
7615 else if (move_pattern == MV_TURNING_RIGHT)
7616 MovDir[x][y] = right_dir;
7617 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7618 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7619 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7620 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7621 else if (move_pattern == MV_TURNING_RANDOM)
7622 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7623 can_turn_right && !can_turn_left ? right_dir :
7624 RND(2) ? left_dir : right_dir);
7625 else if (can_turn_left && can_turn_right)
7626 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7627 else if (can_turn_left)
7628 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7629 else if (can_turn_right)
7630 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7632 MovDir[x][y] = back_dir;
7634 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7636 else if (move_pattern == MV_HORIZONTAL ||
7637 move_pattern == MV_VERTICAL)
7639 if (move_pattern & old_move_dir)
7640 MovDir[x][y] = back_dir;
7641 else if (move_pattern == MV_HORIZONTAL)
7642 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7643 else if (move_pattern == MV_VERTICAL)
7644 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7646 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7648 else if (move_pattern & MV_ANY_DIRECTION)
7650 MovDir[x][y] = move_pattern;
7651 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7653 else if (move_pattern & MV_WIND_DIRECTION)
7655 MovDir[x][y] = game.wind_direction;
7656 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7658 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7660 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7661 MovDir[x][y] = left_dir;
7662 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7663 MovDir[x][y] = right_dir;
7665 if (MovDir[x][y] != old_move_dir)
7666 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7668 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7670 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7671 MovDir[x][y] = right_dir;
7672 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7673 MovDir[x][y] = left_dir;
7675 if (MovDir[x][y] != old_move_dir)
7676 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7678 else if (move_pattern == MV_TOWARDS_PLAYER ||
7679 move_pattern == MV_AWAY_FROM_PLAYER)
7681 int attr_x = -1, attr_y = -1;
7683 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7685 if (game.all_players_gone)
7687 attr_x = game.exit_x;
7688 attr_y = game.exit_y;
7694 for (i = 0; i < MAX_PLAYERS; i++)
7696 struct PlayerInfo *player = &stored_player[i];
7697 int jx = player->jx, jy = player->jy;
7699 if (!player->active)
7703 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7711 MovDir[x][y] = MV_NONE;
7713 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7714 else if (attr_x > x)
7715 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7717 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7718 else if (attr_y > y)
7719 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7721 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7723 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7725 boolean first_horiz = RND(2);
7726 int new_move_dir = MovDir[x][y];
7728 if (element_info[element].move_stepsize == 0) // "not moving"
7730 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7731 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7737 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7738 Moving2Blocked(x, y, &newx, &newy);
7740 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7744 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7745 Moving2Blocked(x, y, &newx, &newy);
7747 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7750 MovDir[x][y] = old_move_dir;
7753 else if (move_pattern == MV_WHEN_PUSHED ||
7754 move_pattern == MV_WHEN_DROPPED)
7756 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7757 MovDir[x][y] = MV_NONE;
7761 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7763 struct XY *test_xy = xy_topdown;
7764 static int test_dir[4] =
7771 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7772 int move_preference = -1000000; // start with very low preference
7773 int new_move_dir = MV_NONE;
7774 int start_test = RND(4);
7777 for (i = 0; i < NUM_DIRECTIONS; i++)
7779 int j = (start_test + i) % 4;
7780 int move_dir = test_dir[j];
7781 int move_dir_preference;
7783 xx = x + test_xy[j].x;
7784 yy = y + test_xy[j].y;
7786 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7787 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7789 new_move_dir = move_dir;
7794 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7797 move_dir_preference = -1 * RunnerVisit[xx][yy];
7798 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7799 move_dir_preference = PlayerVisit[xx][yy];
7801 if (move_dir_preference > move_preference)
7803 // prefer field that has not been visited for the longest time
7804 move_preference = move_dir_preference;
7805 new_move_dir = move_dir;
7807 else if (move_dir_preference == move_preference &&
7808 move_dir == old_move_dir)
7810 // prefer last direction when all directions are preferred equally
7811 move_preference = move_dir_preference;
7812 new_move_dir = move_dir;
7816 MovDir[x][y] = new_move_dir;
7817 if (old_move_dir != new_move_dir)
7818 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7822 static void TurnRound(int x, int y)
7824 int direction = MovDir[x][y];
7828 GfxDir[x][y] = MovDir[x][y];
7830 if (direction != MovDir[x][y])
7834 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7836 ResetGfxFrame(x, y);
7839 static boolean JustBeingPushed(int x, int y)
7843 for (i = 0; i < MAX_PLAYERS; i++)
7845 struct PlayerInfo *player = &stored_player[i];
7847 if (player->active && player->is_pushing && player->MovPos)
7849 int next_jx = player->jx + (player->jx - player->last_jx);
7850 int next_jy = player->jy + (player->jy - player->last_jy);
7852 if (x == next_jx && y == next_jy)
7860 static void StartMoving(int x, int y)
7862 boolean started_moving = FALSE; // some elements can fall _and_ move
7863 int element = Tile[x][y];
7868 if (MovDelay[x][y] == 0)
7869 GfxAction[x][y] = ACTION_DEFAULT;
7871 if (CAN_FALL(element) && y < lev_fieldy - 1)
7873 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7874 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7875 if (JustBeingPushed(x, y))
7878 if (element == EL_QUICKSAND_FULL)
7880 if (IS_FREE(x, y + 1))
7882 InitMovingField(x, y, MV_DOWN);
7883 started_moving = TRUE;
7885 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7886 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7887 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7888 Store[x][y] = EL_ROCK;
7890 Store[x][y] = EL_ROCK;
7893 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7895 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7897 if (!MovDelay[x][y])
7899 MovDelay[x][y] = TILEY + 1;
7901 ResetGfxAnimation(x, y);
7902 ResetGfxAnimation(x, y + 1);
7907 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7908 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7915 Tile[x][y] = EL_QUICKSAND_EMPTY;
7916 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7917 Store[x][y + 1] = Store[x][y];
7920 PlayLevelSoundAction(x, y, ACTION_FILLING);
7922 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7924 if (!MovDelay[x][y])
7926 MovDelay[x][y] = TILEY + 1;
7928 ResetGfxAnimation(x, y);
7929 ResetGfxAnimation(x, y + 1);
7934 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7935 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7942 Tile[x][y] = EL_QUICKSAND_EMPTY;
7943 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7944 Store[x][y + 1] = Store[x][y];
7947 PlayLevelSoundAction(x, y, ACTION_FILLING);
7950 else if (element == EL_QUICKSAND_FAST_FULL)
7952 if (IS_FREE(x, y + 1))
7954 InitMovingField(x, y, MV_DOWN);
7955 started_moving = TRUE;
7957 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7958 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7959 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7960 Store[x][y] = EL_ROCK;
7962 Store[x][y] = EL_ROCK;
7965 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7967 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7969 if (!MovDelay[x][y])
7971 MovDelay[x][y] = TILEY + 1;
7973 ResetGfxAnimation(x, y);
7974 ResetGfxAnimation(x, y + 1);
7979 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7980 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7987 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7988 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7989 Store[x][y + 1] = Store[x][y];
7992 PlayLevelSoundAction(x, y, ACTION_FILLING);
7994 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7996 if (!MovDelay[x][y])
7998 MovDelay[x][y] = TILEY + 1;
8000 ResetGfxAnimation(x, y);
8001 ResetGfxAnimation(x, y + 1);
8006 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8007 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8014 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8015 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8016 Store[x][y + 1] = Store[x][y];
8019 PlayLevelSoundAction(x, y, ACTION_FILLING);
8022 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8023 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8025 InitMovingField(x, y, MV_DOWN);
8026 started_moving = TRUE;
8028 Tile[x][y] = EL_QUICKSAND_FILLING;
8029 Store[x][y] = element;
8031 PlayLevelSoundAction(x, y, ACTION_FILLING);
8033 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8034 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8036 InitMovingField(x, y, MV_DOWN);
8037 started_moving = TRUE;
8039 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
8040 Store[x][y] = element;
8042 PlayLevelSoundAction(x, y, ACTION_FILLING);
8044 else if (element == EL_MAGIC_WALL_FULL)
8046 if (IS_FREE(x, y + 1))
8048 InitMovingField(x, y, MV_DOWN);
8049 started_moving = TRUE;
8051 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
8052 Store[x][y] = EL_CHANGED(Store[x][y]);
8054 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
8056 if (!MovDelay[x][y])
8057 MovDelay[x][y] = TILEY / 4 + 1;
8066 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
8067 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
8068 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8072 else if (element == EL_BD_MAGIC_WALL_FULL)
8074 if (IS_FREE(x, y + 1))
8076 InitMovingField(x, y, MV_DOWN);
8077 started_moving = TRUE;
8079 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8080 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8082 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8084 if (!MovDelay[x][y])
8085 MovDelay[x][y] = TILEY / 4 + 1;
8094 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8095 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8096 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8100 else if (element == EL_DC_MAGIC_WALL_FULL)
8102 if (IS_FREE(x, y + 1))
8104 InitMovingField(x, y, MV_DOWN);
8105 started_moving = TRUE;
8107 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8108 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8110 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8112 if (!MovDelay[x][y])
8113 MovDelay[x][y] = TILEY / 4 + 1;
8122 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8123 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8124 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8128 else if ((CAN_PASS_MAGIC_WALL(element) &&
8129 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8130 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8131 (CAN_PASS_DC_MAGIC_WALL(element) &&
8132 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8135 InitMovingField(x, y, MV_DOWN);
8136 started_moving = TRUE;
8139 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8140 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8141 EL_DC_MAGIC_WALL_FILLING);
8142 Store[x][y] = element;
8144 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8146 SplashAcid(x, y + 1);
8148 InitMovingField(x, y, MV_DOWN);
8149 started_moving = TRUE;
8151 Store[x][y] = EL_ACID;
8154 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8155 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8156 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8157 CAN_FALL(element) && WasJustFalling[x][y] &&
8158 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8160 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8161 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8162 (Tile[x][y + 1] == EL_BLOCKED)))
8164 /* this is needed for a special case not covered by calling "Impact()"
8165 from "ContinueMoving()": if an element moves to a tile directly below
8166 another element which was just falling on that tile (which was empty
8167 in the previous frame), the falling element above would just stop
8168 instead of smashing the element below (in previous version, the above
8169 element was just checked for "moving" instead of "falling", resulting
8170 in incorrect smashes caused by horizontal movement of the above
8171 element; also, the case of the player being the element to smash was
8172 simply not covered here... :-/ ) */
8174 CheckCollision[x][y] = 0;
8175 CheckImpact[x][y] = 0;
8179 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8181 if (MovDir[x][y] == MV_NONE)
8183 InitMovingField(x, y, MV_DOWN);
8184 started_moving = TRUE;
8187 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8189 if (WasJustFalling[x][y]) // prevent animation from being restarted
8190 MovDir[x][y] = MV_DOWN;
8192 InitMovingField(x, y, MV_DOWN);
8193 started_moving = TRUE;
8195 else if (element == EL_AMOEBA_DROP)
8197 Tile[x][y] = EL_AMOEBA_GROWING;
8198 Store[x][y] = EL_AMOEBA_WET;
8200 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8201 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8202 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8203 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8205 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8206 (IS_FREE(x - 1, y + 1) ||
8207 Tile[x - 1][y + 1] == EL_ACID));
8208 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8209 (IS_FREE(x + 1, y + 1) ||
8210 Tile[x + 1][y + 1] == EL_ACID));
8211 boolean can_fall_any = (can_fall_left || can_fall_right);
8212 boolean can_fall_both = (can_fall_left && can_fall_right);
8213 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8215 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8217 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8218 can_fall_right = FALSE;
8219 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8220 can_fall_left = FALSE;
8221 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8222 can_fall_right = FALSE;
8223 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8224 can_fall_left = FALSE;
8226 can_fall_any = (can_fall_left || can_fall_right);
8227 can_fall_both = FALSE;
8232 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8233 can_fall_right = FALSE; // slip down on left side
8235 can_fall_left = !(can_fall_right = RND(2));
8237 can_fall_both = FALSE;
8242 // if not determined otherwise, prefer left side for slipping down
8243 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8244 started_moving = TRUE;
8247 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8249 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8250 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8251 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8252 int belt_dir = game.belt_dir[belt_nr];
8254 if ((belt_dir == MV_LEFT && left_is_free) ||
8255 (belt_dir == MV_RIGHT && right_is_free))
8257 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8259 InitMovingField(x, y, belt_dir);
8260 started_moving = TRUE;
8262 Pushed[x][y] = TRUE;
8263 Pushed[nextx][y] = TRUE;
8265 GfxAction[x][y] = ACTION_DEFAULT;
8269 MovDir[x][y] = 0; // if element was moving, stop it
8274 // not "else if" because of elements that can fall and move (EL_SPRING)
8275 if (CAN_MOVE(element) && !started_moving)
8277 int move_pattern = element_info[element].move_pattern;
8280 Moving2Blocked(x, y, &newx, &newy);
8282 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8285 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8286 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8288 WasJustMoving[x][y] = 0;
8289 CheckCollision[x][y] = 0;
8291 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8293 if (Tile[x][y] != element) // element has changed
8297 if (!MovDelay[x][y]) // start new movement phase
8299 // all objects that can change their move direction after each step
8300 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8302 if (element != EL_YAMYAM &&
8303 element != EL_DARK_YAMYAM &&
8304 element != EL_PACMAN &&
8305 !(move_pattern & MV_ANY_DIRECTION) &&
8306 move_pattern != MV_TURNING_LEFT &&
8307 move_pattern != MV_TURNING_RIGHT &&
8308 move_pattern != MV_TURNING_LEFT_RIGHT &&
8309 move_pattern != MV_TURNING_RIGHT_LEFT &&
8310 move_pattern != MV_TURNING_RANDOM)
8314 if (MovDelay[x][y] && (element == EL_BUG ||
8315 element == EL_SPACESHIP ||
8316 element == EL_SP_SNIKSNAK ||
8317 element == EL_SP_ELECTRON ||
8318 element == EL_MOLE))
8319 TEST_DrawLevelField(x, y);
8323 if (MovDelay[x][y]) // wait some time before next movement
8327 if (element == EL_ROBOT ||
8328 element == EL_YAMYAM ||
8329 element == EL_DARK_YAMYAM)
8331 DrawLevelElementAnimationIfNeeded(x, y, element);
8332 PlayLevelSoundAction(x, y, ACTION_WAITING);
8334 else if (element == EL_SP_ELECTRON)
8335 DrawLevelElementAnimationIfNeeded(x, y, element);
8336 else if (element == EL_DRAGON)
8339 int dir = MovDir[x][y];
8340 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8341 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8342 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8343 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8344 dir == MV_UP ? IMG_FLAMES_1_UP :
8345 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8346 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8348 GfxAction[x][y] = ACTION_ATTACKING;
8350 if (IS_PLAYER(x, y))
8351 DrawPlayerField(x, y);
8353 TEST_DrawLevelField(x, y);
8355 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8357 for (i = 1; i <= 3; i++)
8359 int xx = x + i * dx;
8360 int yy = y + i * dy;
8361 int sx = SCREENX(xx);
8362 int sy = SCREENY(yy);
8363 int flame_graphic = graphic + (i - 1);
8365 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8370 int flamed = MovingOrBlocked2Element(xx, yy);
8372 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8375 RemoveMovingField(xx, yy);
8377 ChangeDelay[xx][yy] = 0;
8379 Tile[xx][yy] = EL_FLAMES;
8381 if (IN_SCR_FIELD(sx, sy))
8383 TEST_DrawLevelFieldCrumbled(xx, yy);
8384 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8389 if (Tile[xx][yy] == EL_FLAMES)
8390 Tile[xx][yy] = EL_EMPTY;
8391 TEST_DrawLevelField(xx, yy);
8396 if (MovDelay[x][y]) // element still has to wait some time
8398 PlayLevelSoundAction(x, y, ACTION_WAITING);
8404 // now make next step
8406 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8408 if (DONT_COLLIDE_WITH(element) &&
8409 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8410 !PLAYER_ENEMY_PROTECTED(newx, newy))
8412 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8417 else if (CAN_MOVE_INTO_ACID(element) &&
8418 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8419 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8420 (MovDir[x][y] == MV_DOWN ||
8421 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8423 SplashAcid(newx, newy);
8424 Store[x][y] = EL_ACID;
8426 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8428 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8429 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8430 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8431 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8434 TEST_DrawLevelField(x, y);
8436 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8437 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8438 DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0);
8440 game.friends_still_needed--;
8441 if (!game.friends_still_needed &&
8443 game.all_players_gone)
8448 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8450 if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING)
8451 TEST_DrawLevelField(newx, newy);
8453 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8455 else if (!IS_FREE(newx, newy))
8457 GfxAction[x][y] = ACTION_WAITING;
8459 if (IS_PLAYER(x, y))
8460 DrawPlayerField(x, y);
8462 TEST_DrawLevelField(x, y);
8467 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8469 if (IS_FOOD_PIG(Tile[newx][newy]))
8471 if (IS_MOVING(newx, newy))
8472 RemoveMovingField(newx, newy);
8475 Tile[newx][newy] = EL_EMPTY;
8476 TEST_DrawLevelField(newx, newy);
8479 PlayLevelSound(x, y, SND_PIG_DIGGING);
8481 else if (!IS_FREE(newx, newy))
8483 if (IS_PLAYER(x, y))
8484 DrawPlayerField(x, y);
8486 TEST_DrawLevelField(x, y);
8491 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8493 if (Store[x][y] != EL_EMPTY)
8495 boolean can_clone = FALSE;
8498 // check if element to clone is still there
8499 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8501 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8509 // cannot clone or target field not free anymore -- do not clone
8510 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8511 Store[x][y] = EL_EMPTY;
8514 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8516 if (IS_MV_DIAGONAL(MovDir[x][y]))
8518 int diagonal_move_dir = MovDir[x][y];
8519 int stored = Store[x][y];
8520 int change_delay = 8;
8523 // android is moving diagonally
8525 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8527 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8528 GfxElement[x][y] = EL_EMC_ANDROID;
8529 GfxAction[x][y] = ACTION_SHRINKING;
8530 GfxDir[x][y] = diagonal_move_dir;
8531 ChangeDelay[x][y] = change_delay;
8533 if (Store[x][y] == EL_EMPTY)
8534 Store[x][y] = GfxElementEmpty[x][y];
8536 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8539 DrawLevelGraphicAnimation(x, y, graphic);
8540 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8542 if (Tile[newx][newy] == EL_ACID)
8544 SplashAcid(newx, newy);
8549 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8551 Store[newx][newy] = EL_EMC_ANDROID;
8552 GfxElement[newx][newy] = EL_EMC_ANDROID;
8553 GfxAction[newx][newy] = ACTION_GROWING;
8554 GfxDir[newx][newy] = diagonal_move_dir;
8555 ChangeDelay[newx][newy] = change_delay;
8557 graphic = el_act_dir2img(GfxElement[newx][newy],
8558 GfxAction[newx][newy], GfxDir[newx][newy]);
8560 DrawLevelGraphicAnimation(newx, newy, graphic);
8561 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8567 Tile[newx][newy] = EL_EMPTY;
8568 TEST_DrawLevelField(newx, newy);
8570 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8573 else if (!IS_FREE(newx, newy))
8578 else if (IS_CUSTOM_ELEMENT(element) &&
8579 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8581 if (!DigFieldByCE(newx, newy, element))
8584 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8586 RunnerVisit[x][y] = FrameCounter;
8587 PlayerVisit[x][y] /= 8; // expire player visit path
8590 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8592 if (!IS_FREE(newx, newy))
8594 if (IS_PLAYER(x, y))
8595 DrawPlayerField(x, y);
8597 TEST_DrawLevelField(x, y);
8603 boolean wanna_flame = !RND(10);
8604 int dx = newx - x, dy = newy - y;
8605 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8606 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8607 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8608 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8609 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8610 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8613 IS_CLASSIC_ENEMY(element1) ||
8614 IS_CLASSIC_ENEMY(element2)) &&
8615 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8616 element1 != EL_FLAMES && element2 != EL_FLAMES)
8618 ResetGfxAnimation(x, y);
8619 GfxAction[x][y] = ACTION_ATTACKING;
8621 if (IS_PLAYER(x, y))
8622 DrawPlayerField(x, y);
8624 TEST_DrawLevelField(x, y);
8626 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8628 MovDelay[x][y] = 50;
8630 Tile[newx][newy] = EL_FLAMES;
8631 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8632 Tile[newx1][newy1] = EL_FLAMES;
8633 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8634 Tile[newx2][newy2] = EL_FLAMES;
8640 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8641 Tile[newx][newy] == EL_DIAMOND)
8643 if (IS_MOVING(newx, newy))
8644 RemoveMovingField(newx, newy);
8647 Tile[newx][newy] = EL_EMPTY;
8648 TEST_DrawLevelField(newx, newy);
8651 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8653 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8654 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8656 if (AmoebaNr[newx][newy])
8658 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8659 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8660 Tile[newx][newy] == EL_BD_AMOEBA)
8661 AmoebaCnt[AmoebaNr[newx][newy]]--;
8664 if (IS_MOVING(newx, newy))
8666 RemoveMovingField(newx, newy);
8670 Tile[newx][newy] = EL_EMPTY;
8671 TEST_DrawLevelField(newx, newy);
8674 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8676 else if ((element == EL_PACMAN || element == EL_MOLE)
8677 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8679 if (AmoebaNr[newx][newy])
8681 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8682 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8683 Tile[newx][newy] == EL_BD_AMOEBA)
8684 AmoebaCnt[AmoebaNr[newx][newy]]--;
8687 if (element == EL_MOLE)
8689 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8690 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8692 ResetGfxAnimation(x, y);
8693 GfxAction[x][y] = ACTION_DIGGING;
8694 TEST_DrawLevelField(x, y);
8696 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8698 return; // wait for shrinking amoeba
8700 else // element == EL_PACMAN
8702 Tile[newx][newy] = EL_EMPTY;
8703 TEST_DrawLevelField(newx, newy);
8704 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8707 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8708 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8709 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8711 // wait for shrinking amoeba to completely disappear
8714 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8716 // object was running against a wall
8720 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8721 DrawLevelElementAnimation(x, y, element);
8723 if (DONT_TOUCH(element))
8724 TestIfBadThingTouchesPlayer(x, y);
8729 InitMovingField(x, y, MovDir[x][y]);
8731 PlayLevelSoundAction(x, y, ACTION_MOVING);
8735 ContinueMoving(x, y);
8738 void ContinueMoving(int x, int y)
8740 int element = Tile[x][y];
8741 struct ElementInfo *ei = &element_info[element];
8742 int direction = MovDir[x][y];
8743 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8744 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8745 int newx = x + dx, newy = y + dy;
8746 int stored = Store[x][y];
8747 int stored_new = Store[newx][newy];
8748 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8749 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8750 boolean last_line = (newy == lev_fieldy - 1);
8751 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8753 if (pushed_by_player) // special case: moving object pushed by player
8755 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos));
8757 else if (use_step_delay) // special case: moving object has step delay
8759 if (!MovDelay[x][y])
8760 MovPos[x][y] += getElementMoveStepsize(x, y);
8765 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8769 TEST_DrawLevelField(x, y);
8771 return; // element is still waiting
8774 else // normal case: generically moving object
8776 MovPos[x][y] += getElementMoveStepsize(x, y);
8779 if (ABS(MovPos[x][y]) < TILEX)
8781 TEST_DrawLevelField(x, y);
8783 return; // element is still moving
8786 // element reached destination field
8788 Tile[x][y] = EL_EMPTY;
8789 Tile[newx][newy] = element;
8790 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8792 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8794 element = Tile[newx][newy] = EL_ACID;
8796 else if (element == EL_MOLE)
8798 Tile[x][y] = EL_SAND;
8800 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8802 else if (element == EL_QUICKSAND_FILLING)
8804 element = Tile[newx][newy] = get_next_element(element);
8805 Store[newx][newy] = Store[x][y];
8807 else if (element == EL_QUICKSAND_EMPTYING)
8809 Tile[x][y] = get_next_element(element);
8810 element = Tile[newx][newy] = Store[x][y];
8812 else if (element == EL_QUICKSAND_FAST_FILLING)
8814 element = Tile[newx][newy] = get_next_element(element);
8815 Store[newx][newy] = Store[x][y];
8817 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8819 Tile[x][y] = get_next_element(element);
8820 element = Tile[newx][newy] = Store[x][y];
8822 else if (element == EL_MAGIC_WALL_FILLING)
8824 element = Tile[newx][newy] = get_next_element(element);
8825 if (!game.magic_wall_active)
8826 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8827 Store[newx][newy] = Store[x][y];
8829 else if (element == EL_MAGIC_WALL_EMPTYING)
8831 Tile[x][y] = get_next_element(element);
8832 if (!game.magic_wall_active)
8833 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8834 element = Tile[newx][newy] = Store[x][y];
8836 InitField(newx, newy, FALSE);
8838 else if (element == EL_BD_MAGIC_WALL_FILLING)
8840 element = Tile[newx][newy] = get_next_element(element);
8841 if (!game.magic_wall_active)
8842 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8843 Store[newx][newy] = Store[x][y];
8845 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8847 Tile[x][y] = get_next_element(element);
8848 if (!game.magic_wall_active)
8849 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8850 element = Tile[newx][newy] = Store[x][y];
8852 InitField(newx, newy, FALSE);
8854 else if (element == EL_DC_MAGIC_WALL_FILLING)
8856 element = Tile[newx][newy] = get_next_element(element);
8857 if (!game.magic_wall_active)
8858 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8859 Store[newx][newy] = Store[x][y];
8861 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8863 Tile[x][y] = get_next_element(element);
8864 if (!game.magic_wall_active)
8865 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8866 element = Tile[newx][newy] = Store[x][y];
8868 InitField(newx, newy, FALSE);
8870 else if (element == EL_AMOEBA_DROPPING)
8872 Tile[x][y] = get_next_element(element);
8873 element = Tile[newx][newy] = Store[x][y];
8875 else if (element == EL_SOKOBAN_OBJECT)
8878 Tile[x][y] = Back[x][y];
8880 if (Back[newx][newy])
8881 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8883 Back[x][y] = Back[newx][newy] = 0;
8886 Store[x][y] = EL_EMPTY;
8891 MovDelay[newx][newy] = 0;
8893 if (CAN_CHANGE_OR_HAS_ACTION(element))
8895 // copy element change control values to new field
8896 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8897 ChangePage[newx][newy] = ChangePage[x][y];
8898 ChangeCount[newx][newy] = ChangeCount[x][y];
8899 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8902 CustomValue[newx][newy] = CustomValue[x][y];
8904 ChangeDelay[x][y] = 0;
8905 ChangePage[x][y] = -1;
8906 ChangeCount[x][y] = 0;
8907 ChangeEvent[x][y] = -1;
8909 CustomValue[x][y] = 0;
8911 // copy animation control values to new field
8912 GfxFrame[newx][newy] = GfxFrame[x][y];
8913 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8914 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8915 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8917 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8919 // some elements can leave other elements behind after moving
8920 if (ei->move_leave_element != EL_EMPTY &&
8921 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8922 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8924 int move_leave_element = ei->move_leave_element;
8926 // this makes it possible to leave the removed element again
8927 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8928 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8930 Tile[x][y] = move_leave_element;
8932 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8933 MovDir[x][y] = direction;
8935 InitField(x, y, FALSE);
8937 if (GFX_CRUMBLED(Tile[x][y]))
8938 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8940 if (IS_PLAYER_ELEMENT(move_leave_element))
8941 RelocatePlayer(x, y, move_leave_element);
8944 // do this after checking for left-behind element
8945 ResetGfxAnimation(x, y); // reset animation values for old field
8947 if (!CAN_MOVE(element) ||
8948 (CAN_FALL(element) && direction == MV_DOWN &&
8949 (element == EL_SPRING ||
8950 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8951 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8952 GfxDir[x][y] = MovDir[newx][newy] = 0;
8954 TEST_DrawLevelField(x, y);
8955 TEST_DrawLevelField(newx, newy);
8957 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8959 // prevent pushed element from moving on in pushed direction
8960 if (pushed_by_player && CAN_MOVE(element) &&
8961 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8962 !(element_info[element].move_pattern & direction))
8963 TurnRound(newx, newy);
8965 // prevent elements on conveyor belt from moving on in last direction
8966 if (pushed_by_conveyor && CAN_FALL(element) &&
8967 direction & MV_HORIZONTAL)
8968 MovDir[newx][newy] = 0;
8970 if (!pushed_by_player)
8972 int nextx = newx + dx, nexty = newy + dy;
8973 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8975 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8977 if (CAN_FALL(element) && direction == MV_DOWN)
8978 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8980 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8981 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8983 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8984 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8987 if (DONT_TOUCH(element)) // object may be nasty to player or others
8989 TestIfBadThingTouchesPlayer(newx, newy);
8990 TestIfBadThingTouchesFriend(newx, newy);
8992 if (!IS_CUSTOM_ELEMENT(element))
8993 TestIfBadThingTouchesOtherBadThing(newx, newy);
8995 else if (element == EL_PENGUIN)
8996 TestIfFriendTouchesBadThing(newx, newy);
8998 if (DONT_GET_HIT_BY(element))
9000 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
9003 // give the player one last chance (one more frame) to move away
9004 if (CAN_FALL(element) && direction == MV_DOWN &&
9005 (last_line || (!IS_FREE(x, newy + 1) &&
9006 (!IS_PLAYER(x, newy + 1) ||
9007 game.engine_version < VERSION_IDENT(3,1,1,0)))))
9010 if (pushed_by_player && !game.use_change_when_pushing_bug)
9012 int push_side = MV_DIR_OPPOSITE(direction);
9013 struct PlayerInfo *player = PLAYERINFO(x, y);
9015 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
9016 player->index_bit, push_side);
9017 CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X,
9018 player->index_bit, push_side);
9021 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
9022 MovDelay[newx][newy] = 1;
9024 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
9026 TestIfElementTouchesCustomElement(x, y); // empty or new element
9027 TestIfElementHitsCustomElement(newx, newy, direction);
9028 TestIfPlayerTouchesCustomElement(newx, newy);
9029 TestIfElementTouchesCustomElement(newx, newy);
9031 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
9032 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
9033 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
9034 MV_DIR_OPPOSITE(direction));
9037 int AmoebaNeighbourNr(int ax, int ay)
9040 int element = Tile[ax][ay];
9042 struct XY *xy = xy_topdown;
9044 for (i = 0; i < NUM_DIRECTIONS; i++)
9046 int x = ax + xy[i].x;
9047 int y = ay + xy[i].y;
9049 if (!IN_LEV_FIELD(x, y))
9052 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
9053 group_nr = AmoebaNr[x][y];
9059 static void AmoebaMerge(int ax, int ay)
9061 int i, x, y, xx, yy;
9062 int new_group_nr = AmoebaNr[ax][ay];
9063 struct XY *xy = xy_topdown;
9065 if (new_group_nr == 0)
9068 for (i = 0; i < NUM_DIRECTIONS; i++)
9073 if (!IN_LEV_FIELD(x, y))
9076 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9077 Tile[x][y] == EL_BD_AMOEBA ||
9078 Tile[x][y] == EL_AMOEBA_DEAD) &&
9079 AmoebaNr[x][y] != new_group_nr)
9081 int old_group_nr = AmoebaNr[x][y];
9083 if (old_group_nr == 0)
9086 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9087 AmoebaCnt[old_group_nr] = 0;
9088 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9089 AmoebaCnt2[old_group_nr] = 0;
9091 SCAN_PLAYFIELD(xx, yy)
9093 if (AmoebaNr[xx][yy] == old_group_nr)
9094 AmoebaNr[xx][yy] = new_group_nr;
9100 void AmoebaToDiamond(int ax, int ay)
9104 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9106 int group_nr = AmoebaNr[ax][ay];
9111 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9112 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9118 SCAN_PLAYFIELD(x, y)
9120 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9123 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9127 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9128 SND_AMOEBA_TURNING_TO_GEM :
9129 SND_AMOEBA_TURNING_TO_ROCK));
9134 struct XY *xy = xy_topdown;
9136 for (i = 0; i < NUM_DIRECTIONS; i++)
9141 if (!IN_LEV_FIELD(x, y))
9144 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9146 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9147 SND_AMOEBA_TURNING_TO_GEM :
9148 SND_AMOEBA_TURNING_TO_ROCK));
9155 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9158 int group_nr = AmoebaNr[ax][ay];
9159 boolean done = FALSE;
9164 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9165 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9171 SCAN_PLAYFIELD(x, y)
9173 if (AmoebaNr[x][y] == group_nr &&
9174 (Tile[x][y] == EL_AMOEBA_DEAD ||
9175 Tile[x][y] == EL_BD_AMOEBA ||
9176 Tile[x][y] == EL_AMOEBA_GROWING))
9179 Tile[x][y] = new_element;
9180 InitField(x, y, FALSE);
9181 TEST_DrawLevelField(x, y);
9187 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9188 SND_BD_AMOEBA_TURNING_TO_ROCK :
9189 SND_BD_AMOEBA_TURNING_TO_GEM));
9192 static void AmoebaGrowing(int x, int y)
9194 static DelayCounter sound_delay = { 0 };
9196 if (!MovDelay[x][y]) // start new growing cycle
9200 if (DelayReached(&sound_delay))
9202 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9203 sound_delay.value = 30;
9207 if (MovDelay[x][y]) // wait some time before growing bigger
9210 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9212 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9213 6 - MovDelay[x][y]);
9215 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9218 if (!MovDelay[x][y])
9220 Tile[x][y] = Store[x][y];
9222 TEST_DrawLevelField(x, y);
9227 static void AmoebaShrinking(int x, int y)
9229 static DelayCounter sound_delay = { 0 };
9231 if (!MovDelay[x][y]) // start new shrinking cycle
9235 if (DelayReached(&sound_delay))
9236 sound_delay.value = 30;
9239 if (MovDelay[x][y]) // wait some time before shrinking
9242 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9244 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9245 6 - MovDelay[x][y]);
9247 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9250 if (!MovDelay[x][y])
9252 Tile[x][y] = EL_EMPTY;
9253 TEST_DrawLevelField(x, y);
9255 // don't let mole enter this field in this cycle;
9256 // (give priority to objects falling to this field from above)
9262 static void AmoebaReproduce(int ax, int ay)
9265 int element = Tile[ax][ay];
9266 int graphic = el2img(element);
9267 int newax = ax, neway = ay;
9268 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9269 struct XY *xy = xy_topdown;
9271 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9273 Tile[ax][ay] = EL_AMOEBA_DEAD;
9274 TEST_DrawLevelField(ax, ay);
9278 if (IS_ANIMATED(graphic))
9279 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9281 if (!MovDelay[ax][ay]) // start making new amoeba field
9282 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9284 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9287 if (MovDelay[ax][ay])
9291 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9294 int x = ax + xy[start].x;
9295 int y = ay + xy[start].y;
9297 if (!IN_LEV_FIELD(x, y))
9300 if (IS_FREE(x, y) ||
9301 CAN_GROW_INTO(Tile[x][y]) ||
9302 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9303 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9309 if (newax == ax && neway == ay)
9312 else // normal or "filled" (BD style) amoeba
9315 boolean waiting_for_player = FALSE;
9317 for (i = 0; i < NUM_DIRECTIONS; i++)
9319 int j = (start + i) % 4;
9320 int x = ax + xy[j].x;
9321 int y = ay + xy[j].y;
9323 if (!IN_LEV_FIELD(x, y))
9326 if (IS_FREE(x, y) ||
9327 CAN_GROW_INTO(Tile[x][y]) ||
9328 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9329 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9335 else if (IS_PLAYER(x, y))
9336 waiting_for_player = TRUE;
9339 if (newax == ax && neway == ay) // amoeba cannot grow
9341 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9343 Tile[ax][ay] = EL_AMOEBA_DEAD;
9344 TEST_DrawLevelField(ax, ay);
9345 AmoebaCnt[AmoebaNr[ax][ay]]--;
9347 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9349 if (element == EL_AMOEBA_FULL)
9350 AmoebaToDiamond(ax, ay);
9351 else if (element == EL_BD_AMOEBA)
9352 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9357 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9359 // amoeba gets larger by growing in some direction
9361 int new_group_nr = AmoebaNr[ax][ay];
9364 if (new_group_nr == 0)
9366 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9368 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9374 AmoebaNr[newax][neway] = new_group_nr;
9375 AmoebaCnt[new_group_nr]++;
9376 AmoebaCnt2[new_group_nr]++;
9378 // if amoeba touches other amoeba(s) after growing, unify them
9379 AmoebaMerge(newax, neway);
9381 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9383 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9389 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9390 (neway == lev_fieldy - 1 && newax != ax))
9392 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9393 Store[newax][neway] = element;
9395 else if (neway == ay || element == EL_EMC_DRIPPER)
9397 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9399 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9403 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9404 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9405 Store[ax][ay] = EL_AMOEBA_DROP;
9406 ContinueMoving(ax, ay);
9410 TEST_DrawLevelField(newax, neway);
9413 static void Life(int ax, int ay)
9417 int element = Tile[ax][ay];
9418 int graphic = el2img(element);
9419 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9421 boolean changed = FALSE;
9423 if (IS_ANIMATED(graphic))
9424 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9429 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9430 MovDelay[ax][ay] = life_time;
9432 if (MovDelay[ax][ay]) // wait some time before next cycle
9435 if (MovDelay[ax][ay])
9439 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9441 int xx = ax+x1, yy = ay+y1;
9442 int old_element = Tile[xx][yy];
9443 int num_neighbours = 0;
9445 if (!IN_LEV_FIELD(xx, yy))
9448 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9450 int x = xx+x2, y = yy+y2;
9452 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9455 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9456 boolean is_neighbour = FALSE;
9458 if (level.use_life_bugs)
9460 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9461 (IS_FREE(x, y) && Stop[x][y]));
9464 (Last[x][y] == element || is_player_cell);
9470 boolean is_free = FALSE;
9472 if (level.use_life_bugs)
9473 is_free = (IS_FREE(xx, yy));
9475 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9477 if (xx == ax && yy == ay) // field in the middle
9479 if (num_neighbours < life_parameter[0] ||
9480 num_neighbours > life_parameter[1])
9482 Tile[xx][yy] = EL_EMPTY;
9483 if (Tile[xx][yy] != old_element)
9484 TEST_DrawLevelField(xx, yy);
9485 Stop[xx][yy] = TRUE;
9489 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9490 { // free border field
9491 if (num_neighbours >= life_parameter[2] &&
9492 num_neighbours <= life_parameter[3])
9494 Tile[xx][yy] = element;
9495 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9496 if (Tile[xx][yy] != old_element)
9497 TEST_DrawLevelField(xx, yy);
9498 Stop[xx][yy] = TRUE;
9505 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9506 SND_GAME_OF_LIFE_GROWING);
9509 static void InitRobotWheel(int x, int y)
9511 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9514 static void RunRobotWheel(int x, int y)
9516 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9519 static void StopRobotWheel(int x, int y)
9521 if (game.robot_wheel_x == x &&
9522 game.robot_wheel_y == y)
9524 game.robot_wheel_x = -1;
9525 game.robot_wheel_y = -1;
9526 game.robot_wheel_active = FALSE;
9530 static void InitTimegateWheel(int x, int y)
9532 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9535 static void RunTimegateWheel(int x, int y)
9537 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9540 static void InitMagicBallDelay(int x, int y)
9542 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9545 static void ActivateMagicBall(int bx, int by)
9549 if (level.ball_random)
9551 int pos_border = RND(8); // select one of the eight border elements
9552 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9553 int xx = pos_content % 3;
9554 int yy = pos_content / 3;
9559 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9560 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9564 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9566 int xx = x - bx + 1;
9567 int yy = y - by + 1;
9569 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9570 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9574 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9577 static void CheckExit(int x, int y)
9579 if (game.gems_still_needed > 0 ||
9580 game.sokoban_fields_still_needed > 0 ||
9581 game.sokoban_objects_still_needed > 0 ||
9582 game.lights_still_needed > 0)
9584 int element = Tile[x][y];
9585 int graphic = el2img(element);
9587 if (IS_ANIMATED(graphic))
9588 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9593 // do not re-open exit door closed after last player
9594 if (game.all_players_gone)
9597 Tile[x][y] = EL_EXIT_OPENING;
9599 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9602 static void CheckExitEM(int x, int y)
9604 if (game.gems_still_needed > 0 ||
9605 game.sokoban_fields_still_needed > 0 ||
9606 game.sokoban_objects_still_needed > 0 ||
9607 game.lights_still_needed > 0)
9609 int element = Tile[x][y];
9610 int graphic = el2img(element);
9612 if (IS_ANIMATED(graphic))
9613 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9618 // do not re-open exit door closed after last player
9619 if (game.all_players_gone)
9622 Tile[x][y] = EL_EM_EXIT_OPENING;
9624 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9627 static void CheckExitSteel(int x, int y)
9629 if (game.gems_still_needed > 0 ||
9630 game.sokoban_fields_still_needed > 0 ||
9631 game.sokoban_objects_still_needed > 0 ||
9632 game.lights_still_needed > 0)
9634 int element = Tile[x][y];
9635 int graphic = el2img(element);
9637 if (IS_ANIMATED(graphic))
9638 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9643 // do not re-open exit door closed after last player
9644 if (game.all_players_gone)
9647 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9649 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9652 static void CheckExitSteelEM(int x, int y)
9654 if (game.gems_still_needed > 0 ||
9655 game.sokoban_fields_still_needed > 0 ||
9656 game.sokoban_objects_still_needed > 0 ||
9657 game.lights_still_needed > 0)
9659 int element = Tile[x][y];
9660 int graphic = el2img(element);
9662 if (IS_ANIMATED(graphic))
9663 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9668 // do not re-open exit door closed after last player
9669 if (game.all_players_gone)
9672 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9674 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9677 static void CheckExitSP(int x, int y)
9679 if (game.gems_still_needed > 0)
9681 int element = Tile[x][y];
9682 int graphic = el2img(element);
9684 if (IS_ANIMATED(graphic))
9685 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9690 // do not re-open exit door closed after last player
9691 if (game.all_players_gone)
9694 Tile[x][y] = EL_SP_EXIT_OPENING;
9696 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9699 static void CloseAllOpenTimegates(void)
9703 SCAN_PLAYFIELD(x, y)
9705 int element = Tile[x][y];
9707 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9709 Tile[x][y] = EL_TIMEGATE_CLOSING;
9711 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9716 static void DrawTwinkleOnField(int x, int y)
9718 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9721 if (Tile[x][y] == EL_BD_DIAMOND)
9724 if (MovDelay[x][y] == 0) // next animation frame
9725 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9727 if (MovDelay[x][y] != 0) // wait some time before next frame
9731 DrawLevelElementAnimation(x, y, Tile[x][y]);
9733 if (MovDelay[x][y] != 0)
9735 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9736 10 - MovDelay[x][y]);
9738 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9743 static void WallGrowing(int x, int y)
9747 if (!MovDelay[x][y]) // next animation frame
9748 MovDelay[x][y] = 3 * delay;
9750 if (MovDelay[x][y]) // wait some time before next frame
9754 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9756 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9757 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9759 DrawLevelGraphic(x, y, graphic, frame);
9762 if (!MovDelay[x][y])
9764 if (MovDir[x][y] == MV_LEFT)
9766 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9767 TEST_DrawLevelField(x - 1, y);
9769 else if (MovDir[x][y] == MV_RIGHT)
9771 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9772 TEST_DrawLevelField(x + 1, y);
9774 else if (MovDir[x][y] == MV_UP)
9776 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9777 TEST_DrawLevelField(x, y - 1);
9781 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9782 TEST_DrawLevelField(x, y + 1);
9785 Tile[x][y] = Store[x][y];
9787 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9788 TEST_DrawLevelField(x, y);
9793 static void CheckWallGrowing(int ax, int ay)
9795 int element = Tile[ax][ay];
9796 int graphic = el2img(element);
9797 boolean free_top = FALSE;
9798 boolean free_bottom = FALSE;
9799 boolean free_left = FALSE;
9800 boolean free_right = FALSE;
9801 boolean stop_top = FALSE;
9802 boolean stop_bottom = FALSE;
9803 boolean stop_left = FALSE;
9804 boolean stop_right = FALSE;
9805 boolean new_wall = FALSE;
9807 boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9808 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9809 element == EL_EXPANDABLE_STEELWALL_ANY);
9811 boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9812 element == EL_EXPANDABLE_WALL_ANY ||
9813 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9814 element == EL_EXPANDABLE_STEELWALL_ANY);
9816 boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9817 element == EL_EXPANDABLE_WALL_ANY ||
9818 element == EL_EXPANDABLE_WALL ||
9819 element == EL_BD_EXPANDABLE_WALL ||
9820 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9821 element == EL_EXPANDABLE_STEELWALL_ANY);
9823 boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9824 element == EL_EXPANDABLE_STEELWALL_VERTICAL);
9826 boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9827 element == EL_EXPANDABLE_WALL ||
9828 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL);
9830 int wall_growing = (is_steelwall ?
9831 EL_EXPANDABLE_STEELWALL_GROWING :
9832 EL_EXPANDABLE_WALL_GROWING);
9834 int gfx_wall_growing_up = (is_steelwall ?
9835 IMG_EXPANDABLE_STEELWALL_GROWING_UP :
9836 IMG_EXPANDABLE_WALL_GROWING_UP);
9837 int gfx_wall_growing_down = (is_steelwall ?
9838 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN :
9839 IMG_EXPANDABLE_WALL_GROWING_DOWN);
9840 int gfx_wall_growing_left = (is_steelwall ?
9841 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT :
9842 IMG_EXPANDABLE_WALL_GROWING_LEFT);
9843 int gfx_wall_growing_right = (is_steelwall ?
9844 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT :
9845 IMG_EXPANDABLE_WALL_GROWING_RIGHT);
9847 if (IS_ANIMATED(graphic))
9848 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9850 if (!MovDelay[ax][ay]) // start building new wall
9851 MovDelay[ax][ay] = 6;
9853 if (MovDelay[ax][ay]) // wait some time before building new wall
9856 if (MovDelay[ax][ay])
9860 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9862 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9864 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9866 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9873 Tile[ax][ay - 1] = wall_growing;
9874 Store[ax][ay - 1] = element;
9875 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9877 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9878 DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0);
9885 Tile[ax][ay + 1] = wall_growing;
9886 Store[ax][ay + 1] = element;
9887 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9889 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9890 DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0);
9896 if (grow_horizontal)
9900 Tile[ax - 1][ay] = wall_growing;
9901 Store[ax - 1][ay] = element;
9902 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9904 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9905 DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0);
9912 Tile[ax + 1][ay] = wall_growing;
9913 Store[ax + 1][ay] = element;
9914 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
9916 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
9917 DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0);
9923 if (element == EL_EXPANDABLE_WALL && (free_left || free_right))
9924 TEST_DrawLevelField(ax, ay);
9926 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
9928 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
9930 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
9932 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
9935 if (((stop_top && stop_bottom) || stop_horizontal) &&
9936 ((stop_left && stop_right) || stop_vertical))
9937 Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL);
9940 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9943 static void CheckForDragon(int x, int y)
9946 boolean dragon_found = FALSE;
9947 struct XY *xy = xy_topdown;
9949 for (i = 0; i < NUM_DIRECTIONS; i++)
9951 for (j = 0; j < 4; j++)
9953 int xx = x + j * xy[i].x;
9954 int yy = y + j * xy[i].y;
9956 if (IN_LEV_FIELD(xx, yy) &&
9957 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9959 if (Tile[xx][yy] == EL_DRAGON)
9960 dragon_found = TRUE;
9969 for (i = 0; i < NUM_DIRECTIONS; i++)
9971 for (j = 0; j < 3; j++)
9973 int xx = x + j * xy[i].x;
9974 int yy = y + j * xy[i].y;
9976 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9978 Tile[xx][yy] = EL_EMPTY;
9979 TEST_DrawLevelField(xx, yy);
9988 static void InitBuggyBase(int x, int y)
9990 int element = Tile[x][y];
9991 int activating_delay = FRAMES_PER_SECOND / 4;
9994 (element == EL_SP_BUGGY_BASE ?
9995 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9996 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9998 element == EL_SP_BUGGY_BASE_ACTIVE ?
9999 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10002 static void WarnBuggyBase(int x, int y)
10005 struct XY *xy = xy_topdown;
10007 for (i = 0; i < NUM_DIRECTIONS; i++)
10009 int xx = x + xy[i].x;
10010 int yy = y + xy[i].y;
10012 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10014 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10021 static void InitTrap(int x, int y)
10023 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10026 static void ActivateTrap(int x, int y)
10028 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10031 static void ChangeActiveTrap(int x, int y)
10033 int graphic = IMG_TRAP_ACTIVE;
10035 // if new animation frame was drawn, correct crumbled sand border
10036 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10037 TEST_DrawLevelFieldCrumbled(x, y);
10040 static int getSpecialActionElement(int element, int number, int base_element)
10042 return (element != EL_EMPTY ? element :
10043 number != -1 ? base_element + number - 1 :
10047 static int getModifiedActionNumber(int value_old, int operator, int operand,
10048 int value_min, int value_max)
10050 int value_new = (operator == CA_MODE_SET ? operand :
10051 operator == CA_MODE_ADD ? value_old + operand :
10052 operator == CA_MODE_SUBTRACT ? value_old - operand :
10053 operator == CA_MODE_MULTIPLY ? value_old * operand :
10054 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10055 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10058 return (value_new < value_min ? value_min :
10059 value_new > value_max ? value_max :
10063 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10065 struct ElementInfo *ei = &element_info[element];
10066 struct ElementChangeInfo *change = &ei->change_page[page];
10067 int target_element = change->target_element;
10068 int action_type = change->action_type;
10069 int action_mode = change->action_mode;
10070 int action_arg = change->action_arg;
10071 int action_element = change->action_element;
10074 if (!change->has_action)
10077 // ---------- determine action paramater values -----------------------------
10079 int level_time_value =
10080 (level.time > 0 ? TimeLeft :
10083 int action_arg_element_raw =
10084 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10085 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10086 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10087 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10088 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10089 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10090 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10092 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10094 int action_arg_direction =
10095 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10096 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10097 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10098 change->actual_trigger_side :
10099 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10100 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10103 int action_arg_number_min =
10104 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10107 int action_arg_number_max =
10108 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10109 action_type == CA_SET_LEVEL_GEMS ? 999 :
10110 action_type == CA_SET_LEVEL_TIME ? 9999 :
10111 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10112 action_type == CA_SET_CE_VALUE ? 9999 :
10113 action_type == CA_SET_CE_SCORE ? 9999 :
10116 int action_arg_number_reset =
10117 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10118 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10119 action_type == CA_SET_LEVEL_TIME ? level.time :
10120 action_type == CA_SET_LEVEL_SCORE ? 0 :
10121 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10122 action_type == CA_SET_CE_SCORE ? 0 :
10125 int action_arg_number =
10126 (action_arg <= CA_ARG_MAX ? action_arg :
10127 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10128 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10129 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10130 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10131 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10132 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10133 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10134 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10135 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10136 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10137 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10138 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10139 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10140 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10141 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10142 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10143 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10144 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10145 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10146 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10147 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10150 int action_arg_number_old =
10151 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10152 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10153 action_type == CA_SET_LEVEL_SCORE ? game.score :
10154 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10155 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10158 int action_arg_number_new =
10159 getModifiedActionNumber(action_arg_number_old,
10160 action_mode, action_arg_number,
10161 action_arg_number_min, action_arg_number_max);
10163 int trigger_player_bits =
10164 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10165 change->actual_trigger_player_bits : change->trigger_player);
10167 int action_arg_player_bits =
10168 (action_arg >= CA_ARG_PLAYER_1 &&
10169 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10170 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10171 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10174 // ---------- execute action -----------------------------------------------
10176 switch (action_type)
10183 // ---------- level actions ----------------------------------------------
10185 case CA_RESTART_LEVEL:
10187 game.restart_level = TRUE;
10192 case CA_SHOW_ENVELOPE:
10194 int element = getSpecialActionElement(action_arg_element,
10195 action_arg_number, EL_ENVELOPE_1);
10197 if (IS_ENVELOPE(element))
10198 local_player->show_envelope = element;
10203 case CA_SET_LEVEL_TIME:
10205 if (level.time > 0) // only modify limited time value
10207 TimeLeft = action_arg_number_new;
10209 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10211 DisplayGameControlValues();
10213 if (!TimeLeft && game.time_limit)
10214 for (i = 0; i < MAX_PLAYERS; i++)
10215 KillPlayer(&stored_player[i]);
10221 case CA_SET_LEVEL_SCORE:
10223 game.score = action_arg_number_new;
10225 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10227 DisplayGameControlValues();
10232 case CA_SET_LEVEL_GEMS:
10234 game.gems_still_needed = action_arg_number_new;
10236 game.snapshot.collected_item = TRUE;
10238 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10240 DisplayGameControlValues();
10245 case CA_SET_LEVEL_WIND:
10247 game.wind_direction = action_arg_direction;
10252 case CA_SET_LEVEL_RANDOM_SEED:
10254 // ensure that setting a new random seed while playing is predictable
10255 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10260 // ---------- player actions ---------------------------------------------
10262 case CA_MOVE_PLAYER:
10263 case CA_MOVE_PLAYER_NEW:
10265 // automatically move to the next field in specified direction
10266 for (i = 0; i < MAX_PLAYERS; i++)
10267 if (trigger_player_bits & (1 << i))
10268 if (action_type == CA_MOVE_PLAYER ||
10269 stored_player[i].MovPos == 0)
10270 stored_player[i].programmed_action = action_arg_direction;
10275 case CA_EXIT_PLAYER:
10277 for (i = 0; i < MAX_PLAYERS; i++)
10278 if (action_arg_player_bits & (1 << i))
10279 ExitPlayer(&stored_player[i]);
10281 if (game.players_still_needed == 0)
10287 case CA_KILL_PLAYER:
10289 for (i = 0; i < MAX_PLAYERS; i++)
10290 if (action_arg_player_bits & (1 << i))
10291 KillPlayer(&stored_player[i]);
10296 case CA_SET_PLAYER_KEYS:
10298 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10299 int element = getSpecialActionElement(action_arg_element,
10300 action_arg_number, EL_KEY_1);
10302 if (IS_KEY(element))
10304 for (i = 0; i < MAX_PLAYERS; i++)
10306 if (trigger_player_bits & (1 << i))
10308 stored_player[i].key[KEY_NR(element)] = key_state;
10310 DrawGameDoorValues();
10318 case CA_SET_PLAYER_SPEED:
10320 for (i = 0; i < MAX_PLAYERS; i++)
10322 if (trigger_player_bits & (1 << i))
10324 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10326 if (action_arg == CA_ARG_SPEED_FASTER &&
10327 stored_player[i].cannot_move)
10329 action_arg_number = STEPSIZE_VERY_SLOW;
10331 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10332 action_arg == CA_ARG_SPEED_FASTER)
10334 action_arg_number = 2;
10335 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10338 else if (action_arg == CA_ARG_NUMBER_RESET)
10340 action_arg_number = level.initial_player_stepsize[i];
10344 getModifiedActionNumber(move_stepsize,
10347 action_arg_number_min,
10348 action_arg_number_max);
10350 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10357 case CA_SET_PLAYER_SHIELD:
10359 for (i = 0; i < MAX_PLAYERS; i++)
10361 if (trigger_player_bits & (1 << i))
10363 if (action_arg == CA_ARG_SHIELD_OFF)
10365 stored_player[i].shield_normal_time_left = 0;
10366 stored_player[i].shield_deadly_time_left = 0;
10368 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10370 stored_player[i].shield_normal_time_left = 999999;
10372 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10374 stored_player[i].shield_normal_time_left = 999999;
10375 stored_player[i].shield_deadly_time_left = 999999;
10383 case CA_SET_PLAYER_GRAVITY:
10385 for (i = 0; i < MAX_PLAYERS; i++)
10387 if (trigger_player_bits & (1 << i))
10389 stored_player[i].gravity =
10390 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10391 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10392 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10393 stored_player[i].gravity);
10400 case CA_SET_PLAYER_ARTWORK:
10402 for (i = 0; i < MAX_PLAYERS; i++)
10404 if (trigger_player_bits & (1 << i))
10406 int artwork_element = action_arg_element;
10408 if (action_arg == CA_ARG_ELEMENT_RESET)
10410 (level.use_artwork_element[i] ? level.artwork_element[i] :
10411 stored_player[i].element_nr);
10413 if (stored_player[i].artwork_element != artwork_element)
10414 stored_player[i].Frame = 0;
10416 stored_player[i].artwork_element = artwork_element;
10418 SetPlayerWaiting(&stored_player[i], FALSE);
10420 // set number of special actions for bored and sleeping animation
10421 stored_player[i].num_special_action_bored =
10422 get_num_special_action(artwork_element,
10423 ACTION_BORING_1, ACTION_BORING_LAST);
10424 stored_player[i].num_special_action_sleeping =
10425 get_num_special_action(artwork_element,
10426 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10433 case CA_SET_PLAYER_INVENTORY:
10435 for (i = 0; i < MAX_PLAYERS; i++)
10437 struct PlayerInfo *player = &stored_player[i];
10440 if (trigger_player_bits & (1 << i))
10442 int inventory_element = action_arg_element;
10444 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10445 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10446 action_arg == CA_ARG_ELEMENT_ACTION)
10448 int element = inventory_element;
10449 int collect_count = element_info[element].collect_count_initial;
10451 if (!IS_CUSTOM_ELEMENT(element))
10454 if (collect_count == 0)
10455 player->inventory_infinite_element = element;
10457 for (k = 0; k < collect_count; k++)
10458 if (player->inventory_size < MAX_INVENTORY_SIZE)
10459 player->inventory_element[player->inventory_size++] =
10462 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10463 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10464 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10466 if (player->inventory_infinite_element != EL_UNDEFINED &&
10467 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10468 action_arg_element_raw))
10469 player->inventory_infinite_element = EL_UNDEFINED;
10471 for (k = 0, j = 0; j < player->inventory_size; j++)
10473 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10474 action_arg_element_raw))
10475 player->inventory_element[k++] = player->inventory_element[j];
10478 player->inventory_size = k;
10480 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10482 if (player->inventory_size > 0)
10484 for (j = 0; j < player->inventory_size - 1; j++)
10485 player->inventory_element[j] = player->inventory_element[j + 1];
10487 player->inventory_size--;
10490 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10492 if (player->inventory_size > 0)
10493 player->inventory_size--;
10495 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10497 player->inventory_infinite_element = EL_UNDEFINED;
10498 player->inventory_size = 0;
10500 else if (action_arg == CA_ARG_INVENTORY_RESET)
10502 player->inventory_infinite_element = EL_UNDEFINED;
10503 player->inventory_size = 0;
10505 if (level.use_initial_inventory[i])
10507 for (j = 0; j < level.initial_inventory_size[i]; j++)
10509 int element = level.initial_inventory_content[i][j];
10510 int collect_count = element_info[element].collect_count_initial;
10512 if (!IS_CUSTOM_ELEMENT(element))
10515 if (collect_count == 0)
10516 player->inventory_infinite_element = element;
10518 for (k = 0; k < collect_count; k++)
10519 if (player->inventory_size < MAX_INVENTORY_SIZE)
10520 player->inventory_element[player->inventory_size++] =
10531 // ---------- CE actions -------------------------------------------------
10533 case CA_SET_CE_VALUE:
10535 int last_ce_value = CustomValue[x][y];
10537 CustomValue[x][y] = action_arg_number_new;
10539 if (CustomValue[x][y] != last_ce_value)
10541 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10542 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10544 if (CustomValue[x][y] == 0)
10546 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10547 ChangeCount[x][y] = 0; // allow at least one more change
10549 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10550 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10557 case CA_SET_CE_SCORE:
10559 int last_ce_score = ei->collect_score;
10561 ei->collect_score = action_arg_number_new;
10563 if (ei->collect_score != last_ce_score)
10565 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10566 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10568 if (ei->collect_score == 0)
10572 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10573 ChangeCount[x][y] = 0; // allow at least one more change
10575 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10576 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10579 This is a very special case that seems to be a mixture between
10580 CheckElementChange() and CheckTriggeredElementChange(): while
10581 the first one only affects single elements that are triggered
10582 directly, the second one affects multiple elements in the playfield
10583 that are triggered indirectly by another element. This is a third
10584 case: Changing the CE score always affects multiple identical CEs,
10585 so every affected CE must be checked, not only the single CE for
10586 which the CE score was changed in the first place (as every instance
10587 of that CE shares the same CE score, and therefore also can change)!
10589 SCAN_PLAYFIELD(xx, yy)
10591 if (Tile[xx][yy] == element)
10592 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10593 CE_SCORE_GETS_ZERO);
10601 case CA_SET_CE_ARTWORK:
10603 int artwork_element = action_arg_element;
10604 boolean reset_frame = FALSE;
10607 if (action_arg == CA_ARG_ELEMENT_RESET)
10608 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10611 if (ei->gfx_element != artwork_element)
10612 reset_frame = TRUE;
10614 ei->gfx_element = artwork_element;
10616 SCAN_PLAYFIELD(xx, yy)
10618 if (Tile[xx][yy] == element)
10622 ResetGfxAnimation(xx, yy);
10623 ResetRandomAnimationValue(xx, yy);
10626 TEST_DrawLevelField(xx, yy);
10633 // ---------- engine actions ---------------------------------------------
10635 case CA_SET_ENGINE_SCAN_MODE:
10637 InitPlayfieldScanMode(action_arg);
10647 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10649 int old_element = Tile[x][y];
10650 int new_element = GetElementFromGroupElement(element);
10651 int previous_move_direction = MovDir[x][y];
10652 int last_ce_value = CustomValue[x][y];
10653 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10654 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10655 boolean add_player_onto_element = (new_element_is_player &&
10656 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10657 IS_WALKABLE(old_element));
10659 if (!add_player_onto_element)
10661 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10662 RemoveMovingField(x, y);
10666 Tile[x][y] = new_element;
10668 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10669 MovDir[x][y] = previous_move_direction;
10671 if (element_info[new_element].use_last_ce_value)
10672 CustomValue[x][y] = last_ce_value;
10674 InitField_WithBug1(x, y, FALSE);
10676 new_element = Tile[x][y]; // element may have changed
10678 ResetGfxAnimation(x, y);
10679 ResetRandomAnimationValue(x, y);
10681 TEST_DrawLevelField(x, y);
10683 if (GFX_CRUMBLED(new_element))
10684 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10686 if (old_element == EL_EXPLOSION)
10688 Store[x][y] = Store2[x][y] = 0;
10690 // check if new element replaces an exploding player, requiring cleanup
10691 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
10692 StorePlayer[x][y] = 0;
10695 // check if element under the player changes from accessible to unaccessible
10696 // (needed for special case of dropping element which then changes)
10697 // (must be checked after creating new element for walkable group elements)
10698 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10699 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10701 KillPlayer(PLAYERINFO(x, y));
10707 // "ChangeCount" not set yet to allow "entered by player" change one time
10708 if (new_element_is_player)
10709 RelocatePlayer(x, y, new_element);
10712 ChangeCount[x][y]++; // count number of changes in the same frame
10714 TestIfBadThingTouchesPlayer(x, y);
10715 TestIfPlayerTouchesCustomElement(x, y);
10716 TestIfElementTouchesCustomElement(x, y);
10719 static void CreateField(int x, int y, int element)
10721 CreateFieldExt(x, y, element, FALSE);
10724 static void CreateElementFromChange(int x, int y, int element)
10726 element = GET_VALID_RUNTIME_ELEMENT(element);
10728 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10730 int old_element = Tile[x][y];
10732 // prevent changed element from moving in same engine frame
10733 // unless both old and new element can either fall or move
10734 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10735 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10739 CreateFieldExt(x, y, element, TRUE);
10742 static boolean ChangeElement(int x, int y, int element, int page)
10744 struct ElementInfo *ei = &element_info[element];
10745 struct ElementChangeInfo *change = &ei->change_page[page];
10746 int ce_value = CustomValue[x][y];
10747 int ce_score = ei->collect_score;
10748 int target_element;
10749 int old_element = Tile[x][y];
10751 // always use default change event to prevent running into a loop
10752 if (ChangeEvent[x][y] == -1)
10753 ChangeEvent[x][y] = CE_DELAY;
10755 if (ChangeEvent[x][y] == CE_DELAY)
10757 // reset actual trigger element, trigger player and action element
10758 change->actual_trigger_element = EL_EMPTY;
10759 change->actual_trigger_player = EL_EMPTY;
10760 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10761 change->actual_trigger_side = CH_SIDE_NONE;
10762 change->actual_trigger_ce_value = 0;
10763 change->actual_trigger_ce_score = 0;
10764 change->actual_trigger_x = -1;
10765 change->actual_trigger_y = -1;
10768 // do not change elements more than a specified maximum number of changes
10769 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10772 ChangeCount[x][y]++; // count number of changes in the same frame
10774 if (ei->has_anim_event)
10775 HandleGlobalAnimEventByElementChange(element, page, x, y,
10776 change->actual_trigger_x,
10777 change->actual_trigger_y);
10779 if (change->explode)
10786 if (change->use_target_content)
10788 boolean complete_replace = TRUE;
10789 boolean can_replace[3][3];
10792 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10795 boolean is_walkable;
10796 boolean is_diggable;
10797 boolean is_collectible;
10798 boolean is_removable;
10799 boolean is_destructible;
10800 int ex = x + xx - 1;
10801 int ey = y + yy - 1;
10802 int content_element = change->target_content.e[xx][yy];
10805 can_replace[xx][yy] = TRUE;
10807 if (ex == x && ey == y) // do not check changing element itself
10810 if (content_element == EL_EMPTY_SPACE)
10812 can_replace[xx][yy] = FALSE; // do not replace border with space
10817 if (!IN_LEV_FIELD(ex, ey))
10819 can_replace[xx][yy] = FALSE;
10820 complete_replace = FALSE;
10827 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10828 e = MovingOrBlocked2Element(ex, ey);
10830 is_empty = (IS_FREE(ex, ey) ||
10831 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10833 is_walkable = (is_empty || IS_WALKABLE(e));
10834 is_diggable = (is_empty || IS_DIGGABLE(e));
10835 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10836 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10837 is_removable = (is_diggable || is_collectible);
10839 can_replace[xx][yy] =
10840 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10841 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10842 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10843 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10844 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10845 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10846 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10848 if (!can_replace[xx][yy])
10849 complete_replace = FALSE;
10852 if (!change->only_if_complete || complete_replace)
10854 boolean something_has_changed = FALSE;
10856 if (change->only_if_complete && change->use_random_replace &&
10857 RND(100) < change->random_percentage)
10860 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10862 int ex = x + xx - 1;
10863 int ey = y + yy - 1;
10864 int content_element;
10866 if (can_replace[xx][yy] && (!change->use_random_replace ||
10867 RND(100) < change->random_percentage))
10869 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10870 RemoveMovingField(ex, ey);
10872 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10874 content_element = change->target_content.e[xx][yy];
10875 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10876 ce_value, ce_score);
10878 CreateElementFromChange(ex, ey, target_element);
10880 something_has_changed = TRUE;
10882 // for symmetry reasons, freeze newly created border elements
10883 if (ex != x || ey != y)
10884 Stop[ex][ey] = TRUE; // no more moving in this frame
10888 if (something_has_changed)
10890 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10891 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10897 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10898 ce_value, ce_score);
10900 if (element == EL_DIAGONAL_GROWING ||
10901 element == EL_DIAGONAL_SHRINKING)
10903 target_element = Store[x][y];
10905 Store[x][y] = EL_EMPTY;
10908 // special case: element changes to player (and may be kept if walkable)
10909 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10910 CreateElementFromChange(x, y, EL_EMPTY);
10912 CreateElementFromChange(x, y, target_element);
10914 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10915 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10918 // this uses direct change before indirect change
10919 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10924 static void HandleElementChange(int x, int y, int page)
10926 int element = MovingOrBlocked2Element(x, y);
10927 struct ElementInfo *ei = &element_info[element];
10928 struct ElementChangeInfo *change = &ei->change_page[page];
10929 boolean handle_action_before_change = FALSE;
10932 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10933 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10935 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10936 x, y, element, element_info[element].token_name);
10937 Debug("game:playing:HandleElementChange", "This should never happen!");
10941 // this can happen with classic bombs on walkable, changing elements
10942 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10947 if (ChangeDelay[x][y] == 0) // initialize element change
10949 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10951 if (change->can_change)
10953 // !!! not clear why graphic animation should be reset at all here !!!
10954 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10955 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10958 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10960 When using an animation frame delay of 1 (this only happens with
10961 "sp_zonk.moving.left/right" in the classic graphics), the default
10962 (non-moving) animation shows wrong animation frames (while the
10963 moving animation, like "sp_zonk.moving.left/right", is correct,
10964 so this graphical bug never shows up with the classic graphics).
10965 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10966 be drawn instead of the correct frames 0,1,2,3. This is caused by
10967 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10968 an element change: First when the change delay ("ChangeDelay[][]")
10969 counter has reached zero after decrementing, then a second time in
10970 the next frame (after "GfxFrame[][]" was already incremented) when
10971 "ChangeDelay[][]" is reset to the initial delay value again.
10973 This causes frame 0 to be drawn twice, while the last frame won't
10974 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10976 As some animations may already be cleverly designed around this bug
10977 (at least the "Snake Bite" snake tail animation does this), it cannot
10978 simply be fixed here without breaking such existing animations.
10979 Unfortunately, it cannot easily be detected if a graphics set was
10980 designed "before" or "after" the bug was fixed. As a workaround,
10981 a new graphics set option "game.graphics_engine_version" was added
10982 to be able to specify the game's major release version for which the
10983 graphics set was designed, which can then be used to decide if the
10984 bugfix should be used (version 4 and above) or not (version 3 or
10985 below, or if no version was specified at all, as with old sets).
10987 (The wrong/fixed animation frames can be tested with the test level set
10988 "test_gfxframe" and level "000", which contains a specially prepared
10989 custom element at level position (x/y) == (11/9) which uses the zonk
10990 animation mentioned above. Using "game.graphics_engine_version: 4"
10991 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10992 This can also be seen from the debug output for this test element.)
10995 // when a custom element is about to change (for example by change delay),
10996 // do not reset graphic animation when the custom element is moving
10997 if (game.graphics_engine_version < 4 &&
11000 ResetGfxAnimation(x, y);
11001 ResetRandomAnimationValue(x, y);
11004 if (change->pre_change_function)
11005 change->pre_change_function(x, y);
11009 ChangeDelay[x][y]--;
11011 if (ChangeDelay[x][y] != 0) // continue element change
11013 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11015 // also needed if CE can not change, but has CE delay with CE action
11016 if (IS_ANIMATED(graphic))
11017 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11019 if (change->can_change)
11021 if (change->change_function)
11022 change->change_function(x, y);
11025 else // finish element change
11027 if (ChangePage[x][y] != -1) // remember page from delayed change
11029 page = ChangePage[x][y];
11030 ChangePage[x][y] = -1;
11032 change = &ei->change_page[page];
11035 if (IS_MOVING(x, y)) // never change a running system ;-)
11037 ChangeDelay[x][y] = 1; // try change after next move step
11038 ChangePage[x][y] = page; // remember page to use for change
11043 // special case: set new level random seed before changing element
11044 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11045 handle_action_before_change = TRUE;
11047 if (change->has_action && handle_action_before_change)
11048 ExecuteCustomElementAction(x, y, element, page);
11050 if (change->can_change)
11052 if (ChangeElement(x, y, element, page))
11054 if (change->post_change_function)
11055 change->post_change_function(x, y);
11059 if (change->has_action && !handle_action_before_change)
11060 ExecuteCustomElementAction(x, y, element, page);
11064 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11065 int trigger_element,
11067 int trigger_player,
11071 boolean change_done_any = FALSE;
11072 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11075 if (!(trigger_events[trigger_element][trigger_event]))
11078 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11080 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11082 int element = EL_CUSTOM_START + i;
11083 boolean change_done = FALSE;
11086 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11087 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11090 for (p = 0; p < element_info[element].num_change_pages; p++)
11092 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11094 if (change->can_change_or_has_action &&
11095 change->has_event[trigger_event] &&
11096 change->trigger_side & trigger_side &&
11097 change->trigger_player & trigger_player &&
11098 change->trigger_page & trigger_page_bits &&
11099 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11101 change->actual_trigger_element = trigger_element;
11102 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11103 change->actual_trigger_player_bits = trigger_player;
11104 change->actual_trigger_side = trigger_side;
11105 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11106 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11107 change->actual_trigger_x = trigger_x;
11108 change->actual_trigger_y = trigger_y;
11110 if ((change->can_change && !change_done) || change->has_action)
11114 SCAN_PLAYFIELD(x, y)
11116 if (Tile[x][y] == element)
11118 if (change->can_change && !change_done)
11120 // if element already changed in this frame, not only prevent
11121 // another element change (checked in ChangeElement()), but
11122 // also prevent additional element actions for this element
11124 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11125 !level.use_action_after_change_bug)
11128 ChangeDelay[x][y] = 1;
11129 ChangeEvent[x][y] = trigger_event;
11131 HandleElementChange(x, y, p);
11133 else if (change->has_action)
11135 // if element already changed in this frame, not only prevent
11136 // another element change (checked in ChangeElement()), but
11137 // also prevent additional element actions for this element
11139 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11140 !level.use_action_after_change_bug)
11143 ExecuteCustomElementAction(x, y, element, p);
11144 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11149 if (change->can_change)
11151 change_done = TRUE;
11152 change_done_any = TRUE;
11159 RECURSION_LOOP_DETECTION_END();
11161 return change_done_any;
11164 static boolean CheckElementChangeExt(int x, int y,
11166 int trigger_element,
11168 int trigger_player,
11171 boolean change_done = FALSE;
11174 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11175 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11178 if (Tile[x][y] == EL_BLOCKED)
11180 Blocked2Moving(x, y, &x, &y);
11181 element = Tile[x][y];
11184 // check if element has already changed or is about to change after moving
11185 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11186 Tile[x][y] != element) ||
11188 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11189 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11190 ChangePage[x][y] != -1)))
11193 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11195 for (p = 0; p < element_info[element].num_change_pages; p++)
11197 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11199 /* check trigger element for all events where the element that is checked
11200 for changing interacts with a directly adjacent element -- this is
11201 different to element changes that affect other elements to change on the
11202 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11203 boolean check_trigger_element =
11204 (trigger_event == CE_NEXT_TO_X ||
11205 trigger_event == CE_TOUCHING_X ||
11206 trigger_event == CE_HITTING_X ||
11207 trigger_event == CE_HIT_BY_X ||
11208 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11210 if (change->can_change_or_has_action &&
11211 change->has_event[trigger_event] &&
11212 change->trigger_side & trigger_side &&
11213 change->trigger_player & trigger_player &&
11214 (!check_trigger_element ||
11215 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11217 change->actual_trigger_element = trigger_element;
11218 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11219 change->actual_trigger_player_bits = trigger_player;
11220 change->actual_trigger_side = trigger_side;
11221 change->actual_trigger_ce_value = CustomValue[x][y];
11222 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11223 change->actual_trigger_x = x;
11224 change->actual_trigger_y = y;
11226 // special case: trigger element not at (x,y) position for some events
11227 if (check_trigger_element)
11239 { 0, 0 }, { 0, 0 }, { 0, 0 },
11243 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11244 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11246 change->actual_trigger_ce_value = CustomValue[xx][yy];
11247 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11248 change->actual_trigger_x = xx;
11249 change->actual_trigger_y = yy;
11252 if (change->can_change && !change_done)
11254 ChangeDelay[x][y] = 1;
11255 ChangeEvent[x][y] = trigger_event;
11257 HandleElementChange(x, y, p);
11259 change_done = TRUE;
11261 else if (change->has_action)
11263 ExecuteCustomElementAction(x, y, element, p);
11264 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11269 RECURSION_LOOP_DETECTION_END();
11271 return change_done;
11274 static void PlayPlayerSound(struct PlayerInfo *player)
11276 int jx = player->jx, jy = player->jy;
11277 int sound_element = player->artwork_element;
11278 int last_action = player->last_action_waiting;
11279 int action = player->action_waiting;
11281 if (player->is_waiting)
11283 if (action != last_action)
11284 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11286 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11290 if (action != last_action)
11291 StopSound(element_info[sound_element].sound[last_action]);
11293 if (last_action == ACTION_SLEEPING)
11294 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11298 static void PlayAllPlayersSound(void)
11302 for (i = 0; i < MAX_PLAYERS; i++)
11303 if (stored_player[i].active)
11304 PlayPlayerSound(&stored_player[i]);
11307 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11309 boolean last_waiting = player->is_waiting;
11310 int move_dir = player->MovDir;
11312 player->dir_waiting = move_dir;
11313 player->last_action_waiting = player->action_waiting;
11317 if (!last_waiting) // not waiting -> waiting
11319 player->is_waiting = TRUE;
11321 player->frame_counter_bored =
11323 game.player_boring_delay_fixed +
11324 GetSimpleRandom(game.player_boring_delay_random);
11325 player->frame_counter_sleeping =
11327 game.player_sleeping_delay_fixed +
11328 GetSimpleRandom(game.player_sleeping_delay_random);
11330 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11333 if (game.player_sleeping_delay_fixed +
11334 game.player_sleeping_delay_random > 0 &&
11335 player->anim_delay_counter == 0 &&
11336 player->post_delay_counter == 0 &&
11337 FrameCounter >= player->frame_counter_sleeping)
11338 player->is_sleeping = TRUE;
11339 else if (game.player_boring_delay_fixed +
11340 game.player_boring_delay_random > 0 &&
11341 FrameCounter >= player->frame_counter_bored)
11342 player->is_bored = TRUE;
11344 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11345 player->is_bored ? ACTION_BORING :
11348 if (player->is_sleeping && player->use_murphy)
11350 // special case for sleeping Murphy when leaning against non-free tile
11352 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11353 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11354 !IS_MOVING(player->jx - 1, player->jy)))
11355 move_dir = MV_LEFT;
11356 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11357 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11358 !IS_MOVING(player->jx + 1, player->jy)))
11359 move_dir = MV_RIGHT;
11361 player->is_sleeping = FALSE;
11363 player->dir_waiting = move_dir;
11366 if (player->is_sleeping)
11368 if (player->num_special_action_sleeping > 0)
11370 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11372 int last_special_action = player->special_action_sleeping;
11373 int num_special_action = player->num_special_action_sleeping;
11374 int special_action =
11375 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11376 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11377 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11378 last_special_action + 1 : ACTION_SLEEPING);
11379 int special_graphic =
11380 el_act_dir2img(player->artwork_element, special_action, move_dir);
11382 player->anim_delay_counter =
11383 graphic_info[special_graphic].anim_delay_fixed +
11384 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11385 player->post_delay_counter =
11386 graphic_info[special_graphic].post_delay_fixed +
11387 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11389 player->special_action_sleeping = special_action;
11392 if (player->anim_delay_counter > 0)
11394 player->action_waiting = player->special_action_sleeping;
11395 player->anim_delay_counter--;
11397 else if (player->post_delay_counter > 0)
11399 player->post_delay_counter--;
11403 else if (player->is_bored)
11405 if (player->num_special_action_bored > 0)
11407 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11409 int special_action =
11410 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11411 int special_graphic =
11412 el_act_dir2img(player->artwork_element, special_action, move_dir);
11414 player->anim_delay_counter =
11415 graphic_info[special_graphic].anim_delay_fixed +
11416 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11417 player->post_delay_counter =
11418 graphic_info[special_graphic].post_delay_fixed +
11419 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11421 player->special_action_bored = special_action;
11424 if (player->anim_delay_counter > 0)
11426 player->action_waiting = player->special_action_bored;
11427 player->anim_delay_counter--;
11429 else if (player->post_delay_counter > 0)
11431 player->post_delay_counter--;
11436 else if (last_waiting) // waiting -> not waiting
11438 player->is_waiting = FALSE;
11439 player->is_bored = FALSE;
11440 player->is_sleeping = FALSE;
11442 player->frame_counter_bored = -1;
11443 player->frame_counter_sleeping = -1;
11445 player->anim_delay_counter = 0;
11446 player->post_delay_counter = 0;
11448 player->dir_waiting = player->MovDir;
11449 player->action_waiting = ACTION_DEFAULT;
11451 player->special_action_bored = ACTION_DEFAULT;
11452 player->special_action_sleeping = ACTION_DEFAULT;
11456 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11458 if ((!player->is_moving && player->was_moving) ||
11459 (player->MovPos == 0 && player->was_moving) ||
11460 (player->is_snapping && !player->was_snapping) ||
11461 (player->is_dropping && !player->was_dropping))
11463 if (!CheckSaveEngineSnapshotToList())
11466 player->was_moving = FALSE;
11467 player->was_snapping = TRUE;
11468 player->was_dropping = TRUE;
11472 if (player->is_moving)
11473 player->was_moving = TRUE;
11475 if (!player->is_snapping)
11476 player->was_snapping = FALSE;
11478 if (!player->is_dropping)
11479 player->was_dropping = FALSE;
11482 static struct MouseActionInfo mouse_action_last = { 0 };
11483 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11484 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11487 CheckSaveEngineSnapshotToList();
11489 mouse_action_last = mouse_action;
11492 static void CheckSingleStepMode(struct PlayerInfo *player)
11494 if (tape.single_step && tape.recording && !tape.pausing)
11496 // as it is called "single step mode", just return to pause mode when the
11497 // player stopped moving after one tile (or never starts moving at all)
11498 // (reverse logic needed here in case single step mode used in team mode)
11499 if (player->is_moving ||
11500 player->is_pushing ||
11501 player->is_dropping_pressed ||
11502 player->effective_mouse_action.button)
11503 game.enter_single_step_mode = FALSE;
11506 CheckSaveEngineSnapshot(player);
11509 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11511 int left = player_action & JOY_LEFT;
11512 int right = player_action & JOY_RIGHT;
11513 int up = player_action & JOY_UP;
11514 int down = player_action & JOY_DOWN;
11515 int button1 = player_action & JOY_BUTTON_1;
11516 int button2 = player_action & JOY_BUTTON_2;
11517 int dx = (left ? -1 : right ? 1 : 0);
11518 int dy = (up ? -1 : down ? 1 : 0);
11520 if (!player->active || tape.pausing)
11526 SnapField(player, dx, dy);
11530 DropElement(player);
11532 MovePlayer(player, dx, dy);
11535 CheckSingleStepMode(player);
11537 SetPlayerWaiting(player, FALSE);
11539 return player_action;
11543 // no actions for this player (no input at player's configured device)
11545 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11546 SnapField(player, 0, 0);
11547 CheckGravityMovementWhenNotMoving(player);
11549 if (player->MovPos == 0)
11550 SetPlayerWaiting(player, TRUE);
11552 if (player->MovPos == 0) // needed for tape.playing
11553 player->is_moving = FALSE;
11555 player->is_dropping = FALSE;
11556 player->is_dropping_pressed = FALSE;
11557 player->drop_pressed_delay = 0;
11559 CheckSingleStepMode(player);
11565 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11568 if (!tape.use_mouse_actions)
11571 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11572 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11573 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11576 static void SetTapeActionFromMouseAction(byte *tape_action,
11577 struct MouseActionInfo *mouse_action)
11579 if (!tape.use_mouse_actions)
11582 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11583 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11584 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11587 static void CheckLevelSolved(void)
11589 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11591 if (game_em.level_solved &&
11592 !game_em.game_over) // game won
11596 game_em.game_over = TRUE;
11598 game.all_players_gone = TRUE;
11601 if (game_em.game_over) // game lost
11602 game.all_players_gone = TRUE;
11604 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11606 if (game_sp.level_solved &&
11607 !game_sp.game_over) // game won
11611 game_sp.game_over = TRUE;
11613 game.all_players_gone = TRUE;
11616 if (game_sp.game_over) // game lost
11617 game.all_players_gone = TRUE;
11619 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11621 if (game_mm.level_solved &&
11622 !game_mm.game_over) // game won
11626 game_mm.game_over = TRUE;
11628 game.all_players_gone = TRUE;
11631 if (game_mm.game_over) // game lost
11632 game.all_players_gone = TRUE;
11636 static void CheckLevelTime_StepCounter(void)
11646 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11647 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11649 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11651 DisplayGameControlValues();
11653 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11654 for (i = 0; i < MAX_PLAYERS; i++)
11655 KillPlayer(&stored_player[i]);
11657 else if (game.no_level_time_limit && !game.all_players_gone)
11659 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11661 DisplayGameControlValues();
11665 static void CheckLevelTime(void)
11669 if (TimeFrames >= FRAMES_PER_SECOND)
11674 for (i = 0; i < MAX_PLAYERS; i++)
11676 struct PlayerInfo *player = &stored_player[i];
11678 if (SHIELD_ON(player))
11680 player->shield_normal_time_left--;
11682 if (player->shield_deadly_time_left > 0)
11683 player->shield_deadly_time_left--;
11687 if (!game.LevelSolved && !level.use_step_counter)
11695 if (TimeLeft <= 10 && game.time_limit)
11696 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11698 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11699 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11701 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11703 if (!TimeLeft && game.time_limit)
11705 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11706 game_em.lev->killed_out_of_time = TRUE;
11708 for (i = 0; i < MAX_PLAYERS; i++)
11709 KillPlayer(&stored_player[i]);
11712 else if (game.no_level_time_limit && !game.all_players_gone)
11714 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11717 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11720 if (tape.recording || tape.playing)
11721 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11724 if (tape.recording || tape.playing)
11725 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11727 UpdateAndDisplayGameControlValues();
11730 void AdvanceFrameAndPlayerCounters(int player_nr)
11734 // advance frame counters (global frame counter and time frame counter)
11738 // advance player counters (counters for move delay, move animation etc.)
11739 for (i = 0; i < MAX_PLAYERS; i++)
11741 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11742 int move_delay_value = stored_player[i].move_delay_value;
11743 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11745 if (!advance_player_counters) // not all players may be affected
11748 if (move_frames == 0) // less than one move per game frame
11750 int stepsize = TILEX / move_delay_value;
11751 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11752 int count = (stored_player[i].is_moving ?
11753 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11755 if (count % delay == 0)
11759 stored_player[i].Frame += move_frames;
11761 if (stored_player[i].MovPos != 0)
11762 stored_player[i].StepFrame += move_frames;
11764 if (stored_player[i].move_delay > 0)
11765 stored_player[i].move_delay--;
11767 // due to bugs in previous versions, counter must count up, not down
11768 if (stored_player[i].push_delay != -1)
11769 stored_player[i].push_delay++;
11771 if (stored_player[i].drop_delay > 0)
11772 stored_player[i].drop_delay--;
11774 if (stored_player[i].is_dropping_pressed)
11775 stored_player[i].drop_pressed_delay++;
11779 void AdvanceFrameCounter(void)
11784 void AdvanceGfxFrame(void)
11788 SCAN_PLAYFIELD(x, y)
11794 static void HandleMouseAction(struct MouseActionInfo *mouse_action,
11795 struct MouseActionInfo *mouse_action_last)
11797 if (mouse_action->button)
11799 int new_button = (mouse_action->button && mouse_action_last->button == 0);
11800 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button);
11801 int x = mouse_action->lx;
11802 int y = mouse_action->ly;
11803 int element = Tile[x][y];
11807 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
11808 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
11812 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
11813 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
11816 if (level.use_step_counter)
11818 boolean counted_click = FALSE;
11820 // element clicked that can change when clicked/pressed
11821 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
11822 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
11823 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
11824 counted_click = TRUE;
11826 // element clicked that can trigger change when clicked/pressed
11827 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
11828 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
11829 counted_click = TRUE;
11831 if (new_button && counted_click)
11832 CheckLevelTime_StepCounter();
11837 void StartGameActions(boolean init_network_game, boolean record_tape,
11840 unsigned int new_random_seed = InitRND(random_seed);
11843 TapeStartRecording(new_random_seed);
11845 if (setup.auto_pause_on_start && !tape.pausing)
11846 TapeTogglePause(TAPE_TOGGLE_MANUAL);
11848 if (init_network_game)
11850 SendToServer_LevelFile();
11851 SendToServer_StartPlaying();
11859 static void GameActionsExt(void)
11862 static unsigned int game_frame_delay = 0;
11864 unsigned int game_frame_delay_value;
11865 byte *recorded_player_action;
11866 byte summarized_player_action = 0;
11867 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11870 // detect endless loops, caused by custom element programming
11871 if (recursion_loop_detected && recursion_loop_depth == 0)
11873 char *message = getStringCat3("Internal Error! Element ",
11874 EL_NAME(recursion_loop_element),
11875 " caused endless loop! Quit the game?");
11877 Warn("element '%s' caused endless loop in game engine",
11878 EL_NAME(recursion_loop_element));
11880 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11882 recursion_loop_detected = FALSE; // if game should be continued
11889 if (game.restart_level)
11890 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11892 CheckLevelSolved();
11894 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11897 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11900 if (game_status != GAME_MODE_PLAYING) // status might have changed
11903 game_frame_delay_value =
11904 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11906 if (tape.playing && tape.warp_forward && !tape.pausing)
11907 game_frame_delay_value = 0;
11909 SetVideoFrameDelay(game_frame_delay_value);
11911 // (de)activate virtual buttons depending on current game status
11912 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11914 if (game.all_players_gone) // if no players there to be controlled anymore
11915 SetOverlayActive(FALSE);
11916 else if (!tape.playing) // if game continues after tape stopped playing
11917 SetOverlayActive(TRUE);
11922 // ---------- main game synchronization point ----------
11924 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11926 Debug("game:playing:skip", "skip == %d", skip);
11929 // ---------- main game synchronization point ----------
11931 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11935 if (network_playing && !network_player_action_received)
11937 // try to get network player actions in time
11939 // last chance to get network player actions without main loop delay
11940 HandleNetworking();
11942 // game was quit by network peer
11943 if (game_status != GAME_MODE_PLAYING)
11946 // check if network player actions still missing and game still running
11947 if (!network_player_action_received && !checkGameEnded())
11948 return; // failed to get network player actions in time
11950 // do not yet reset "network_player_action_received" (for tape.pausing)
11956 // at this point we know that we really continue executing the game
11958 network_player_action_received = FALSE;
11960 // when playing tape, read previously recorded player input from tape data
11961 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11963 local_player->effective_mouse_action = local_player->mouse_action;
11965 if (recorded_player_action != NULL)
11966 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11967 recorded_player_action);
11969 // TapePlayAction() may return NULL when toggling to "pause before death"
11973 if (tape.set_centered_player)
11975 game.centered_player_nr_next = tape.centered_player_nr_next;
11976 game.set_centered_player = TRUE;
11979 for (i = 0; i < MAX_PLAYERS; i++)
11981 summarized_player_action |= stored_player[i].action;
11983 if (!network_playing && (game.team_mode || tape.playing))
11984 stored_player[i].effective_action = stored_player[i].action;
11987 if (network_playing && !checkGameEnded())
11988 SendToServer_MovePlayer(summarized_player_action);
11990 // summarize all actions at local players mapped input device position
11991 // (this allows using different input devices in single player mode)
11992 if (!network.enabled && !game.team_mode)
11993 stored_player[map_player_action[local_player->index_nr]].effective_action =
11994 summarized_player_action;
11996 // summarize all actions at centered player in local team mode
11997 if (tape.recording &&
11998 setup.team_mode && !network.enabled &&
11999 setup.input_on_focus &&
12000 game.centered_player_nr != -1)
12002 for (i = 0; i < MAX_PLAYERS; i++)
12003 stored_player[map_player_action[i]].effective_action =
12004 (i == game.centered_player_nr ? summarized_player_action : 0);
12007 if (recorded_player_action != NULL)
12008 for (i = 0; i < MAX_PLAYERS; i++)
12009 stored_player[i].effective_action = recorded_player_action[i];
12011 for (i = 0; i < MAX_PLAYERS; i++)
12013 tape_action[i] = stored_player[i].effective_action;
12015 /* (this may happen in the RND game engine if a player was not present on
12016 the playfield on level start, but appeared later from a custom element */
12017 if (setup.team_mode &&
12020 !tape.player_participates[i])
12021 tape.player_participates[i] = TRUE;
12024 SetTapeActionFromMouseAction(tape_action,
12025 &local_player->effective_mouse_action);
12027 // only record actions from input devices, but not programmed actions
12028 if (tape.recording)
12029 TapeRecordAction(tape_action);
12031 // remember if game was played (especially after tape stopped playing)
12032 if (!tape.playing && summarized_player_action && !checkGameFailed())
12033 game.GamePlayed = TRUE;
12035 #if USE_NEW_PLAYER_ASSIGNMENTS
12036 // !!! also map player actions in single player mode !!!
12037 // if (game.team_mode)
12040 byte mapped_action[MAX_PLAYERS];
12042 #if DEBUG_PLAYER_ACTIONS
12043 for (i = 0; i < MAX_PLAYERS; i++)
12044 DebugContinued("", "%d, ", stored_player[i].effective_action);
12047 for (i = 0; i < MAX_PLAYERS; i++)
12048 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
12050 for (i = 0; i < MAX_PLAYERS; i++)
12051 stored_player[i].effective_action = mapped_action[i];
12053 #if DEBUG_PLAYER_ACTIONS
12054 DebugContinued("", "=> ");
12055 for (i = 0; i < MAX_PLAYERS; i++)
12056 DebugContinued("", "%d, ", stored_player[i].effective_action);
12057 DebugContinued("game:playing:player", "\n");
12060 #if DEBUG_PLAYER_ACTIONS
12063 for (i = 0; i < MAX_PLAYERS; i++)
12064 DebugContinued("", "%d, ", stored_player[i].effective_action);
12065 DebugContinued("game:playing:player", "\n");
12070 for (i = 0; i < MAX_PLAYERS; i++)
12072 // allow engine snapshot in case of changed movement attempt
12073 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12074 (stored_player[i].effective_action & KEY_MOTION))
12075 game.snapshot.changed_action = TRUE;
12077 // allow engine snapshot in case of snapping/dropping attempt
12078 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12079 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12080 game.snapshot.changed_action = TRUE;
12082 game.snapshot.last_action[i] = stored_player[i].effective_action;
12085 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12087 GameActions_EM_Main();
12089 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12091 GameActions_SP_Main();
12093 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12095 GameActions_MM_Main();
12099 GameActions_RND_Main();
12102 BlitScreenToBitmap(backbuffer);
12104 CheckLevelSolved();
12107 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12109 if (global.show_frames_per_second)
12111 static unsigned int fps_counter = 0;
12112 static int fps_frames = 0;
12113 unsigned int fps_delay_ms = Counter() - fps_counter;
12117 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12119 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12122 fps_counter = Counter();
12124 // always draw FPS to screen after FPS value was updated
12125 redraw_mask |= REDRAW_FPS;
12128 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12129 if (GetDrawDeactivationMask() == REDRAW_NONE)
12130 redraw_mask |= REDRAW_FPS;
12134 static void GameActions_CheckSaveEngineSnapshot(void)
12136 if (!game.snapshot.save_snapshot)
12139 // clear flag for saving snapshot _before_ saving snapshot
12140 game.snapshot.save_snapshot = FALSE;
12142 SaveEngineSnapshotToList();
12145 void GameActions(void)
12149 GameActions_CheckSaveEngineSnapshot();
12152 void GameActions_EM_Main(void)
12154 byte effective_action[MAX_PLAYERS];
12157 for (i = 0; i < MAX_PLAYERS; i++)
12158 effective_action[i] = stored_player[i].effective_action;
12160 GameActions_EM(effective_action);
12163 void GameActions_SP_Main(void)
12165 byte effective_action[MAX_PLAYERS];
12168 for (i = 0; i < MAX_PLAYERS; i++)
12169 effective_action[i] = stored_player[i].effective_action;
12171 GameActions_SP(effective_action);
12173 for (i = 0; i < MAX_PLAYERS; i++)
12175 if (stored_player[i].force_dropping)
12176 stored_player[i].action |= KEY_BUTTON_DROP;
12178 stored_player[i].force_dropping = FALSE;
12182 void GameActions_MM_Main(void)
12186 GameActions_MM(local_player->effective_mouse_action);
12189 void GameActions_RND_Main(void)
12194 void GameActions_RND(void)
12196 static struct MouseActionInfo mouse_action_last = { 0 };
12197 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12198 int magic_wall_x = 0, magic_wall_y = 0;
12199 int i, x, y, element, graphic, last_gfx_frame;
12201 InitPlayfieldScanModeVars();
12203 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12205 SCAN_PLAYFIELD(x, y)
12207 ChangeCount[x][y] = 0;
12208 ChangeEvent[x][y] = -1;
12212 if (game.set_centered_player)
12214 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12216 // switching to "all players" only possible if all players fit to screen
12217 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12219 game.centered_player_nr_next = game.centered_player_nr;
12220 game.set_centered_player = FALSE;
12223 // do not switch focus to non-existing (or non-active) player
12224 if (game.centered_player_nr_next >= 0 &&
12225 !stored_player[game.centered_player_nr_next].active)
12227 game.centered_player_nr_next = game.centered_player_nr;
12228 game.set_centered_player = FALSE;
12232 if (game.set_centered_player &&
12233 ScreenMovPos == 0) // screen currently aligned at tile position
12237 if (game.centered_player_nr_next == -1)
12239 setScreenCenteredToAllPlayers(&sx, &sy);
12243 sx = stored_player[game.centered_player_nr_next].jx;
12244 sy = stored_player[game.centered_player_nr_next].jy;
12247 game.centered_player_nr = game.centered_player_nr_next;
12248 game.set_centered_player = FALSE;
12250 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12251 DrawGameDoorValues();
12254 // check single step mode (set flag and clear again if any player is active)
12255 game.enter_single_step_mode =
12256 (tape.single_step && tape.recording && !tape.pausing);
12258 for (i = 0; i < MAX_PLAYERS; i++)
12260 int actual_player_action = stored_player[i].effective_action;
12263 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12264 - rnd_equinox_tetrachloride 048
12265 - rnd_equinox_tetrachloride_ii 096
12266 - rnd_emanuel_schmieg 002
12267 - doctor_sloan_ww 001, 020
12269 if (stored_player[i].MovPos == 0)
12270 CheckGravityMovement(&stored_player[i]);
12273 // overwrite programmed action with tape action
12274 if (stored_player[i].programmed_action)
12275 actual_player_action = stored_player[i].programmed_action;
12277 PlayerActions(&stored_player[i], actual_player_action);
12279 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12282 // single step pause mode may already have been toggled by "ScrollPlayer()"
12283 if (game.enter_single_step_mode && !tape.pausing)
12284 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12286 ScrollScreen(NULL, SCROLL_GO_ON);
12288 /* for backwards compatibility, the following code emulates a fixed bug that
12289 occured when pushing elements (causing elements that just made their last
12290 pushing step to already (if possible) make their first falling step in the
12291 same game frame, which is bad); this code is also needed to use the famous
12292 "spring push bug" which is used in older levels and might be wanted to be
12293 used also in newer levels, but in this case the buggy pushing code is only
12294 affecting the "spring" element and no other elements */
12296 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12298 for (i = 0; i < MAX_PLAYERS; i++)
12300 struct PlayerInfo *player = &stored_player[i];
12301 int x = player->jx;
12302 int y = player->jy;
12304 if (player->active && player->is_pushing && player->is_moving &&
12306 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12307 Tile[x][y] == EL_SPRING))
12309 ContinueMoving(x, y);
12311 // continue moving after pushing (this is actually a bug)
12312 if (!IS_MOVING(x, y))
12313 Stop[x][y] = FALSE;
12318 SCAN_PLAYFIELD(x, y)
12320 Last[x][y] = Tile[x][y];
12322 ChangeCount[x][y] = 0;
12323 ChangeEvent[x][y] = -1;
12325 // this must be handled before main playfield loop
12326 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12329 if (MovDelay[x][y] <= 0)
12333 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12336 if (MovDelay[x][y] <= 0)
12338 int element = Store[x][y];
12339 int move_direction = MovDir[x][y];
12340 int player_index_bit = Store2[x][y];
12346 TEST_DrawLevelField(x, y);
12348 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12350 if (IS_ENVELOPE(element))
12351 local_player->show_envelope = element;
12356 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12358 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12360 Debug("game:playing:GameActions_RND", "This should never happen!");
12362 ChangePage[x][y] = -1;
12366 Stop[x][y] = FALSE;
12367 if (WasJustMoving[x][y] > 0)
12368 WasJustMoving[x][y]--;
12369 if (WasJustFalling[x][y] > 0)
12370 WasJustFalling[x][y]--;
12371 if (CheckCollision[x][y] > 0)
12372 CheckCollision[x][y]--;
12373 if (CheckImpact[x][y] > 0)
12374 CheckImpact[x][y]--;
12378 /* reset finished pushing action (not done in ContinueMoving() to allow
12379 continuous pushing animation for elements with zero push delay) */
12380 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12382 ResetGfxAnimation(x, y);
12383 TEST_DrawLevelField(x, y);
12387 if (IS_BLOCKED(x, y))
12391 Blocked2Moving(x, y, &oldx, &oldy);
12392 if (!IS_MOVING(oldx, oldy))
12394 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12395 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12396 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12397 Debug("game:playing:GameActions_RND", "This should never happen!");
12403 HandleMouseAction(&mouse_action, &mouse_action_last);
12405 SCAN_PLAYFIELD(x, y)
12407 element = Tile[x][y];
12408 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12409 last_gfx_frame = GfxFrame[x][y];
12411 if (element == EL_EMPTY)
12412 graphic = el2img(GfxElementEmpty[x][y]);
12414 ResetGfxFrame(x, y);
12416 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12417 DrawLevelGraphicAnimation(x, y, graphic);
12419 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12420 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12421 ResetRandomAnimationValue(x, y);
12423 SetRandomAnimationValue(x, y);
12425 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12427 if (IS_INACTIVE(element))
12429 if (IS_ANIMATED(graphic))
12430 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12435 // this may take place after moving, so 'element' may have changed
12436 if (IS_CHANGING(x, y) &&
12437 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12439 int page = element_info[element].event_page_nr[CE_DELAY];
12441 HandleElementChange(x, y, page);
12443 element = Tile[x][y];
12444 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12447 CheckNextToConditions(x, y);
12449 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12453 element = Tile[x][y];
12454 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12456 if (IS_ANIMATED(graphic) &&
12457 !IS_MOVING(x, y) &&
12459 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12461 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12462 TEST_DrawTwinkleOnField(x, y);
12464 else if (element == EL_ACID)
12467 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12469 else if ((element == EL_EXIT_OPEN ||
12470 element == EL_EM_EXIT_OPEN ||
12471 element == EL_SP_EXIT_OPEN ||
12472 element == EL_STEEL_EXIT_OPEN ||
12473 element == EL_EM_STEEL_EXIT_OPEN ||
12474 element == EL_SP_TERMINAL ||
12475 element == EL_SP_TERMINAL_ACTIVE ||
12476 element == EL_EXTRA_TIME ||
12477 element == EL_SHIELD_NORMAL ||
12478 element == EL_SHIELD_DEADLY) &&
12479 IS_ANIMATED(graphic))
12480 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12481 else if (IS_MOVING(x, y))
12482 ContinueMoving(x, y);
12483 else if (IS_ACTIVE_BOMB(element))
12484 CheckDynamite(x, y);
12485 else if (element == EL_AMOEBA_GROWING)
12486 AmoebaGrowing(x, y);
12487 else if (element == EL_AMOEBA_SHRINKING)
12488 AmoebaShrinking(x, y);
12490 #if !USE_NEW_AMOEBA_CODE
12491 else if (IS_AMOEBALIVE(element))
12492 AmoebaReproduce(x, y);
12495 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12497 else if (element == EL_EXIT_CLOSED)
12499 else if (element == EL_EM_EXIT_CLOSED)
12501 else if (element == EL_STEEL_EXIT_CLOSED)
12502 CheckExitSteel(x, y);
12503 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12504 CheckExitSteelEM(x, y);
12505 else if (element == EL_SP_EXIT_CLOSED)
12507 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12508 element == EL_EXPANDABLE_STEELWALL_GROWING)
12510 else if (element == EL_EXPANDABLE_WALL ||
12511 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12512 element == EL_EXPANDABLE_WALL_VERTICAL ||
12513 element == EL_EXPANDABLE_WALL_ANY ||
12514 element == EL_BD_EXPANDABLE_WALL ||
12515 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12516 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12517 element == EL_EXPANDABLE_STEELWALL_ANY)
12518 CheckWallGrowing(x, y);
12519 else if (element == EL_FLAMES)
12520 CheckForDragon(x, y);
12521 else if (element == EL_EXPLOSION)
12522 ; // drawing of correct explosion animation is handled separately
12523 else if (element == EL_ELEMENT_SNAPPING ||
12524 element == EL_DIAGONAL_SHRINKING ||
12525 element == EL_DIAGONAL_GROWING)
12527 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]);
12529 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12531 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12532 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12534 if (IS_BELT_ACTIVE(element))
12535 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12537 if (game.magic_wall_active)
12539 int jx = local_player->jx, jy = local_player->jy;
12541 // play the element sound at the position nearest to the player
12542 if ((element == EL_MAGIC_WALL_FULL ||
12543 element == EL_MAGIC_WALL_ACTIVE ||
12544 element == EL_MAGIC_WALL_EMPTYING ||
12545 element == EL_BD_MAGIC_WALL_FULL ||
12546 element == EL_BD_MAGIC_WALL_ACTIVE ||
12547 element == EL_BD_MAGIC_WALL_EMPTYING ||
12548 element == EL_DC_MAGIC_WALL_FULL ||
12549 element == EL_DC_MAGIC_WALL_ACTIVE ||
12550 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12551 ABS(x - jx) + ABS(y - jy) <
12552 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12560 #if USE_NEW_AMOEBA_CODE
12561 // new experimental amoeba growth stuff
12562 if (!(FrameCounter % 8))
12564 static unsigned int random = 1684108901;
12566 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12568 x = RND(lev_fieldx);
12569 y = RND(lev_fieldy);
12570 element = Tile[x][y];
12572 if (!IS_PLAYER(x, y) &&
12573 (element == EL_EMPTY ||
12574 CAN_GROW_INTO(element) ||
12575 element == EL_QUICKSAND_EMPTY ||
12576 element == EL_QUICKSAND_FAST_EMPTY ||
12577 element == EL_ACID_SPLASH_LEFT ||
12578 element == EL_ACID_SPLASH_RIGHT))
12580 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12581 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12582 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12583 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12584 Tile[x][y] = EL_AMOEBA_DROP;
12587 random = random * 129 + 1;
12592 game.explosions_delayed = FALSE;
12594 SCAN_PLAYFIELD(x, y)
12596 element = Tile[x][y];
12598 if (ExplodeField[x][y])
12599 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12600 else if (element == EL_EXPLOSION)
12601 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12603 ExplodeField[x][y] = EX_TYPE_NONE;
12606 game.explosions_delayed = TRUE;
12608 if (game.magic_wall_active)
12610 if (!(game.magic_wall_time_left % 4))
12612 int element = Tile[magic_wall_x][magic_wall_y];
12614 if (element == EL_BD_MAGIC_WALL_FULL ||
12615 element == EL_BD_MAGIC_WALL_ACTIVE ||
12616 element == EL_BD_MAGIC_WALL_EMPTYING)
12617 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12618 else if (element == EL_DC_MAGIC_WALL_FULL ||
12619 element == EL_DC_MAGIC_WALL_ACTIVE ||
12620 element == EL_DC_MAGIC_WALL_EMPTYING)
12621 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12623 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12626 if (game.magic_wall_time_left > 0)
12628 game.magic_wall_time_left--;
12630 if (!game.magic_wall_time_left)
12632 SCAN_PLAYFIELD(x, y)
12634 element = Tile[x][y];
12636 if (element == EL_MAGIC_WALL_ACTIVE ||
12637 element == EL_MAGIC_WALL_FULL)
12639 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12640 TEST_DrawLevelField(x, y);
12642 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12643 element == EL_BD_MAGIC_WALL_FULL)
12645 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12646 TEST_DrawLevelField(x, y);
12648 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12649 element == EL_DC_MAGIC_WALL_FULL)
12651 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12652 TEST_DrawLevelField(x, y);
12656 game.magic_wall_active = FALSE;
12661 if (game.light_time_left > 0)
12663 game.light_time_left--;
12665 if (game.light_time_left == 0)
12666 RedrawAllLightSwitchesAndInvisibleElements();
12669 if (game.timegate_time_left > 0)
12671 game.timegate_time_left--;
12673 if (game.timegate_time_left == 0)
12674 CloseAllOpenTimegates();
12677 if (game.lenses_time_left > 0)
12679 game.lenses_time_left--;
12681 if (game.lenses_time_left == 0)
12682 RedrawAllInvisibleElementsForLenses();
12685 if (game.magnify_time_left > 0)
12687 game.magnify_time_left--;
12689 if (game.magnify_time_left == 0)
12690 RedrawAllInvisibleElementsForMagnifier();
12693 for (i = 0; i < MAX_PLAYERS; i++)
12695 struct PlayerInfo *player = &stored_player[i];
12697 if (SHIELD_ON(player))
12699 if (player->shield_deadly_time_left)
12700 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12701 else if (player->shield_normal_time_left)
12702 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12706 #if USE_DELAYED_GFX_REDRAW
12707 SCAN_PLAYFIELD(x, y)
12709 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12711 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12712 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12714 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12715 DrawLevelField(x, y);
12717 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12718 DrawLevelFieldCrumbled(x, y);
12720 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12721 DrawLevelFieldCrumbledNeighbours(x, y);
12723 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12724 DrawTwinkleOnField(x, y);
12727 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12732 PlayAllPlayersSound();
12734 for (i = 0; i < MAX_PLAYERS; i++)
12736 struct PlayerInfo *player = &stored_player[i];
12738 if (player->show_envelope != 0 && (!player->active ||
12739 player->MovPos == 0))
12741 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12743 player->show_envelope = 0;
12747 // use random number generator in every frame to make it less predictable
12748 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12751 mouse_action_last = mouse_action;
12754 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12756 int min_x = x, min_y = y, max_x = x, max_y = y;
12757 int scr_fieldx = getScreenFieldSizeX();
12758 int scr_fieldy = getScreenFieldSizeY();
12761 for (i = 0; i < MAX_PLAYERS; i++)
12763 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12765 if (!stored_player[i].active || &stored_player[i] == player)
12768 min_x = MIN(min_x, jx);
12769 min_y = MIN(min_y, jy);
12770 max_x = MAX(max_x, jx);
12771 max_y = MAX(max_y, jy);
12774 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12777 static boolean AllPlayersInVisibleScreen(void)
12781 for (i = 0; i < MAX_PLAYERS; i++)
12783 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12785 if (!stored_player[i].active)
12788 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12795 void ScrollLevel(int dx, int dy)
12797 int scroll_offset = 2 * TILEX_VAR;
12800 BlitBitmap(drawto_field, drawto_field,
12801 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12802 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12803 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12804 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12805 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12806 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12810 x = (dx == 1 ? BX1 : BX2);
12811 for (y = BY1; y <= BY2; y++)
12812 DrawScreenField(x, y);
12817 y = (dy == 1 ? BY1 : BY2);
12818 for (x = BX1; x <= BX2; x++)
12819 DrawScreenField(x, y);
12822 redraw_mask |= REDRAW_FIELD;
12825 static boolean canFallDown(struct PlayerInfo *player)
12827 int jx = player->jx, jy = player->jy;
12829 return (IN_LEV_FIELD(jx, jy + 1) &&
12830 (IS_FREE(jx, jy + 1) ||
12831 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12832 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12833 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12836 static boolean canPassField(int x, int y, int move_dir)
12838 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12839 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12840 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12841 int nextx = x + dx;
12842 int nexty = y + dy;
12843 int element = Tile[x][y];
12845 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12846 !CAN_MOVE(element) &&
12847 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12848 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12849 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12852 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12854 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12855 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12856 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12860 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12861 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12862 (IS_DIGGABLE(Tile[newx][newy]) ||
12863 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12864 canPassField(newx, newy, move_dir)));
12867 static void CheckGravityMovement(struct PlayerInfo *player)
12869 if (player->gravity && !player->programmed_action)
12871 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12872 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12873 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12874 int jx = player->jx, jy = player->jy;
12875 boolean player_is_moving_to_valid_field =
12876 (!player_is_snapping &&
12877 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12878 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12879 boolean player_can_fall_down = canFallDown(player);
12881 if (player_can_fall_down &&
12882 !player_is_moving_to_valid_field)
12883 player->programmed_action = MV_DOWN;
12887 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12889 return CheckGravityMovement(player);
12891 if (player->gravity && !player->programmed_action)
12893 int jx = player->jx, jy = player->jy;
12894 boolean field_under_player_is_free =
12895 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12896 boolean player_is_standing_on_valid_field =
12897 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12898 (IS_WALKABLE(Tile[jx][jy]) &&
12899 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12901 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12902 player->programmed_action = MV_DOWN;
12907 MovePlayerOneStep()
12908 -----------------------------------------------------------------------------
12909 dx, dy: direction (non-diagonal) to try to move the player to
12910 real_dx, real_dy: direction as read from input device (can be diagonal)
12913 boolean MovePlayerOneStep(struct PlayerInfo *player,
12914 int dx, int dy, int real_dx, int real_dy)
12916 int jx = player->jx, jy = player->jy;
12917 int new_jx = jx + dx, new_jy = jy + dy;
12919 boolean player_can_move = !player->cannot_move;
12921 if (!player->active || (!dx && !dy))
12922 return MP_NO_ACTION;
12924 player->MovDir = (dx < 0 ? MV_LEFT :
12925 dx > 0 ? MV_RIGHT :
12927 dy > 0 ? MV_DOWN : MV_NONE);
12929 if (!IN_LEV_FIELD(new_jx, new_jy))
12930 return MP_NO_ACTION;
12932 if (!player_can_move)
12934 if (player->MovPos == 0)
12936 player->is_moving = FALSE;
12937 player->is_digging = FALSE;
12938 player->is_collecting = FALSE;
12939 player->is_snapping = FALSE;
12940 player->is_pushing = FALSE;
12944 if (!network.enabled && game.centered_player_nr == -1 &&
12945 !AllPlayersInSight(player, new_jx, new_jy))
12946 return MP_NO_ACTION;
12948 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG);
12949 if (can_move != MP_MOVING)
12952 // check if DigField() has caused relocation of the player
12953 if (player->jx != jx || player->jy != jy)
12954 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12956 StorePlayer[jx][jy] = 0;
12957 player->last_jx = jx;
12958 player->last_jy = jy;
12959 player->jx = new_jx;
12960 player->jy = new_jy;
12961 StorePlayer[new_jx][new_jy] = player->element_nr;
12963 if (player->move_delay_value_next != -1)
12965 player->move_delay_value = player->move_delay_value_next;
12966 player->move_delay_value_next = -1;
12970 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12972 player->step_counter++;
12974 PlayerVisit[jx][jy] = FrameCounter;
12976 player->is_moving = TRUE;
12979 // should better be called in MovePlayer(), but this breaks some tapes
12980 ScrollPlayer(player, SCROLL_INIT);
12986 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12988 int jx = player->jx, jy = player->jy;
12989 int old_jx = jx, old_jy = jy;
12990 int moved = MP_NO_ACTION;
12992 if (!player->active)
12997 if (player->MovPos == 0)
12999 player->is_moving = FALSE;
13000 player->is_digging = FALSE;
13001 player->is_collecting = FALSE;
13002 player->is_snapping = FALSE;
13003 player->is_pushing = FALSE;
13009 if (player->move_delay > 0)
13012 player->move_delay = -1; // set to "uninitialized" value
13014 // store if player is automatically moved to next field
13015 player->is_auto_moving = (player->programmed_action != MV_NONE);
13017 // remove the last programmed player action
13018 player->programmed_action = 0;
13020 if (player->MovPos)
13022 // should only happen if pre-1.2 tape recordings are played
13023 // this is only for backward compatibility
13025 int original_move_delay_value = player->move_delay_value;
13028 Debug("game:playing:MovePlayer",
13029 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13033 // scroll remaining steps with finest movement resolution
13034 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13036 while (player->MovPos)
13038 ScrollPlayer(player, SCROLL_GO_ON);
13039 ScrollScreen(NULL, SCROLL_GO_ON);
13041 AdvanceFrameAndPlayerCounters(player->index_nr);
13044 BackToFront_WithFrameDelay(0);
13047 player->move_delay_value = original_move_delay_value;
13050 player->is_active = FALSE;
13052 if (player->last_move_dir & MV_HORIZONTAL)
13054 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13055 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13059 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13060 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13063 if (!moved && !player->is_active)
13065 player->is_moving = FALSE;
13066 player->is_digging = FALSE;
13067 player->is_collecting = FALSE;
13068 player->is_snapping = FALSE;
13069 player->is_pushing = FALSE;
13075 if (moved & MP_MOVING && !ScreenMovPos &&
13076 (player->index_nr == game.centered_player_nr ||
13077 game.centered_player_nr == -1))
13079 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13081 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13083 // actual player has left the screen -- scroll in that direction
13084 if (jx != old_jx) // player has moved horizontally
13085 scroll_x += (jx - old_jx);
13086 else // player has moved vertically
13087 scroll_y += (jy - old_jy);
13091 int offset_raw = game.scroll_delay_value;
13093 if (jx != old_jx) // player has moved horizontally
13095 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13096 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13097 int new_scroll_x = jx - MIDPOSX + offset_x;
13099 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13100 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13101 scroll_x = new_scroll_x;
13103 // don't scroll over playfield boundaries
13104 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13106 // don't scroll more than one field at a time
13107 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13109 // don't scroll against the player's moving direction
13110 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13111 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13112 scroll_x = old_scroll_x;
13114 else // player has moved vertically
13116 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13117 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13118 int new_scroll_y = jy - MIDPOSY + offset_y;
13120 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13121 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13122 scroll_y = new_scroll_y;
13124 // don't scroll over playfield boundaries
13125 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13127 // don't scroll more than one field at a time
13128 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13130 // don't scroll against the player's moving direction
13131 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13132 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13133 scroll_y = old_scroll_y;
13137 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13139 if (!network.enabled && game.centered_player_nr == -1 &&
13140 !AllPlayersInVisibleScreen())
13142 scroll_x = old_scroll_x;
13143 scroll_y = old_scroll_y;
13147 ScrollScreen(player, SCROLL_INIT);
13148 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13153 player->StepFrame = 0;
13155 if (moved & MP_MOVING)
13157 if (old_jx != jx && old_jy == jy)
13158 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13159 else if (old_jx == jx && old_jy != jy)
13160 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13162 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13164 player->last_move_dir = player->MovDir;
13165 player->is_moving = TRUE;
13166 player->is_snapping = FALSE;
13167 player->is_switching = FALSE;
13168 player->is_dropping = FALSE;
13169 player->is_dropping_pressed = FALSE;
13170 player->drop_pressed_delay = 0;
13173 // should better be called here than above, but this breaks some tapes
13174 ScrollPlayer(player, SCROLL_INIT);
13179 CheckGravityMovementWhenNotMoving(player);
13181 player->is_moving = FALSE;
13183 /* at this point, the player is allowed to move, but cannot move right now
13184 (e.g. because of something blocking the way) -- ensure that the player
13185 is also allowed to move in the next frame (in old versions before 3.1.1,
13186 the player was forced to wait again for eight frames before next try) */
13188 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13189 player->move_delay = 0; // allow direct movement in the next frame
13192 if (player->move_delay == -1) // not yet initialized by DigField()
13193 player->move_delay = player->move_delay_value;
13195 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13197 TestIfPlayerTouchesBadThing(jx, jy);
13198 TestIfPlayerTouchesCustomElement(jx, jy);
13201 if (!player->active)
13202 RemovePlayer(player);
13207 void ScrollPlayer(struct PlayerInfo *player, int mode)
13209 int jx = player->jx, jy = player->jy;
13210 int last_jx = player->last_jx, last_jy = player->last_jy;
13211 int move_stepsize = TILEX / player->move_delay_value;
13213 if (!player->active)
13216 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13219 if (mode == SCROLL_INIT)
13221 player->actual_frame_counter.count = FrameCounter;
13222 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13224 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13225 Tile[last_jx][last_jy] == EL_EMPTY)
13227 int last_field_block_delay = 0; // start with no blocking at all
13228 int block_delay_adjustment = player->block_delay_adjustment;
13230 // if player blocks last field, add delay for exactly one move
13231 if (player->block_last_field)
13233 last_field_block_delay += player->move_delay_value;
13235 // when blocking enabled, prevent moving up despite gravity
13236 if (player->gravity && player->MovDir == MV_UP)
13237 block_delay_adjustment = -1;
13240 // add block delay adjustment (also possible when not blocking)
13241 last_field_block_delay += block_delay_adjustment;
13243 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13244 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13247 if (player->MovPos != 0) // player has not yet reached destination
13250 else if (!FrameReached(&player->actual_frame_counter))
13253 if (player->MovPos != 0)
13255 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13256 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13258 // before DrawPlayer() to draw correct player graphic for this case
13259 if (player->MovPos == 0)
13260 CheckGravityMovement(player);
13263 if (player->MovPos == 0) // player reached destination field
13265 if (player->move_delay_reset_counter > 0)
13267 player->move_delay_reset_counter--;
13269 if (player->move_delay_reset_counter == 0)
13271 // continue with normal speed after quickly moving through gate
13272 HALVE_PLAYER_SPEED(player);
13274 // be able to make the next move without delay
13275 player->move_delay = 0;
13279 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13280 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13281 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13282 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13283 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13284 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13285 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13286 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13288 ExitPlayer(player);
13290 if (game.players_still_needed == 0 &&
13291 (game.friends_still_needed == 0 ||
13292 IS_SP_ELEMENT(Tile[jx][jy])))
13296 player->last_jx = jx;
13297 player->last_jy = jy;
13299 // this breaks one level: "machine", level 000
13301 int move_direction = player->MovDir;
13302 int enter_side = MV_DIR_OPPOSITE(move_direction);
13303 int leave_side = move_direction;
13304 int old_jx = last_jx;
13305 int old_jy = last_jy;
13306 int old_element = Tile[old_jx][old_jy];
13307 int new_element = Tile[jx][jy];
13309 if (IS_CUSTOM_ELEMENT(old_element))
13310 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13312 player->index_bit, leave_side);
13314 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13315 CE_PLAYER_LEAVES_X,
13316 player->index_bit, leave_side);
13318 // needed because pushed element has not yet reached its destination,
13319 // so it would trigger a change event at its previous field location
13320 if (!player->is_pushing)
13322 if (IS_CUSTOM_ELEMENT(new_element))
13323 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13324 player->index_bit, enter_side);
13326 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13327 CE_PLAYER_ENTERS_X,
13328 player->index_bit, enter_side);
13331 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13332 CE_MOVE_OF_X, move_direction);
13335 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13337 TestIfPlayerTouchesBadThing(jx, jy);
13338 TestIfPlayerTouchesCustomElement(jx, jy);
13340 // needed because pushed element has not yet reached its destination,
13341 // so it would trigger a change event at its previous field location
13342 if (!player->is_pushing)
13343 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13345 if (level.finish_dig_collect &&
13346 (player->is_digging || player->is_collecting))
13348 int last_element = player->last_removed_element;
13349 int move_direction = player->MovDir;
13350 int enter_side = MV_DIR_OPPOSITE(move_direction);
13351 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13352 CE_PLAYER_COLLECTS_X);
13354 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13355 player->index_bit, enter_side);
13357 player->last_removed_element = EL_UNDEFINED;
13360 if (!player->active)
13361 RemovePlayer(player);
13364 if (level.use_step_counter)
13365 CheckLevelTime_StepCounter();
13367 if (tape.single_step && tape.recording && !tape.pausing &&
13368 !player->programmed_action)
13369 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13371 if (!player->programmed_action)
13372 CheckSaveEngineSnapshot(player);
13376 void ScrollScreen(struct PlayerInfo *player, int mode)
13378 static DelayCounter screen_frame_counter = { 0 };
13380 if (mode == SCROLL_INIT)
13382 // set scrolling step size according to actual player's moving speed
13383 ScrollStepSize = TILEX / player->move_delay_value;
13385 screen_frame_counter.count = FrameCounter;
13386 screen_frame_counter.value = 1;
13388 ScreenMovDir = player->MovDir;
13389 ScreenMovPos = player->MovPos;
13390 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13393 else if (!FrameReached(&screen_frame_counter))
13398 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13399 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13400 redraw_mask |= REDRAW_FIELD;
13403 ScreenMovDir = MV_NONE;
13406 void CheckNextToConditions(int x, int y)
13408 int element = Tile[x][y];
13410 if (IS_PLAYER(x, y))
13411 TestIfPlayerNextToCustomElement(x, y);
13413 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13414 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13415 TestIfElementNextToCustomElement(x, y);
13418 void TestIfPlayerNextToCustomElement(int x, int y)
13420 struct XY *xy = xy_topdown;
13421 static int trigger_sides[4][2] =
13423 // center side border side
13424 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13425 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13426 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13427 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13431 if (!IS_PLAYER(x, y))
13434 struct PlayerInfo *player = PLAYERINFO(x, y);
13436 if (player->is_moving)
13439 for (i = 0; i < NUM_DIRECTIONS; i++)
13441 int xx = x + xy[i].x;
13442 int yy = y + xy[i].y;
13443 int border_side = trigger_sides[i][1];
13444 int border_element;
13446 if (!IN_LEV_FIELD(xx, yy))
13449 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13450 continue; // center and border element not connected
13452 border_element = Tile[xx][yy];
13454 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13455 player->index_bit, border_side);
13456 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13457 CE_PLAYER_NEXT_TO_X,
13458 player->index_bit, border_side);
13460 /* use player element that is initially defined in the level playfield,
13461 not the player element that corresponds to the runtime player number
13462 (example: a level that contains EL_PLAYER_3 as the only player would
13463 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13465 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13466 CE_NEXT_TO_X, border_side);
13470 void TestIfPlayerTouchesCustomElement(int x, int y)
13472 struct XY *xy = xy_topdown;
13473 static int trigger_sides[4][2] =
13475 // center side border side
13476 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13477 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13478 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13479 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13481 static int touch_dir[4] =
13483 MV_LEFT | MV_RIGHT,
13488 int center_element = Tile[x][y]; // should always be non-moving!
13491 for (i = 0; i < NUM_DIRECTIONS; i++)
13493 int xx = x + xy[i].x;
13494 int yy = y + xy[i].y;
13495 int center_side = trigger_sides[i][0];
13496 int border_side = trigger_sides[i][1];
13497 int border_element;
13499 if (!IN_LEV_FIELD(xx, yy))
13502 if (IS_PLAYER(x, y)) // player found at center element
13504 struct PlayerInfo *player = PLAYERINFO(x, y);
13506 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13507 border_element = Tile[xx][yy]; // may be moving!
13508 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13509 border_element = Tile[xx][yy];
13510 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13511 border_element = MovingOrBlocked2Element(xx, yy);
13513 continue; // center and border element do not touch
13515 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13516 player->index_bit, border_side);
13517 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13518 CE_PLAYER_TOUCHES_X,
13519 player->index_bit, border_side);
13522 /* use player element that is initially defined in the level playfield,
13523 not the player element that corresponds to the runtime player number
13524 (example: a level that contains EL_PLAYER_3 as the only player would
13525 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13526 int player_element = PLAYERINFO(x, y)->initial_element;
13528 // as element "X" is the player here, check opposite (center) side
13529 CheckElementChangeBySide(xx, yy, border_element, player_element,
13530 CE_TOUCHING_X, center_side);
13533 else if (IS_PLAYER(xx, yy)) // player found at border element
13535 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13537 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13539 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13540 continue; // center and border element do not touch
13543 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13544 player->index_bit, center_side);
13545 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13546 CE_PLAYER_TOUCHES_X,
13547 player->index_bit, center_side);
13550 /* use player element that is initially defined in the level playfield,
13551 not the player element that corresponds to the runtime player number
13552 (example: a level that contains EL_PLAYER_3 as the only player would
13553 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13554 int player_element = PLAYERINFO(xx, yy)->initial_element;
13556 // as element "X" is the player here, check opposite (border) side
13557 CheckElementChangeBySide(x, y, center_element, player_element,
13558 CE_TOUCHING_X, border_side);
13566 void TestIfElementNextToCustomElement(int x, int y)
13568 struct XY *xy = xy_topdown;
13569 static int trigger_sides[4][2] =
13571 // center side border side
13572 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13573 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13574 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13575 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13577 int center_element = Tile[x][y]; // should always be non-moving!
13580 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13583 for (i = 0; i < NUM_DIRECTIONS; i++)
13585 int xx = x + xy[i].x;
13586 int yy = y + xy[i].y;
13587 int border_side = trigger_sides[i][1];
13588 int border_element;
13590 if (!IN_LEV_FIELD(xx, yy))
13593 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13594 continue; // center and border element not connected
13596 border_element = Tile[xx][yy];
13598 // check for change of center element (but change it only once)
13599 if (CheckElementChangeBySide(x, y, center_element, border_element,
13600 CE_NEXT_TO_X, border_side))
13605 void TestIfElementTouchesCustomElement(int x, int y)
13607 struct XY *xy = xy_topdown;
13608 static int trigger_sides[4][2] =
13610 // center side border side
13611 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13612 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13613 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13614 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13616 static int touch_dir[4] =
13618 MV_LEFT | MV_RIGHT,
13623 boolean change_center_element = FALSE;
13624 int center_element = Tile[x][y]; // should always be non-moving!
13625 int border_element_old[NUM_DIRECTIONS];
13628 for (i = 0; i < NUM_DIRECTIONS; i++)
13630 int xx = x + xy[i].x;
13631 int yy = y + xy[i].y;
13632 int border_element;
13634 border_element_old[i] = -1;
13636 if (!IN_LEV_FIELD(xx, yy))
13639 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13640 border_element = Tile[xx][yy]; // may be moving!
13641 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13642 border_element = Tile[xx][yy];
13643 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13644 border_element = MovingOrBlocked2Element(xx, yy);
13646 continue; // center and border element do not touch
13648 border_element_old[i] = border_element;
13651 for (i = 0; i < NUM_DIRECTIONS; i++)
13653 int xx = x + xy[i].x;
13654 int yy = y + xy[i].y;
13655 int center_side = trigger_sides[i][0];
13656 int border_element = border_element_old[i];
13658 if (border_element == -1)
13661 // check for change of border element
13662 CheckElementChangeBySide(xx, yy, border_element, center_element,
13663 CE_TOUCHING_X, center_side);
13665 // (center element cannot be player, so we don't have to check this here)
13668 for (i = 0; i < NUM_DIRECTIONS; i++)
13670 int xx = x + xy[i].x;
13671 int yy = y + xy[i].y;
13672 int border_side = trigger_sides[i][1];
13673 int border_element = border_element_old[i];
13675 if (border_element == -1)
13678 // check for change of center element (but change it only once)
13679 if (!change_center_element)
13680 change_center_element =
13681 CheckElementChangeBySide(x, y, center_element, border_element,
13682 CE_TOUCHING_X, border_side);
13684 if (IS_PLAYER(xx, yy))
13686 /* use player element that is initially defined in the level playfield,
13687 not the player element that corresponds to the runtime player number
13688 (example: a level that contains EL_PLAYER_3 as the only player would
13689 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13690 int player_element = PLAYERINFO(xx, yy)->initial_element;
13692 // as element "X" is the player here, check opposite (border) side
13693 CheckElementChangeBySide(x, y, center_element, player_element,
13694 CE_TOUCHING_X, border_side);
13699 void TestIfElementHitsCustomElement(int x, int y, int direction)
13701 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13702 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13703 int hitx = x + dx, hity = y + dy;
13704 int hitting_element = Tile[x][y];
13705 int touched_element;
13707 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13710 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13711 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13713 if (IN_LEV_FIELD(hitx, hity))
13715 int opposite_direction = MV_DIR_OPPOSITE(direction);
13716 int hitting_side = direction;
13717 int touched_side = opposite_direction;
13718 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13719 MovDir[hitx][hity] != direction ||
13720 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13726 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13727 CE_HITTING_X, touched_side);
13729 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13730 CE_HIT_BY_X, hitting_side);
13732 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13733 CE_HIT_BY_SOMETHING, opposite_direction);
13735 if (IS_PLAYER(hitx, hity))
13737 /* use player element that is initially defined in the level playfield,
13738 not the player element that corresponds to the runtime player number
13739 (example: a level that contains EL_PLAYER_3 as the only player would
13740 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13741 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13743 CheckElementChangeBySide(x, y, hitting_element, player_element,
13744 CE_HITTING_X, touched_side);
13749 // "hitting something" is also true when hitting the playfield border
13750 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13751 CE_HITTING_SOMETHING, direction);
13754 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13756 int i, kill_x = -1, kill_y = -1;
13758 int bad_element = -1;
13759 struct XY *test_xy = xy_topdown;
13760 static int test_dir[4] =
13768 for (i = 0; i < NUM_DIRECTIONS; i++)
13770 int test_x, test_y, test_move_dir, test_element;
13772 test_x = good_x + test_xy[i].x;
13773 test_y = good_y + test_xy[i].y;
13775 if (!IN_LEV_FIELD(test_x, test_y))
13779 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13781 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13783 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13784 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13786 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13787 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13791 bad_element = test_element;
13797 if (kill_x != -1 || kill_y != -1)
13799 if (IS_PLAYER(good_x, good_y))
13801 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13803 if (player->shield_deadly_time_left > 0 &&
13804 !IS_INDESTRUCTIBLE(bad_element))
13805 Bang(kill_x, kill_y);
13806 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13807 KillPlayer(player);
13810 Bang(good_x, good_y);
13814 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13816 int i, kill_x = -1, kill_y = -1;
13817 int bad_element = Tile[bad_x][bad_y];
13818 struct XY *test_xy = xy_topdown;
13819 static int touch_dir[4] =
13821 MV_LEFT | MV_RIGHT,
13826 static int test_dir[4] =
13834 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13837 for (i = 0; i < NUM_DIRECTIONS; i++)
13839 int test_x, test_y, test_move_dir, test_element;
13841 test_x = bad_x + test_xy[i].x;
13842 test_y = bad_y + test_xy[i].y;
13844 if (!IN_LEV_FIELD(test_x, test_y))
13848 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13850 test_element = Tile[test_x][test_y];
13852 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13853 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13855 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13856 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13858 // good thing is player or penguin that does not move away
13859 if (IS_PLAYER(test_x, test_y))
13861 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13863 if (bad_element == EL_ROBOT && player->is_moving)
13864 continue; // robot does not kill player if he is moving
13866 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13868 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13869 continue; // center and border element do not touch
13877 else if (test_element == EL_PENGUIN)
13887 if (kill_x != -1 || kill_y != -1)
13889 if (IS_PLAYER(kill_x, kill_y))
13891 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13893 if (player->shield_deadly_time_left > 0 &&
13894 !IS_INDESTRUCTIBLE(bad_element))
13895 Bang(bad_x, bad_y);
13896 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13897 KillPlayer(player);
13900 Bang(kill_x, kill_y);
13904 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13906 int bad_element = Tile[bad_x][bad_y];
13907 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13908 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13909 int test_x = bad_x + dx, test_y = bad_y + dy;
13910 int test_move_dir, test_element;
13911 int kill_x = -1, kill_y = -1;
13913 if (!IN_LEV_FIELD(test_x, test_y))
13917 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13919 test_element = Tile[test_x][test_y];
13921 if (test_move_dir != bad_move_dir)
13923 // good thing can be player or penguin that does not move away
13924 if (IS_PLAYER(test_x, test_y))
13926 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13928 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13929 player as being hit when he is moving towards the bad thing, because
13930 the "get hit by" condition would be lost after the player stops) */
13931 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13932 return; // player moves away from bad thing
13937 else if (test_element == EL_PENGUIN)
13944 if (kill_x != -1 || kill_y != -1)
13946 if (IS_PLAYER(kill_x, kill_y))
13948 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13950 if (player->shield_deadly_time_left > 0 &&
13951 !IS_INDESTRUCTIBLE(bad_element))
13952 Bang(bad_x, bad_y);
13953 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13954 KillPlayer(player);
13957 Bang(kill_x, kill_y);
13961 void TestIfPlayerTouchesBadThing(int x, int y)
13963 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13966 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13968 TestIfGoodThingHitsBadThing(x, y, move_dir);
13971 void TestIfBadThingTouchesPlayer(int x, int y)
13973 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13976 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13978 TestIfBadThingHitsGoodThing(x, y, move_dir);
13981 void TestIfFriendTouchesBadThing(int x, int y)
13983 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13986 void TestIfBadThingTouchesFriend(int x, int y)
13988 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13991 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13993 int i, kill_x = bad_x, kill_y = bad_y;
13994 struct XY *xy = xy_topdown;
13996 for (i = 0; i < NUM_DIRECTIONS; i++)
14000 x = bad_x + xy[i].x;
14001 y = bad_y + xy[i].y;
14002 if (!IN_LEV_FIELD(x, y))
14005 element = Tile[x][y];
14006 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14007 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14015 if (kill_x != bad_x || kill_y != bad_y)
14016 Bang(bad_x, bad_y);
14019 void KillPlayer(struct PlayerInfo *player)
14021 int jx = player->jx, jy = player->jy;
14023 if (!player->active)
14027 Debug("game:playing:KillPlayer",
14028 "0: killed == %d, active == %d, reanimated == %d",
14029 player->killed, player->active, player->reanimated);
14032 /* the following code was introduced to prevent an infinite loop when calling
14034 -> CheckTriggeredElementChangeExt()
14035 -> ExecuteCustomElementAction()
14037 -> (infinitely repeating the above sequence of function calls)
14038 which occurs when killing the player while having a CE with the setting
14039 "kill player X when explosion of <player X>"; the solution using a new
14040 field "player->killed" was chosen for backwards compatibility, although
14041 clever use of the fields "player->active" etc. would probably also work */
14043 if (player->killed)
14047 player->killed = TRUE;
14049 // remove accessible field at the player's position
14050 RemoveField(jx, jy);
14052 // deactivate shield (else Bang()/Explode() would not work right)
14053 player->shield_normal_time_left = 0;
14054 player->shield_deadly_time_left = 0;
14057 Debug("game:playing:KillPlayer",
14058 "1: killed == %d, active == %d, reanimated == %d",
14059 player->killed, player->active, player->reanimated);
14065 Debug("game:playing:KillPlayer",
14066 "2: killed == %d, active == %d, reanimated == %d",
14067 player->killed, player->active, player->reanimated);
14070 if (player->reanimated) // killed player may have been reanimated
14071 player->killed = player->reanimated = FALSE;
14073 BuryPlayer(player);
14076 static void KillPlayerUnlessEnemyProtected(int x, int y)
14078 if (!PLAYER_ENEMY_PROTECTED(x, y))
14079 KillPlayer(PLAYERINFO(x, y));
14082 static void KillPlayerUnlessExplosionProtected(int x, int y)
14084 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14085 KillPlayer(PLAYERINFO(x, y));
14088 void BuryPlayer(struct PlayerInfo *player)
14090 int jx = player->jx, jy = player->jy;
14092 if (!player->active)
14095 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14097 RemovePlayer(player);
14099 player->buried = TRUE;
14101 if (game.all_players_gone)
14102 game.GameOver = TRUE;
14105 void RemovePlayer(struct PlayerInfo *player)
14107 int jx = player->jx, jy = player->jy;
14108 int i, found = FALSE;
14110 player->present = FALSE;
14111 player->active = FALSE;
14113 // required for some CE actions (even if the player is not active anymore)
14114 player->MovPos = 0;
14116 if (!ExplodeField[jx][jy])
14117 StorePlayer[jx][jy] = 0;
14119 if (player->is_moving)
14120 TEST_DrawLevelField(player->last_jx, player->last_jy);
14122 for (i = 0; i < MAX_PLAYERS; i++)
14123 if (stored_player[i].active)
14128 game.all_players_gone = TRUE;
14129 game.GameOver = TRUE;
14132 game.exit_x = game.robot_wheel_x = jx;
14133 game.exit_y = game.robot_wheel_y = jy;
14136 void ExitPlayer(struct PlayerInfo *player)
14138 DrawPlayer(player); // needed here only to cleanup last field
14139 RemovePlayer(player);
14141 if (game.players_still_needed > 0)
14142 game.players_still_needed--;
14145 static void SetFieldForSnapping(int x, int y, int element, int direction,
14146 int player_index_bit)
14148 struct ElementInfo *ei = &element_info[element];
14149 int direction_bit = MV_DIR_TO_BIT(direction);
14150 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14151 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14152 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14154 Tile[x][y] = EL_ELEMENT_SNAPPING;
14155 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14156 MovDir[x][y] = direction;
14157 Store[x][y] = element;
14158 Store2[x][y] = player_index_bit;
14160 ResetGfxAnimation(x, y);
14162 GfxElement[x][y] = element;
14163 GfxAction[x][y] = action;
14164 GfxDir[x][y] = direction;
14165 GfxFrame[x][y] = -1;
14168 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14169 int player_index_bit)
14171 TestIfElementTouchesCustomElement(x, y); // for empty space
14173 if (level.finish_dig_collect)
14175 int dig_side = MV_DIR_OPPOSITE(direction);
14176 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14177 CE_PLAYER_COLLECTS_X);
14179 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14180 player_index_bit, dig_side);
14181 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14182 player_index_bit, dig_side);
14187 =============================================================================
14188 checkDiagonalPushing()
14189 -----------------------------------------------------------------------------
14190 check if diagonal input device direction results in pushing of object
14191 (by checking if the alternative direction is walkable, diggable, ...)
14192 =============================================================================
14195 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14196 int x, int y, int real_dx, int real_dy)
14198 int jx, jy, dx, dy, xx, yy;
14200 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14203 // diagonal direction: check alternative direction
14208 xx = jx + (dx == 0 ? real_dx : 0);
14209 yy = jy + (dy == 0 ? real_dy : 0);
14211 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14215 =============================================================================
14217 -----------------------------------------------------------------------------
14218 x, y: field next to player (non-diagonal) to try to dig to
14219 real_dx, real_dy: direction as read from input device (can be diagonal)
14220 =============================================================================
14223 static int DigField(struct PlayerInfo *player,
14224 int oldx, int oldy, int x, int y,
14225 int real_dx, int real_dy, int mode)
14227 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14228 boolean player_was_pushing = player->is_pushing;
14229 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14230 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14231 int jx = oldx, jy = oldy;
14232 int dx = x - jx, dy = y - jy;
14233 int nextx = x + dx, nexty = y + dy;
14234 int move_direction = (dx == -1 ? MV_LEFT :
14235 dx == +1 ? MV_RIGHT :
14237 dy == +1 ? MV_DOWN : MV_NONE);
14238 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14239 int dig_side = MV_DIR_OPPOSITE(move_direction);
14240 int old_element = Tile[jx][jy];
14241 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14244 if (is_player) // function can also be called by EL_PENGUIN
14246 if (player->MovPos == 0)
14248 player->is_digging = FALSE;
14249 player->is_collecting = FALSE;
14252 if (player->MovPos == 0) // last pushing move finished
14253 player->is_pushing = FALSE;
14255 if (mode == DF_NO_PUSH) // player just stopped pushing
14257 player->is_switching = FALSE;
14258 player->push_delay = -1;
14260 return MP_NO_ACTION;
14263 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14264 old_element = Back[jx][jy];
14266 // in case of element dropped at player position, check background
14267 else if (Back[jx][jy] != EL_EMPTY &&
14268 game.engine_version >= VERSION_IDENT(2,2,0,0))
14269 old_element = Back[jx][jy];
14271 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14272 return MP_NO_ACTION; // field has no opening in this direction
14274 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction))
14275 return MP_NO_ACTION; // field has no opening in this direction
14277 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14281 Tile[jx][jy] = player->artwork_element;
14282 InitMovingField(jx, jy, MV_DOWN);
14283 Store[jx][jy] = EL_ACID;
14284 ContinueMoving(jx, jy);
14285 BuryPlayer(player);
14287 return MP_DONT_RUN_INTO;
14290 if (player_can_move && DONT_RUN_INTO(element))
14292 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14294 return MP_DONT_RUN_INTO;
14297 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14298 return MP_NO_ACTION;
14300 collect_count = element_info[element].collect_count_initial;
14302 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14303 return MP_NO_ACTION;
14305 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14306 player_can_move = player_can_move_or_snap;
14308 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14309 game.engine_version >= VERSION_IDENT(2,2,0,0))
14311 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14312 player->index_bit, dig_side);
14313 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14314 player->index_bit, dig_side);
14316 if (element == EL_DC_LANDMINE)
14319 if (Tile[x][y] != element) // field changed by snapping
14322 return MP_NO_ACTION;
14325 if (player->gravity && is_player && !player->is_auto_moving &&
14326 canFallDown(player) && move_direction != MV_DOWN &&
14327 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14328 return MP_NO_ACTION; // player cannot walk here due to gravity
14330 if (player_can_move &&
14331 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14333 int sound_element = SND_ELEMENT(element);
14334 int sound_action = ACTION_WALKING;
14336 if (IS_RND_GATE(element))
14338 if (!player->key[RND_GATE_NR(element)])
14339 return MP_NO_ACTION;
14341 else if (IS_RND_GATE_GRAY(element))
14343 if (!player->key[RND_GATE_GRAY_NR(element)])
14344 return MP_NO_ACTION;
14346 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14348 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14349 return MP_NO_ACTION;
14351 else if (element == EL_EXIT_OPEN ||
14352 element == EL_EM_EXIT_OPEN ||
14353 element == EL_EM_EXIT_OPENING ||
14354 element == EL_STEEL_EXIT_OPEN ||
14355 element == EL_EM_STEEL_EXIT_OPEN ||
14356 element == EL_EM_STEEL_EXIT_OPENING ||
14357 element == EL_SP_EXIT_OPEN ||
14358 element == EL_SP_EXIT_OPENING)
14360 sound_action = ACTION_PASSING; // player is passing exit
14362 else if (element == EL_EMPTY)
14364 sound_action = ACTION_MOVING; // nothing to walk on
14367 // play sound from background or player, whatever is available
14368 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14369 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14371 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14373 else if (player_can_move &&
14374 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14376 if (!ACCESS_FROM(element, opposite_direction))
14377 return MP_NO_ACTION; // field not accessible from this direction
14379 if (CAN_MOVE(element)) // only fixed elements can be passed!
14380 return MP_NO_ACTION;
14382 if (IS_EM_GATE(element))
14384 if (!player->key[EM_GATE_NR(element)])
14385 return MP_NO_ACTION;
14387 else if (IS_EM_GATE_GRAY(element))
14389 if (!player->key[EM_GATE_GRAY_NR(element)])
14390 return MP_NO_ACTION;
14392 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14394 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14395 return MP_NO_ACTION;
14397 else if (IS_EMC_GATE(element))
14399 if (!player->key[EMC_GATE_NR(element)])
14400 return MP_NO_ACTION;
14402 else if (IS_EMC_GATE_GRAY(element))
14404 if (!player->key[EMC_GATE_GRAY_NR(element)])
14405 return MP_NO_ACTION;
14407 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14409 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14410 return MP_NO_ACTION;
14412 else if (element == EL_DC_GATE_WHITE ||
14413 element == EL_DC_GATE_WHITE_GRAY ||
14414 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14416 if (player->num_white_keys == 0)
14417 return MP_NO_ACTION;
14419 player->num_white_keys--;
14421 else if (IS_SP_PORT(element))
14423 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14424 element == EL_SP_GRAVITY_PORT_RIGHT ||
14425 element == EL_SP_GRAVITY_PORT_UP ||
14426 element == EL_SP_GRAVITY_PORT_DOWN)
14427 player->gravity = !player->gravity;
14428 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14429 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14430 element == EL_SP_GRAVITY_ON_PORT_UP ||
14431 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14432 player->gravity = TRUE;
14433 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14434 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14435 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14436 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14437 player->gravity = FALSE;
14440 // automatically move to the next field with double speed
14441 player->programmed_action = move_direction;
14443 if (player->move_delay_reset_counter == 0)
14445 player->move_delay_reset_counter = 2; // two double speed steps
14447 DOUBLE_PLAYER_SPEED(player);
14450 PlayLevelSoundAction(x, y, ACTION_PASSING);
14452 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14456 if (mode != DF_SNAP)
14458 GfxElement[x][y] = GFX_ELEMENT(element);
14459 player->is_digging = TRUE;
14462 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14464 // use old behaviour for old levels (digging)
14465 if (!level.finish_dig_collect)
14467 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14468 player->index_bit, dig_side);
14470 // if digging triggered player relocation, finish digging tile
14471 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14472 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14475 if (mode == DF_SNAP)
14477 if (level.block_snap_field)
14478 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14480 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14482 // use old behaviour for old levels (snapping)
14483 if (!level.finish_dig_collect)
14484 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14485 player->index_bit, dig_side);
14488 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14492 if (is_player && mode != DF_SNAP)
14494 GfxElement[x][y] = element;
14495 player->is_collecting = TRUE;
14498 if (element == EL_SPEED_PILL)
14500 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14502 else if (element == EL_EXTRA_TIME && level.time > 0)
14504 TimeLeft += level.extra_time;
14506 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14508 DisplayGameControlValues();
14510 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14512 int shield_time = (element == EL_SHIELD_DEADLY ?
14513 level.shield_deadly_time :
14514 level.shield_normal_time);
14516 player->shield_normal_time_left += shield_time;
14517 if (element == EL_SHIELD_DEADLY)
14518 player->shield_deadly_time_left += shield_time;
14520 else if (element == EL_DYNAMITE ||
14521 element == EL_EM_DYNAMITE ||
14522 element == EL_SP_DISK_RED)
14524 if (player->inventory_size < MAX_INVENTORY_SIZE)
14525 player->inventory_element[player->inventory_size++] = element;
14527 DrawGameDoorValues();
14529 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14531 player->dynabomb_count++;
14532 player->dynabombs_left++;
14534 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14536 player->dynabomb_size++;
14538 else if (element == EL_DYNABOMB_INCREASE_POWER)
14540 player->dynabomb_xl = TRUE;
14542 else if (IS_KEY(element))
14544 player->key[KEY_NR(element)] = TRUE;
14546 DrawGameDoorValues();
14548 else if (element == EL_DC_KEY_WHITE)
14550 player->num_white_keys++;
14552 // display white keys?
14553 // DrawGameDoorValues();
14555 else if (IS_ENVELOPE(element))
14557 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14559 if (!wait_for_snapping)
14560 player->show_envelope = element;
14562 else if (element == EL_EMC_LENSES)
14564 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14566 RedrawAllInvisibleElementsForLenses();
14568 else if (element == EL_EMC_MAGNIFIER)
14570 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14572 RedrawAllInvisibleElementsForMagnifier();
14574 else if (IS_DROPPABLE(element) ||
14575 IS_THROWABLE(element)) // can be collected and dropped
14579 if (collect_count == 0)
14580 player->inventory_infinite_element = element;
14582 for (i = 0; i < collect_count; i++)
14583 if (player->inventory_size < MAX_INVENTORY_SIZE)
14584 player->inventory_element[player->inventory_size++] = element;
14586 DrawGameDoorValues();
14588 else if (collect_count > 0)
14590 game.gems_still_needed -= collect_count;
14591 if (game.gems_still_needed < 0)
14592 game.gems_still_needed = 0;
14594 game.snapshot.collected_item = TRUE;
14596 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14598 DisplayGameControlValues();
14601 RaiseScoreElement(element);
14602 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14604 // use old behaviour for old levels (collecting)
14605 if (!level.finish_dig_collect && is_player)
14607 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14608 player->index_bit, dig_side);
14610 // if collecting triggered player relocation, finish collecting tile
14611 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14612 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14615 if (mode == DF_SNAP)
14617 if (level.block_snap_field)
14618 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14620 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14622 // use old behaviour for old levels (snapping)
14623 if (!level.finish_dig_collect)
14624 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14625 player->index_bit, dig_side);
14628 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14630 if (mode == DF_SNAP && element != EL_BD_ROCK)
14631 return MP_NO_ACTION;
14633 if (CAN_FALL(element) && dy)
14634 return MP_NO_ACTION;
14636 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14637 !(element == EL_SPRING && level.use_spring_bug))
14638 return MP_NO_ACTION;
14640 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14641 ((move_direction & MV_VERTICAL &&
14642 ((element_info[element].move_pattern & MV_LEFT &&
14643 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14644 (element_info[element].move_pattern & MV_RIGHT &&
14645 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14646 (move_direction & MV_HORIZONTAL &&
14647 ((element_info[element].move_pattern & MV_UP &&
14648 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14649 (element_info[element].move_pattern & MV_DOWN &&
14650 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14651 return MP_NO_ACTION;
14653 // do not push elements already moving away faster than player
14654 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14655 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14656 return MP_NO_ACTION;
14658 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14660 if (player->push_delay_value == -1 || !player_was_pushing)
14661 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14663 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14665 if (player->push_delay_value == -1)
14666 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14668 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14670 if (!player->is_pushing)
14671 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14674 player->is_pushing = TRUE;
14675 player->is_active = TRUE;
14677 if (!(IN_LEV_FIELD(nextx, nexty) &&
14678 (IS_FREE(nextx, nexty) ||
14679 (IS_SB_ELEMENT(element) &&
14680 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14681 (IS_CUSTOM_ELEMENT(element) &&
14682 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14683 return MP_NO_ACTION;
14685 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14686 return MP_NO_ACTION;
14688 if (player->push_delay == -1) // new pushing; restart delay
14689 player->push_delay = 0;
14691 if (player->push_delay < player->push_delay_value &&
14692 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14693 element != EL_SPRING && element != EL_BALLOON)
14695 // make sure that there is no move delay before next try to push
14696 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14697 player->move_delay = 0;
14699 return MP_NO_ACTION;
14702 if (IS_CUSTOM_ELEMENT(element) &&
14703 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14705 if (!DigFieldByCE(nextx, nexty, element))
14706 return MP_NO_ACTION;
14709 if (IS_SB_ELEMENT(element))
14711 boolean sokoban_task_solved = FALSE;
14713 if (element == EL_SOKOBAN_FIELD_FULL)
14715 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14717 IncrementSokobanFieldsNeeded();
14718 IncrementSokobanObjectsNeeded();
14721 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14723 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14725 DecrementSokobanFieldsNeeded();
14726 DecrementSokobanObjectsNeeded();
14728 // sokoban object was pushed from empty field to sokoban field
14729 if (Back[x][y] == EL_EMPTY)
14730 sokoban_task_solved = TRUE;
14733 Tile[x][y] = EL_SOKOBAN_OBJECT;
14735 if (Back[x][y] == Back[nextx][nexty])
14736 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14737 else if (Back[x][y] != 0)
14738 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14741 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14744 if (sokoban_task_solved &&
14745 game.sokoban_fields_still_needed == 0 &&
14746 game.sokoban_objects_still_needed == 0 &&
14747 level.auto_exit_sokoban)
14749 game.players_still_needed = 0;
14753 PlaySound(SND_GAME_SOKOBAN_SOLVING);
14757 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14759 InitMovingField(x, y, move_direction);
14760 GfxAction[x][y] = ACTION_PUSHING;
14762 if (mode == DF_SNAP)
14763 ContinueMoving(x, y);
14765 MovPos[x][y] = (dx != 0 ? dx : dy);
14767 Pushed[x][y] = TRUE;
14768 Pushed[nextx][nexty] = TRUE;
14770 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14771 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14773 player->push_delay_value = -1; // get new value later
14775 // check for element change _after_ element has been pushed
14776 if (game.use_change_when_pushing_bug)
14778 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14779 player->index_bit, dig_side);
14780 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14781 player->index_bit, dig_side);
14784 else if (IS_SWITCHABLE(element))
14786 if (PLAYER_SWITCHING(player, x, y))
14788 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14789 player->index_bit, dig_side);
14794 player->is_switching = TRUE;
14795 player->switch_x = x;
14796 player->switch_y = y;
14798 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14800 if (element == EL_ROBOT_WHEEL)
14802 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14804 game.robot_wheel_x = x;
14805 game.robot_wheel_y = y;
14806 game.robot_wheel_active = TRUE;
14808 TEST_DrawLevelField(x, y);
14810 else if (element == EL_SP_TERMINAL)
14814 SCAN_PLAYFIELD(xx, yy)
14816 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14820 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14822 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14824 ResetGfxAnimation(xx, yy);
14825 TEST_DrawLevelField(xx, yy);
14829 else if (IS_BELT_SWITCH(element))
14831 ToggleBeltSwitch(x, y);
14833 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14834 element == EL_SWITCHGATE_SWITCH_DOWN ||
14835 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14836 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14838 ToggleSwitchgateSwitch();
14840 else if (element == EL_LIGHT_SWITCH ||
14841 element == EL_LIGHT_SWITCH_ACTIVE)
14843 ToggleLightSwitch(x, y);
14845 else if (element == EL_TIMEGATE_SWITCH ||
14846 element == EL_DC_TIMEGATE_SWITCH)
14848 ActivateTimegateSwitch(x, y);
14850 else if (element == EL_BALLOON_SWITCH_LEFT ||
14851 element == EL_BALLOON_SWITCH_RIGHT ||
14852 element == EL_BALLOON_SWITCH_UP ||
14853 element == EL_BALLOON_SWITCH_DOWN ||
14854 element == EL_BALLOON_SWITCH_NONE ||
14855 element == EL_BALLOON_SWITCH_ANY)
14857 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14858 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14859 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14860 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14861 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14864 else if (element == EL_LAMP)
14866 Tile[x][y] = EL_LAMP_ACTIVE;
14867 game.lights_still_needed--;
14869 ResetGfxAnimation(x, y);
14870 TEST_DrawLevelField(x, y);
14872 else if (element == EL_TIME_ORB_FULL)
14874 Tile[x][y] = EL_TIME_ORB_EMPTY;
14876 if (level.time > 0 || level.use_time_orb_bug)
14878 TimeLeft += level.time_orb_time;
14879 game.no_level_time_limit = FALSE;
14881 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14883 DisplayGameControlValues();
14886 ResetGfxAnimation(x, y);
14887 TEST_DrawLevelField(x, y);
14889 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14890 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14894 game.ball_active = !game.ball_active;
14896 SCAN_PLAYFIELD(xx, yy)
14898 int e = Tile[xx][yy];
14900 if (game.ball_active)
14902 if (e == EL_EMC_MAGIC_BALL)
14903 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14904 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14905 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14909 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14910 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14911 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14912 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14917 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14918 player->index_bit, dig_side);
14920 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14921 player->index_bit, dig_side);
14923 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14924 player->index_bit, dig_side);
14930 if (!PLAYER_SWITCHING(player, x, y))
14932 player->is_switching = TRUE;
14933 player->switch_x = x;
14934 player->switch_y = y;
14936 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14937 player->index_bit, dig_side);
14938 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14939 player->index_bit, dig_side);
14941 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14942 player->index_bit, dig_side);
14943 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14944 player->index_bit, dig_side);
14947 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14948 player->index_bit, dig_side);
14949 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14950 player->index_bit, dig_side);
14952 return MP_NO_ACTION;
14955 player->push_delay = -1;
14957 if (is_player) // function can also be called by EL_PENGUIN
14959 if (Tile[x][y] != element) // really digged/collected something
14961 player->is_collecting = !player->is_digging;
14962 player->is_active = TRUE;
14964 player->last_removed_element = element;
14971 static boolean DigFieldByCE(int x, int y, int digging_element)
14973 int element = Tile[x][y];
14975 if (!IS_FREE(x, y))
14977 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14978 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14981 // no element can dig solid indestructible elements
14982 if (IS_INDESTRUCTIBLE(element) &&
14983 !IS_DIGGABLE(element) &&
14984 !IS_COLLECTIBLE(element))
14987 if (AmoebaNr[x][y] &&
14988 (element == EL_AMOEBA_FULL ||
14989 element == EL_BD_AMOEBA ||
14990 element == EL_AMOEBA_GROWING))
14992 AmoebaCnt[AmoebaNr[x][y]]--;
14993 AmoebaCnt2[AmoebaNr[x][y]]--;
14996 if (IS_MOVING(x, y))
14997 RemoveMovingField(x, y);
15001 TEST_DrawLevelField(x, y);
15004 // if digged element was about to explode, prevent the explosion
15005 ExplodeField[x][y] = EX_TYPE_NONE;
15007 PlayLevelSoundAction(x, y, action);
15010 Store[x][y] = EL_EMPTY;
15012 // this makes it possible to leave the removed element again
15013 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15014 Store[x][y] = element;
15019 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15021 int jx = player->jx, jy = player->jy;
15022 int x = jx + dx, y = jy + dy;
15023 int snap_direction = (dx == -1 ? MV_LEFT :
15024 dx == +1 ? MV_RIGHT :
15026 dy == +1 ? MV_DOWN : MV_NONE);
15027 boolean can_continue_snapping = (level.continuous_snapping &&
15028 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15030 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15033 if (!player->active || !IN_LEV_FIELD(x, y))
15041 if (player->MovPos == 0)
15042 player->is_pushing = FALSE;
15044 player->is_snapping = FALSE;
15046 if (player->MovPos == 0)
15048 player->is_moving = FALSE;
15049 player->is_digging = FALSE;
15050 player->is_collecting = FALSE;
15056 // prevent snapping with already pressed snap key when not allowed
15057 if (player->is_snapping && !can_continue_snapping)
15060 player->MovDir = snap_direction;
15062 if (player->MovPos == 0)
15064 player->is_moving = FALSE;
15065 player->is_digging = FALSE;
15066 player->is_collecting = FALSE;
15069 player->is_dropping = FALSE;
15070 player->is_dropping_pressed = FALSE;
15071 player->drop_pressed_delay = 0;
15073 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15076 player->is_snapping = TRUE;
15077 player->is_active = TRUE;
15079 if (player->MovPos == 0)
15081 player->is_moving = FALSE;
15082 player->is_digging = FALSE;
15083 player->is_collecting = FALSE;
15086 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15087 TEST_DrawLevelField(player->last_jx, player->last_jy);
15089 TEST_DrawLevelField(x, y);
15094 static boolean DropElement(struct PlayerInfo *player)
15096 int old_element, new_element;
15097 int dropx = player->jx, dropy = player->jy;
15098 int drop_direction = player->MovDir;
15099 int drop_side = drop_direction;
15100 int drop_element = get_next_dropped_element(player);
15102 /* do not drop an element on top of another element; when holding drop key
15103 pressed without moving, dropped element must move away before the next
15104 element can be dropped (this is especially important if the next element
15105 is dynamite, which can be placed on background for historical reasons) */
15106 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15109 if (IS_THROWABLE(drop_element))
15111 dropx += GET_DX_FROM_DIR(drop_direction);
15112 dropy += GET_DY_FROM_DIR(drop_direction);
15114 if (!IN_LEV_FIELD(dropx, dropy))
15118 old_element = Tile[dropx][dropy]; // old element at dropping position
15119 new_element = drop_element; // default: no change when dropping
15121 // check if player is active, not moving and ready to drop
15122 if (!player->active || player->MovPos || player->drop_delay > 0)
15125 // check if player has anything that can be dropped
15126 if (new_element == EL_UNDEFINED)
15129 // only set if player has anything that can be dropped
15130 player->is_dropping_pressed = TRUE;
15132 // check if drop key was pressed long enough for EM style dynamite
15133 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15136 // check if anything can be dropped at the current position
15137 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15140 // collected custom elements can only be dropped on empty fields
15141 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15144 if (old_element != EL_EMPTY)
15145 Back[dropx][dropy] = old_element; // store old element on this field
15147 ResetGfxAnimation(dropx, dropy);
15148 ResetRandomAnimationValue(dropx, dropy);
15150 if (player->inventory_size > 0 ||
15151 player->inventory_infinite_element != EL_UNDEFINED)
15153 if (player->inventory_size > 0)
15155 player->inventory_size--;
15157 DrawGameDoorValues();
15159 if (new_element == EL_DYNAMITE)
15160 new_element = EL_DYNAMITE_ACTIVE;
15161 else if (new_element == EL_EM_DYNAMITE)
15162 new_element = EL_EM_DYNAMITE_ACTIVE;
15163 else if (new_element == EL_SP_DISK_RED)
15164 new_element = EL_SP_DISK_RED_ACTIVE;
15167 Tile[dropx][dropy] = new_element;
15169 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15170 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15171 el2img(Tile[dropx][dropy]), 0);
15173 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15175 // needed if previous element just changed to "empty" in the last frame
15176 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15178 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15179 player->index_bit, drop_side);
15180 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15182 player->index_bit, drop_side);
15184 TestIfElementTouchesCustomElement(dropx, dropy);
15186 else // player is dropping a dyna bomb
15188 player->dynabombs_left--;
15190 Tile[dropx][dropy] = new_element;
15192 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15193 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15194 el2img(Tile[dropx][dropy]), 0);
15196 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15199 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15200 InitField_WithBug1(dropx, dropy, FALSE);
15202 new_element = Tile[dropx][dropy]; // element might have changed
15204 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15205 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15207 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15208 MovDir[dropx][dropy] = drop_direction;
15210 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15212 // do not cause impact style collision by dropping elements that can fall
15213 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15216 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15217 player->is_dropping = TRUE;
15219 player->drop_pressed_delay = 0;
15220 player->is_dropping_pressed = FALSE;
15222 player->drop_x = dropx;
15223 player->drop_y = dropy;
15228 // ----------------------------------------------------------------------------
15229 // game sound playing functions
15230 // ----------------------------------------------------------------------------
15232 static int *loop_sound_frame = NULL;
15233 static int *loop_sound_volume = NULL;
15235 void InitPlayLevelSound(void)
15237 int num_sounds = getSoundListSize();
15239 checked_free(loop_sound_frame);
15240 checked_free(loop_sound_volume);
15242 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15243 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15246 static void PlayLevelSound(int x, int y, int nr)
15248 int sx = SCREENX(x), sy = SCREENY(y);
15249 int volume, stereo_position;
15250 int max_distance = 8;
15251 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15253 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15254 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15257 if (!IN_LEV_FIELD(x, y) ||
15258 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15259 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15262 volume = SOUND_MAX_VOLUME;
15264 if (!IN_SCR_FIELD(sx, sy))
15266 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15267 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15269 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15272 stereo_position = (SOUND_MAX_LEFT +
15273 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15274 (SCR_FIELDX + 2 * max_distance));
15276 if (IS_LOOP_SOUND(nr))
15278 /* This assures that quieter loop sounds do not overwrite louder ones,
15279 while restarting sound volume comparison with each new game frame. */
15281 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15284 loop_sound_volume[nr] = volume;
15285 loop_sound_frame[nr] = FrameCounter;
15288 PlaySoundExt(nr, volume, stereo_position, type);
15291 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15293 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15294 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15295 y < LEVELY(BY1) ? LEVELY(BY1) :
15296 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15300 static void PlayLevelSoundAction(int x, int y, int action)
15302 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15305 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15307 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15309 if (sound_effect != SND_UNDEFINED)
15310 PlayLevelSound(x, y, sound_effect);
15313 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15316 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15318 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15319 PlayLevelSound(x, y, sound_effect);
15322 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15324 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15326 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15327 PlayLevelSound(x, y, sound_effect);
15330 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15332 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15334 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15335 StopSound(sound_effect);
15338 static int getLevelMusicNr(void)
15340 int level_pos = level_nr - leveldir_current->first_level;
15342 if (levelset.music[level_nr] != MUS_UNDEFINED)
15343 return levelset.music[level_nr]; // from config file
15345 return MAP_NOCONF_MUSIC(level_pos); // from music dir
15348 static void FadeLevelSounds(void)
15353 static void FadeLevelMusic(void)
15355 int music_nr = getLevelMusicNr();
15356 char *curr_music = getCurrentlyPlayingMusicFilename();
15357 char *next_music = getMusicInfoEntryFilename(music_nr);
15359 if (!strEqual(curr_music, next_music))
15363 void FadeLevelSoundsAndMusic(void)
15369 static void PlayLevelMusic(void)
15371 int music_nr = getLevelMusicNr();
15372 char *curr_music = getCurrentlyPlayingMusicFilename();
15373 char *next_music = getMusicInfoEntryFilename(music_nr);
15375 if (!strEqual(curr_music, next_music))
15376 PlayMusicLoop(music_nr);
15379 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15381 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15383 int x = xx - offset;
15384 int y = yy - offset;
15389 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15393 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15397 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15401 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15405 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15409 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15413 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15416 case SOUND_android_clone:
15417 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15420 case SOUND_android_move:
15421 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15425 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15429 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15433 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15436 case SOUND_eater_eat:
15437 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15441 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15444 case SOUND_collect:
15445 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15448 case SOUND_diamond:
15449 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15453 // !!! CHECK THIS !!!
15455 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15457 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15461 case SOUND_wonderfall:
15462 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15466 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15470 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15474 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15478 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15482 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15486 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15490 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15494 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15497 case SOUND_exit_open:
15498 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15501 case SOUND_exit_leave:
15502 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15505 case SOUND_dynamite:
15506 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15510 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15514 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15518 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15522 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15526 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15530 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15534 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15539 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15541 int element = map_element_SP_to_RND(element_sp);
15542 int action = map_action_SP_to_RND(action_sp);
15543 int offset = (setup.sp_show_border_elements ? 0 : 1);
15544 int x = xx - offset;
15545 int y = yy - offset;
15547 PlayLevelSoundElementAction(x, y, element, action);
15550 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15552 int element = map_element_MM_to_RND(element_mm);
15553 int action = map_action_MM_to_RND(action_mm);
15555 int x = xx - offset;
15556 int y = yy - offset;
15558 if (!IS_MM_ELEMENT(element))
15559 element = EL_MM_DEFAULT;
15561 PlayLevelSoundElementAction(x, y, element, action);
15564 void PlaySound_MM(int sound_mm)
15566 int sound = map_sound_MM_to_RND(sound_mm);
15568 if (sound == SND_UNDEFINED)
15574 void PlaySoundLoop_MM(int sound_mm)
15576 int sound = map_sound_MM_to_RND(sound_mm);
15578 if (sound == SND_UNDEFINED)
15581 PlaySoundLoop(sound);
15584 void StopSound_MM(int sound_mm)
15586 int sound = map_sound_MM_to_RND(sound_mm);
15588 if (sound == SND_UNDEFINED)
15594 void RaiseScore(int value)
15596 game.score += value;
15598 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15600 DisplayGameControlValues();
15603 void RaiseScoreElement(int element)
15608 case EL_BD_DIAMOND:
15609 case EL_EMERALD_YELLOW:
15610 case EL_EMERALD_RED:
15611 case EL_EMERALD_PURPLE:
15612 case EL_SP_INFOTRON:
15613 RaiseScore(level.score[SC_EMERALD]);
15616 RaiseScore(level.score[SC_DIAMOND]);
15619 RaiseScore(level.score[SC_CRYSTAL]);
15622 RaiseScore(level.score[SC_PEARL]);
15625 case EL_BD_BUTTERFLY:
15626 case EL_SP_ELECTRON:
15627 RaiseScore(level.score[SC_BUG]);
15630 case EL_BD_FIREFLY:
15631 case EL_SP_SNIKSNAK:
15632 RaiseScore(level.score[SC_SPACESHIP]);
15635 case EL_DARK_YAMYAM:
15636 RaiseScore(level.score[SC_YAMYAM]);
15639 RaiseScore(level.score[SC_ROBOT]);
15642 RaiseScore(level.score[SC_PACMAN]);
15645 RaiseScore(level.score[SC_NUT]);
15648 case EL_EM_DYNAMITE:
15649 case EL_SP_DISK_RED:
15650 case EL_DYNABOMB_INCREASE_NUMBER:
15651 case EL_DYNABOMB_INCREASE_SIZE:
15652 case EL_DYNABOMB_INCREASE_POWER:
15653 RaiseScore(level.score[SC_DYNAMITE]);
15655 case EL_SHIELD_NORMAL:
15656 case EL_SHIELD_DEADLY:
15657 RaiseScore(level.score[SC_SHIELD]);
15659 case EL_EXTRA_TIME:
15660 RaiseScore(level.extra_time_score);
15674 case EL_DC_KEY_WHITE:
15675 RaiseScore(level.score[SC_KEY]);
15678 RaiseScore(element_info[element].collect_score);
15683 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15685 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15689 // prevent short reactivation of overlay buttons while closing door
15690 SetOverlayActive(FALSE);
15691 UnmapGameButtons();
15693 // door may still be open due to skipped or envelope style request
15694 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
15697 if (network.enabled)
15698 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15702 FadeSkipNextFadeIn();
15704 SetGameStatus(GAME_MODE_MAIN);
15709 else // continue playing the game
15711 if (tape.playing && tape.deactivate_display)
15712 TapeDeactivateDisplayOff(TRUE);
15714 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15716 if (tape.playing && tape.deactivate_display)
15717 TapeDeactivateDisplayOn();
15721 void RequestQuitGame(boolean escape_key_pressed)
15723 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15724 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15725 level_editor_test_game);
15726 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15727 quick_quit || score_info_tape_play);
15729 RequestQuitGameExt(skip_request, quick_quit,
15730 "Do you really want to quit the game?");
15733 static char *getRestartGameMessage(void)
15735 boolean play_again = hasStartedNetworkGame();
15736 static char message[MAX_OUTPUT_LINESIZE];
15737 char *game_over_text = "Game over!";
15738 char *play_again_text = " Play it again?";
15740 if (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
15741 game_mm.game_over_message != NULL)
15742 game_over_text = game_mm.game_over_message;
15744 snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text,
15745 (play_again ? play_again_text : ""));
15750 static void RequestRestartGame(void)
15752 char *message = getRestartGameMessage();
15753 boolean has_started_game = hasStartedNetworkGame();
15754 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15755 int door_state = DOOR_CLOSE_1;
15757 if (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game)
15759 CloseDoor(door_state);
15761 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15765 // if game was invoked from level editor, also close tape recorder door
15766 if (level_editor_test_game)
15767 door_state = DOOR_CLOSE_ALL;
15769 CloseDoor(door_state);
15771 SetGameStatus(GAME_MODE_MAIN);
15777 boolean CheckRestartGame(void)
15779 static int game_over_delay = 0;
15780 int game_over_delay_value = 50;
15781 boolean game_over = checkGameFailed();
15785 game_over_delay = game_over_delay_value;
15790 if (game_over_delay > 0)
15792 if (game_over_delay == game_over_delay_value / 2)
15793 PlaySound(SND_GAME_LOSING);
15800 // do not handle game over if request dialog is already active
15801 if (game.request_active)
15804 // do not ask to play again if game was never actually played
15805 if (!game.GamePlayed)
15808 // do not ask to play again if this was disabled in setup menu
15809 if (!setup.ask_on_game_over)
15812 RequestRestartGame();
15817 boolean checkGameSolved(void)
15819 // set for all game engines if level was solved
15820 return game.LevelSolved_GameEnd;
15823 boolean checkGameFailed(void)
15825 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15826 return (game_em.game_over && !game_em.level_solved);
15827 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15828 return (game_sp.game_over && !game_sp.level_solved);
15829 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15830 return (game_mm.game_over && !game_mm.level_solved);
15831 else // GAME_ENGINE_TYPE_RND
15832 return (game.GameOver && !game.LevelSolved);
15835 boolean checkGameEnded(void)
15837 return (checkGameSolved() || checkGameFailed());
15841 // ----------------------------------------------------------------------------
15842 // random generator functions
15843 // ----------------------------------------------------------------------------
15845 unsigned int InitEngineRandom_RND(int seed)
15847 game.num_random_calls = 0;
15849 return InitEngineRandom(seed);
15852 unsigned int RND(int max)
15856 game.num_random_calls++;
15858 return GetEngineRandom(max);
15865 // ----------------------------------------------------------------------------
15866 // game engine snapshot handling functions
15867 // ----------------------------------------------------------------------------
15869 struct EngineSnapshotInfo
15871 // runtime values for custom element collect score
15872 int collect_score[NUM_CUSTOM_ELEMENTS];
15874 // runtime values for group element choice position
15875 int choice_pos[NUM_GROUP_ELEMENTS];
15877 // runtime values for belt position animations
15878 int belt_graphic[4][NUM_BELT_PARTS];
15879 int belt_anim_mode[4][NUM_BELT_PARTS];
15882 static struct EngineSnapshotInfo engine_snapshot_rnd;
15883 static char *snapshot_level_identifier = NULL;
15884 static int snapshot_level_nr = -1;
15886 static void SaveEngineSnapshotValues_RND(void)
15888 static int belt_base_active_element[4] =
15890 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15891 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15892 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15893 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15897 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15899 int element = EL_CUSTOM_START + i;
15901 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15904 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15906 int element = EL_GROUP_START + i;
15908 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15911 for (i = 0; i < 4; i++)
15913 for (j = 0; j < NUM_BELT_PARTS; j++)
15915 int element = belt_base_active_element[i] + j;
15916 int graphic = el2img(element);
15917 int anim_mode = graphic_info[graphic].anim_mode;
15919 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15920 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15925 static void LoadEngineSnapshotValues_RND(void)
15927 unsigned int num_random_calls = game.num_random_calls;
15930 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15932 int element = EL_CUSTOM_START + i;
15934 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15937 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15939 int element = EL_GROUP_START + i;
15941 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15944 for (i = 0; i < 4; i++)
15946 for (j = 0; j < NUM_BELT_PARTS; j++)
15948 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15949 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15951 graphic_info[graphic].anim_mode = anim_mode;
15955 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15957 InitRND(tape.random_seed);
15958 for (i = 0; i < num_random_calls; i++)
15962 if (game.num_random_calls != num_random_calls)
15964 Error("number of random calls out of sync");
15965 Error("number of random calls should be %d", num_random_calls);
15966 Error("number of random calls is %d", game.num_random_calls);
15968 Fail("this should not happen -- please debug");
15972 void FreeEngineSnapshotSingle(void)
15974 FreeSnapshotSingle();
15976 setString(&snapshot_level_identifier, NULL);
15977 snapshot_level_nr = -1;
15980 void FreeEngineSnapshotList(void)
15982 FreeSnapshotList();
15985 static ListNode *SaveEngineSnapshotBuffers(void)
15987 ListNode *buffers = NULL;
15989 // copy some special values to a structure better suited for the snapshot
15991 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15992 SaveEngineSnapshotValues_RND();
15993 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15994 SaveEngineSnapshotValues_EM();
15995 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15996 SaveEngineSnapshotValues_SP(&buffers);
15997 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15998 SaveEngineSnapshotValues_MM();
16000 // save values stored in special snapshot structure
16002 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16003 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
16004 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16005 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
16006 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16007 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
16008 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16009 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
16011 // save further RND engine values
16013 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
16014 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
16015 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
16017 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
16018 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
16019 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
16020 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16021 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16023 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16024 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16025 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16027 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16029 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16030 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16032 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16033 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16034 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16035 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16036 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16037 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16038 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16039 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16040 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16041 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16042 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16043 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16044 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16045 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16046 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16047 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16048 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16049 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16051 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16052 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16054 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16055 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16056 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16058 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16059 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16061 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16062 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16063 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16064 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16065 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16066 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16068 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16069 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16072 ListNode *node = engine_snapshot_list_rnd;
16075 while (node != NULL)
16077 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16082 Debug("game:playing:SaveEngineSnapshotBuffers",
16083 "size of engine snapshot: %d bytes", num_bytes);
16089 void SaveEngineSnapshotSingle(void)
16091 ListNode *buffers = SaveEngineSnapshotBuffers();
16093 // finally save all snapshot buffers to single snapshot
16094 SaveSnapshotSingle(buffers);
16096 // save level identification information
16097 setString(&snapshot_level_identifier, leveldir_current->identifier);
16098 snapshot_level_nr = level_nr;
16101 boolean CheckSaveEngineSnapshotToList(void)
16103 boolean save_snapshot =
16104 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16105 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16106 game.snapshot.changed_action) ||
16107 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16108 game.snapshot.collected_item));
16110 game.snapshot.changed_action = FALSE;
16111 game.snapshot.collected_item = FALSE;
16112 game.snapshot.save_snapshot = save_snapshot;
16114 return save_snapshot;
16117 void SaveEngineSnapshotToList(void)
16119 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16123 ListNode *buffers = SaveEngineSnapshotBuffers();
16125 // finally save all snapshot buffers to snapshot list
16126 SaveSnapshotToList(buffers);
16129 void SaveEngineSnapshotToListInitial(void)
16131 FreeEngineSnapshotList();
16133 SaveEngineSnapshotToList();
16136 static void LoadEngineSnapshotValues(void)
16138 // restore special values from snapshot structure
16140 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16141 LoadEngineSnapshotValues_RND();
16142 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16143 LoadEngineSnapshotValues_EM();
16144 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16145 LoadEngineSnapshotValues_SP();
16146 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16147 LoadEngineSnapshotValues_MM();
16150 void LoadEngineSnapshotSingle(void)
16152 LoadSnapshotSingle();
16154 LoadEngineSnapshotValues();
16157 static void LoadEngineSnapshot_Undo(int steps)
16159 LoadSnapshotFromList_Older(steps);
16161 LoadEngineSnapshotValues();
16164 static void LoadEngineSnapshot_Redo(int steps)
16166 LoadSnapshotFromList_Newer(steps);
16168 LoadEngineSnapshotValues();
16171 boolean CheckEngineSnapshotSingle(void)
16173 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16174 snapshot_level_nr == level_nr);
16177 boolean CheckEngineSnapshotList(void)
16179 return CheckSnapshotList();
16183 // ---------- new game button stuff -------------------------------------------
16190 boolean *setup_value;
16191 boolean allowed_on_tape;
16192 boolean is_touch_button;
16194 } gamebutton_info[NUM_GAME_BUTTONS] =
16197 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16198 GAME_CTRL_ID_STOP, NULL,
16199 TRUE, FALSE, "stop game"
16202 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16203 GAME_CTRL_ID_PAUSE, NULL,
16204 TRUE, FALSE, "pause game"
16207 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16208 GAME_CTRL_ID_PLAY, NULL,
16209 TRUE, FALSE, "play game"
16212 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16213 GAME_CTRL_ID_UNDO, NULL,
16214 TRUE, FALSE, "undo step"
16217 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16218 GAME_CTRL_ID_REDO, NULL,
16219 TRUE, FALSE, "redo step"
16222 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16223 GAME_CTRL_ID_SAVE, NULL,
16224 TRUE, FALSE, "save game"
16227 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16228 GAME_CTRL_ID_PAUSE2, NULL,
16229 TRUE, FALSE, "pause game"
16232 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16233 GAME_CTRL_ID_LOAD, NULL,
16234 TRUE, FALSE, "load game"
16237 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16238 GAME_CTRL_ID_PANEL_STOP, NULL,
16239 FALSE, FALSE, "stop game"
16242 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16243 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16244 FALSE, FALSE, "pause game"
16247 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16248 GAME_CTRL_ID_PANEL_PLAY, NULL,
16249 FALSE, FALSE, "play game"
16252 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16253 GAME_CTRL_ID_TOUCH_STOP, NULL,
16254 FALSE, TRUE, "stop game"
16257 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16258 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16259 FALSE, TRUE, "pause game"
16262 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16263 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16264 TRUE, FALSE, "background music on/off"
16267 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16268 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16269 TRUE, FALSE, "sound loops on/off"
16272 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16273 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16274 TRUE, FALSE, "normal sounds on/off"
16277 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16278 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16279 FALSE, FALSE, "background music on/off"
16282 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16283 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16284 FALSE, FALSE, "sound loops on/off"
16287 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16288 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16289 FALSE, FALSE, "normal sounds on/off"
16293 void CreateGameButtons(void)
16297 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16299 int graphic = gamebutton_info[i].graphic;
16300 struct GraphicInfo *gfx = &graphic_info[graphic];
16301 struct XY *pos = gamebutton_info[i].pos;
16302 struct GadgetInfo *gi;
16305 unsigned int event_mask;
16306 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16307 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16308 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16309 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16310 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16311 int gd_x = gfx->src_x;
16312 int gd_y = gfx->src_y;
16313 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16314 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16315 int gd_xa = gfx->src_x + gfx->active_xoffset;
16316 int gd_ya = gfx->src_y + gfx->active_yoffset;
16317 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16318 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16319 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16320 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16323 // do not use touch buttons if overlay touch buttons are disabled
16324 if (is_touch_button && !setup.touch.overlay_buttons)
16327 if (gfx->bitmap == NULL)
16329 game_gadget[id] = NULL;
16334 if (id == GAME_CTRL_ID_STOP ||
16335 id == GAME_CTRL_ID_PANEL_STOP ||
16336 id == GAME_CTRL_ID_TOUCH_STOP ||
16337 id == GAME_CTRL_ID_PLAY ||
16338 id == GAME_CTRL_ID_PANEL_PLAY ||
16339 id == GAME_CTRL_ID_SAVE ||
16340 id == GAME_CTRL_ID_LOAD)
16342 button_type = GD_TYPE_NORMAL_BUTTON;
16344 event_mask = GD_EVENT_RELEASED;
16346 else if (id == GAME_CTRL_ID_UNDO ||
16347 id == GAME_CTRL_ID_REDO)
16349 button_type = GD_TYPE_NORMAL_BUTTON;
16351 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16355 button_type = GD_TYPE_CHECK_BUTTON;
16356 checked = (gamebutton_info[i].setup_value != NULL ?
16357 *gamebutton_info[i].setup_value : FALSE);
16358 event_mask = GD_EVENT_PRESSED;
16361 gi = CreateGadget(GDI_CUSTOM_ID, id,
16362 GDI_IMAGE_ID, graphic,
16363 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16366 GDI_WIDTH, gfx->width,
16367 GDI_HEIGHT, gfx->height,
16368 GDI_TYPE, button_type,
16369 GDI_STATE, GD_BUTTON_UNPRESSED,
16370 GDI_CHECKED, checked,
16371 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16372 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16373 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16374 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16375 GDI_DIRECT_DRAW, FALSE,
16376 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16377 GDI_EVENT_MASK, event_mask,
16378 GDI_CALLBACK_ACTION, HandleGameButtons,
16382 Fail("cannot create gadget");
16384 game_gadget[id] = gi;
16388 void FreeGameButtons(void)
16392 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16393 FreeGadget(game_gadget[i]);
16396 static void UnmapGameButtonsAtSamePosition(int id)
16400 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16402 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16403 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16404 UnmapGadget(game_gadget[i]);
16407 static void UnmapGameButtonsAtSamePosition_All(void)
16409 if (setup.show_load_save_buttons)
16411 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16412 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16413 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16415 else if (setup.show_undo_redo_buttons)
16417 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16418 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16419 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16423 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16424 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16425 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16427 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16428 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16429 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16433 void MapLoadSaveButtons(void)
16435 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16436 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16438 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16439 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16442 void MapUndoRedoButtons(void)
16444 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16445 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16447 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16448 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16451 void ModifyPauseButtons(void)
16455 GAME_CTRL_ID_PAUSE,
16456 GAME_CTRL_ID_PAUSE2,
16457 GAME_CTRL_ID_PANEL_PAUSE,
16458 GAME_CTRL_ID_TOUCH_PAUSE,
16463 for (i = 0; ids[i] > -1; i++)
16464 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16467 static void MapGameButtonsExt(boolean on_tape)
16471 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16473 if ((i == GAME_CTRL_ID_UNDO ||
16474 i == GAME_CTRL_ID_REDO) &&
16475 game_status != GAME_MODE_PLAYING)
16478 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16479 MapGadget(game_gadget[i]);
16482 UnmapGameButtonsAtSamePosition_All();
16484 RedrawGameButtons();
16487 static void UnmapGameButtonsExt(boolean on_tape)
16491 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16492 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16493 UnmapGadget(game_gadget[i]);
16496 static void RedrawGameButtonsExt(boolean on_tape)
16500 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16501 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16502 RedrawGadget(game_gadget[i]);
16505 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16510 gi->checked = state;
16513 static void RedrawSoundButtonGadget(int id)
16515 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16516 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16517 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16518 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16519 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16520 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16523 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16524 RedrawGadget(game_gadget[id2]);
16527 void MapGameButtons(void)
16529 MapGameButtonsExt(FALSE);
16532 void UnmapGameButtons(void)
16534 UnmapGameButtonsExt(FALSE);
16537 void RedrawGameButtons(void)
16539 RedrawGameButtonsExt(FALSE);
16542 void MapGameButtonsOnTape(void)
16544 MapGameButtonsExt(TRUE);
16547 void UnmapGameButtonsOnTape(void)
16549 UnmapGameButtonsExt(TRUE);
16552 void RedrawGameButtonsOnTape(void)
16554 RedrawGameButtonsExt(TRUE);
16557 static void GameUndoRedoExt(void)
16559 ClearPlayerAction();
16561 tape.pausing = TRUE;
16564 UpdateAndDisplayGameControlValues();
16566 DrawCompleteVideoDisplay();
16567 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16568 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16569 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16571 ModifyPauseButtons();
16576 static void GameUndo(int steps)
16578 if (!CheckEngineSnapshotList())
16581 int tape_property_bits = tape.property_bits;
16583 LoadEngineSnapshot_Undo(steps);
16585 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16590 static void GameRedo(int steps)
16592 if (!CheckEngineSnapshotList())
16595 int tape_property_bits = tape.property_bits;
16597 LoadEngineSnapshot_Redo(steps);
16599 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16604 static void HandleGameButtonsExt(int id, int button)
16606 static boolean game_undo_executed = FALSE;
16607 int steps = BUTTON_STEPSIZE(button);
16608 boolean handle_game_buttons =
16609 (game_status == GAME_MODE_PLAYING ||
16610 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16612 if (!handle_game_buttons)
16617 case GAME_CTRL_ID_STOP:
16618 case GAME_CTRL_ID_PANEL_STOP:
16619 case GAME_CTRL_ID_TOUCH_STOP:
16624 case GAME_CTRL_ID_PAUSE:
16625 case GAME_CTRL_ID_PAUSE2:
16626 case GAME_CTRL_ID_PANEL_PAUSE:
16627 case GAME_CTRL_ID_TOUCH_PAUSE:
16628 if (network.enabled && game_status == GAME_MODE_PLAYING)
16631 SendToServer_ContinuePlaying();
16633 SendToServer_PausePlaying();
16636 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16638 game_undo_executed = FALSE;
16642 case GAME_CTRL_ID_PLAY:
16643 case GAME_CTRL_ID_PANEL_PLAY:
16644 if (game_status == GAME_MODE_MAIN)
16646 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16648 else if (tape.pausing)
16650 if (network.enabled)
16651 SendToServer_ContinuePlaying();
16653 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16657 case GAME_CTRL_ID_UNDO:
16658 // Important: When using "save snapshot when collecting an item" mode,
16659 // load last (current) snapshot for first "undo" after pressing "pause"
16660 // (else the last-but-one snapshot would be loaded, because the snapshot
16661 // pointer already points to the last snapshot when pressing "pause",
16662 // which is fine for "every step/move" mode, but not for "every collect")
16663 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16664 !game_undo_executed)
16667 game_undo_executed = TRUE;
16672 case GAME_CTRL_ID_REDO:
16676 case GAME_CTRL_ID_SAVE:
16680 case GAME_CTRL_ID_LOAD:
16684 case SOUND_CTRL_ID_MUSIC:
16685 case SOUND_CTRL_ID_PANEL_MUSIC:
16686 if (setup.sound_music)
16688 setup.sound_music = FALSE;
16692 else if (audio.music_available)
16694 setup.sound = setup.sound_music = TRUE;
16696 SetAudioMode(setup.sound);
16698 if (game_status == GAME_MODE_PLAYING)
16702 RedrawSoundButtonGadget(id);
16706 case SOUND_CTRL_ID_LOOPS:
16707 case SOUND_CTRL_ID_PANEL_LOOPS:
16708 if (setup.sound_loops)
16709 setup.sound_loops = FALSE;
16710 else if (audio.loops_available)
16712 setup.sound = setup.sound_loops = TRUE;
16714 SetAudioMode(setup.sound);
16717 RedrawSoundButtonGadget(id);
16721 case SOUND_CTRL_ID_SIMPLE:
16722 case SOUND_CTRL_ID_PANEL_SIMPLE:
16723 if (setup.sound_simple)
16724 setup.sound_simple = FALSE;
16725 else if (audio.sound_available)
16727 setup.sound = setup.sound_simple = TRUE;
16729 SetAudioMode(setup.sound);
16732 RedrawSoundButtonGadget(id);
16741 static void HandleGameButtons(struct GadgetInfo *gi)
16743 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16746 void HandleSoundButtonKeys(Key key)
16748 if (key == setup.shortcut.sound_simple)
16749 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16750 else if (key == setup.shortcut.sound_loops)
16751 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16752 else if (key == setup.shortcut.sound_music)
16753 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);